Control: tags -1 + patch I figured out the grub-probe problem. It was really just grub being outdated because I was building a stable Debian system instead of the unstable one I believed I was building.
Using testing or unstable, grub was able to find the correct device, but thanks to the fact that --bootsize and --grub were incompatible, grub did not have enough space at the start of the disk to install itself there. I changed the partition code to reserve 1 MiB at the start, similar to the amount reserved when --bootsize were not used. With this change in place, vmdebootstrap could create images with a btrfs subvolume as root. The attached patch is working. Please include in a future version of vmdebootstrap. -- Happy hacking Petter Reinholdtsen
--- vmdebootstrap.orig 2014-10-18 20:35:19.000000000 +0200 +++ vmdebootstrap 2014-11-03 05:34:47.319278095 +0100 @@ -39,6 +39,9 @@ self.settings.boolean(['verbose'], 'report what is going on') self.settings.string(['image'], 'put created disk image in FILE', metavar='FILE') + self.settings.string(['roottype'], + 'specify file system type for /', + default='ext4') self.settings.bytesize(['size'], 'create a disk image of size SIZE (%default)', metavar='SIZE', @@ -129,7 +132,7 @@ try: rootdev = None - roottype = 'ext4' + roottype = self.settings['roottype'] bootdev = None boottype = None rootdir = None @@ -141,6 +144,26 @@ (rootdev, bootdev) = self.setup_kpartx() self.mkfs(rootdev, type=roottype) rootdir = self.mount(rootdev) + rootfsdir = rootdir + if 'btrfs' == roottype: + self.settings['package'].append("btrfs-tools") + # Put root in a subvolume, to ease snapshots and volume management + self.message("Creating root file system as btrfs subvolume @") + self.runcmd(['btrfs', 'subvolume', 'create', "%s/@" % rootdir]) + + # Make sure the subvolume mount point show in in + # /proc/mounts for grub-update to figure out the + # device for the root file system. + newrootdir = "%s/build" % rootdir + os.mkdir(newrootdir) + self.mount(rootdev, newrootdir, ['-o','subvol=@']) +# self.runcmd(['btrfs', 'subvolume', 'set-default', '@', rootdir]) + + # Make the btrfs root file system available in the chroot. + os.mkdir("%s/btrfs" % newrootdir) + self.mount(rootdev, "%s/btrfs" % newrootdir) + + rootdir = newrootdir if bootdev: if self.settings['boottype']: boottype = self.settings['boottype'] @@ -168,9 +191,9 @@ if self.settings['image']: if self.settings['grub']: - self.install_grub2(rootdev, rootdir) + self.install_grub2(rootdev, rootdir, rootfsdir) elif self.settings['extlinux']: - self.install_extlinux(rootdev, rootdir) + self.install_extlinux(rootdev, rootdir, rootfsdir) self.append_serial_console(rootdir) self.optimize_image(rootdir) if self.settings['squash']: @@ -221,13 +244,19 @@ logging.debug('mkdir %s' % dirname) return dirname - def mount(self, device, path=None): + def mount(self, device, path=None, opts=None): if not path: mount_point = self.mkdtemp() else: mount_point = path self.message('Mounting %s on %s' % (device, mount_point)) - self.runcmd(['mount', device, mount_point]) + cmd = ['mount'] + if opts is not None: + for opt in opts: + cmd.append(opt) + cmd.append(device) + cmd.append(mount_point) + self.runcmd(cmd) self.mount_points.append(mount_point) logging.debug('mounted %s on %s' % (device, mount_point)) return mount_point @@ -243,13 +272,14 @@ self.runcmd(['parted', '-s', self.settings['image'], 'mklabel', 'msdos']) if self.settings['bootsize'] and self.settings['bootsize'] is not '0%': - bootsize = str(self.settings['bootsize'] / (1024 * 1024)) + reserved = 1 # MiB space for boot loader / grub + bootend = str(reserved + (self.settings['bootsize'] / (1024 * 1024))) self.runcmd(['parted', '-s', self.settings['image'], - 'mkpart', 'primary', 'fat16', '0', bootsize]) + 'mkpart', 'primary', 'fat16', str(reserved), bootend]) else: - bootsize = '0%' + bootend = '0%' self.runcmd(['parted', '-s', self.settings['image'], - 'mkpart', 'primary', bootsize, '100%']) + 'mkpart', 'primary', bootend, '100%']) self.runcmd(['parted', '-s', self.settings['image'], 'set', '1', 'boot', 'on']) @@ -289,7 +319,7 @@ self.runcmd(['mkfs', '-t', type, device]) def debootstrap(self, rootdir): - self.message('Debootstrapping') + self.message('Debootstrapping %s' % self.settings['distribution']) if self.settings['foreign']: necessary_packages = [] @@ -379,7 +409,12 @@ fstab = os.path.join(rootdir, 'etc', 'fstab') with open(fstab, 'w') as f: f.write('proc /proc proc defaults 0 0\n') - f.write('%s / %s errors=remount-ro 0 1\n' % (rootdevstr, roottype)) + if 'btrfs' == roottype: +# f.write('%s / %s defaults 0 1\n' % (rootdevstr, roottype)) + f.write('%s / %s subvol=@ 0 1\n' % (rootdevstr, roottype)) + f.write('%s /btrfs %s defaults 1\n' % (rootdevstr, roottype)) + else: + f.write('%s / %s errors=remount-ro 0 1\n' % (rootdevstr, roottype)) if bootdevstr: f.write('%s /boot %s errors=remount-ro 0 2\n' % (bootdevstr, boottype)) @@ -472,7 +507,8 @@ with open(inittab, 'a') as f: f.write('\nS0:23:respawn:%s\n' % serial_command) - def install_grub2(self, rootdev, rootdir): + def install_grub2(self, rootdev, rootdir, rootfsdir): + # FIXME use rootfsdir self.message("Configuring grub2") # rely on kpartx using consistent naming to map loop0p1 to loop0 install_dev = os.path.join('/dev', os.path.basename(rootdev)[:-2]) @@ -486,13 +522,14 @@ self.runcmd(['chroot', rootdir, 'update-grub']) self.runcmd(['chroot', rootdir, 'grub-install', install_dev]) except cliapp.AppException as e: + self.message(str(e)) self.message("Failed. Is grub2-common installed? Using extlinux.") self.runcmd(['umount', os.path.join(rootdir, 'sys')]) self.runcmd(['umount', os.path.join(rootdir, 'proc')]) self.runcmd(['umount', os.path.join(rootdir, 'dev')]) - self.install_extlinux(rootdev, rootdir) + self.install_extlinux(rootdev, rootdir, rootfsdir) - def install_extlinux(self, rootdev, rootdir): + def install_extlinux(self, rootdev, rootdir, rootfsdir): if not os.path.exists("/usr/bin/extlinux"): self.message("extlinux not installed, skipping.") return @@ -519,7 +556,7 @@ '-s', 'UUID', rootdev]) uuid = out.splitlines()[0].strip() - conf = os.path.join(rootdir, 'extlinux.conf') + conf = os.path.join(rootfsdir, 'extlinux.conf') logging.debug('configure extlinux %s' % conf) f = open(conf, 'w') f.write(''' @@ -528,7 +565,7 @@ label linux kernel %(kernel)s -append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s +append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s %(rootflags)s %(extserial)s ''' % { 'kernel': kernel_image, @@ -536,11 +573,12 @@ 'uuid': uuid, 'kserial': 'console=ttyS0,115200' if self.settings['serial-console'] else '', + 'rootflags': 'rootfsflags=subvol=@' if 'btrfs' == self.settings['roottype'] else '', 'extserial': 'serial 0 115200' if self.settings['serial-console'] else '', }) f.close() - self.runcmd(['extlinux', '--install', rootdir]) + self.runcmd(['extlinux', '--install', rootfsdir]) self.runcmd(['sync']) time.sleep(2)