X-Git-Url: https://git.siccegge.de//index.cgi?p=forks%2Fvmdebootstrap.git;a=blobdiff_plain;f=vmdebootstrap;h=c9764ae67c44fffe66cdff7b42d23fe4acbca7a6;hp=8d90dd4f58166a50f6c7ddc74b126ec896ae7ebd;hb=HEAD;hpb=3c3fc0a3b29b3b3a3106ddad048f2f4e73a9413c diff --git a/vmdebootstrap b/vmdebootstrap index 8d90dd4..c9764ae 100755 --- a/vmdebootstrap +++ b/vmdebootstrap @@ -1,7 +1,7 @@ #! /usr/bin/python # Copyright 2011-2013 Lars Wirzenius # Copyright 2012 Codethink Limited -# Copyright 2014 Neil Williams +# Copyright 2014-2015 Neil Williams # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,16 +20,20 @@ import cliapp import crypt import logging import os +import glob import re +import sys import shutil +import datetime import subprocess import tempfile import time +from distro_info import DebianDistroInfo, UbuntuDistroInfo -__version__ = '0.7' +__version__ = '0.11' -# pylint: disable=invalid-name +# pylint: disable=invalid-name,line-too-long,missing-docstring,too-many-branches class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-methods @@ -38,6 +42,38 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth super(VmDebootstrap, self).__init__(progname, version, description, epilog) self.remove_dirs = [] self.mount_points = [] + self.debian_info = DebianDistroInfo() + self.ubuntu_info = UbuntuDistroInfo() + self.bootdir = None + self.efi_arch_table = { + 'amd64': { + 'removable': '/EFI/boot/bootx64.efi', # destination location + 'install': '/EFI/debian/grubx64.efi', # package location + 'package': 'grub-efi-amd64', # bootstrap package + 'bin_package': 'grub-efi-amd64-bin', # binary only + 'extra': 'i386', # architecture to add binary package + 'exclusive': False, # only EFI supported for this arch. + 'target': 'x86_64-efi', # grub target name + }, + 'i386': { + 'removable': '/EFI/boot/bootia32.efi', + 'install': '/EFI/debian/grubia32.efi', + 'package': 'grub-efi-ia32', + 'bin_package': 'grub-efi-ia32-bin', + 'extra': None, + 'exclusive': False, + 'target': 'i386-efi', + }, + 'arm64': { + 'removable': '/EFI/boot/bootaa64.efi', + 'install': '/EFI/debian/grubaa64.efi', + 'package': 'grub-efi-arm64', + 'bin_package': 'grub-efi-arm64-bin', + 'extra': None, + 'exclusive': True, + 'target': 'arm64-efi', + } + } def add_settings(self): default_arch = subprocess.check_output( @@ -66,6 +102,14 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth ['bootoffset'], 'Space to leave at start of the image for bootloader', default='0') + self.settings.boolean( + ['use-uefi'], + 'Setup image for UEFI boot', + default=False) + self.settings.bytesize( + ['esp-size'], + 'Size of EFI System Partition - requires use-uefi', + default='5mib') self.settings.string( ['part-type'], 'Partition type to use for this image', @@ -82,7 +126,10 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth 'set up foreign debootstrap environment using provided program (ie binfmt handler)') self.settings.string( ['variant'], - 'select debootstrap variant it not using the default') + 'select debootstrap variant if not using the default [deprecated]') + self.settings.string_list( + ['debootstrapopts'], + 'pass additional options to debootstrap'), self.settings.boolean( ['extlinux'], 'install extlinux?', @@ -120,6 +167,10 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth self.settings.boolean( ['no-kernel'], 'do not install a linux package') + self.settings.string( + ['kernel-package'], + 'install PACKAGE instead of the default kernel package', + metavar='PACKAGE') self.settings.boolean( ['enable-dhcp'], 'enable DHCP on eth0') @@ -176,6 +227,10 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth self.settings.boolean( ['pkglist'], 'Create a list of package names included in the image.') + self.settings.boolean( + ['no-acpid'], + 'do not install the acpid package', + default=False) def process_args(self, args): # pylint: disable=too-many-branches,too-many-statements if not self.settings['image'] and not self.settings['tarball']: @@ -184,7 +239,40 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth if self.settings['image'] and not self.settings['size']: raise cliapp.AppException( 'If disk image is specified, you must give image size.') + if not self.debian_info.valid(self.settings['distribution']): + if not self.ubuntu_info.valid(self.settings['distribution']): + raise cliapp.AppException( + '%s is not a valid Debian or Ubuntu suite or codename.' + % self.settings['distribution']) + if not self.settings['use-uefi'] and self.settings['esp-size'] != 5242880: + raise cliapp.AppException( + 'You must specify use-uefi for esp-size to have effect') + if self.settings['arch'] in self.efi_arch_table and\ + self.efi_arch_table[self.settings['arch']]['exclusive'] and\ + not self.settings['use-uefi']: + raise cliapp.AppException( + 'Only UEFI is supported on %s' % self.settings['arch']) + elif self.settings['use-uefi'] and self.settings['arch'] not in self.efi_arch_table: + raise cliapp.AppException( + '%s is not a supported UEFI architecture' % self.settings['arch']) + if self.settings['use-uefi'] and ( + self.settings['bootsize'] or + self.settings['bootoffset']): + raise cliapp.AppException( + 'A separate boot partition is not supported with UEFI') + + if self.settings['use-uefi'] and not self.settings['grub']: + raise cliapp.AppException( + 'UEFI without Grub is not supported.') + + # wheezy (which became oldstable on 04/25/2015) only had amd64 uefi + if self.was_oldstable(datetime.date(2015, 4, 26)): + if self.settings['use-uefi'] and self.settings['arch'] != 'amd64': + raise cliapp.AppException( + 'Only amd64 supports UEFI in Wheezy') + if os.geteuid() != 0: + sys.exit("You need to have root privileges to run this script.") rootdir = None try: rootdev = None @@ -202,22 +290,27 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth self.runcmd(['mkswap', swapdev]) self.mkfs(rootdev, fstype=roottype) rootdir = self.mount(rootdev) - if bootdev: + if self.settings['use-uefi']: + self.bootdir = '%s/%s/%s' % (rootdir, 'boot', 'efi') + logging.debug("bootdir:%s", self.bootdir) + self.mkfs(bootdev, fstype='vfat') + os.makedirs(self.bootdir) + self.mount(bootdev, self.bootdir) + elif bootdev: if self.settings['boottype']: boottype = self.settings['boottype'] else: boottype = 'ext2' self.mkfs(bootdev, fstype=boottype) - bootdir = '%s/%s' % (rootdir, 'boot/') - os.mkdir(bootdir) - self.mount(bootdev, bootdir) + self.bootdir = '%s/%s' % (rootdir, 'boot/') + os.mkdir(self.bootdir) + self.mount(bootdev, self.bootdir) else: rootdir = self.mkdtemp() self.debootstrap(rootdir) self.set_hostname(rootdir) self.create_fstab(rootdir, rootdev, roottype, bootdev, boottype) self.install_debs(rootdir) - self.cleanup_apt_cache(rootdir) self.set_root_password(rootdir) self.create_users(rootdir) self.remove_udev_persistent_rules(rootdir) @@ -225,10 +318,13 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth if self.settings['configure-apt'] or self.settings['apt-mirror']: self.configure_apt(rootdir) self.customize(rootdir) + self.cleanup_apt_cache(rootdir) self.update_initramfs(rootdir) if self.settings['image']: - if self.settings['grub']: + if self.settings['use-uefi']: + self.install_grub_uefi(rootdir) + elif self.settings['grub']: self.install_grub2(rootdev, rootdir) elif self.settings['extlinux']: self.install_extlinux(rootdev, rootdir) @@ -312,7 +408,6 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth self.runcmd(['parted', '-s', self.settings['image'], 'mklabel', self.settings['part-type']]) partoffset = 0 - bootsize = 0 extent = '100%' swap = 256 * 1024 * 1024 if self.settings['swap'] > 0: @@ -322,6 +417,18 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth # minimum 256Mb as default qemu ram is 128Mb logging.debug("Setting minimum 256Mb swap space") extent = "%s%%" % int(100 * (self.settings['size'] - swap) / self.settings['size']) + + if self.settings['use-uefi']: + espsize = self.settings['esp-size'] / (1024 * 1024) + self.message("Using ESP size: %smib %s bytes" % (espsize, self.settings['esp-size'])) + self.runcmd(['parted', '-s', self.settings['image'], + 'mkpart', 'primary', 'fat32', + '1', str(espsize)]) + self.runcmd(['parted', '-s', self.settings['image'], + 'set', '1', 'boot', 'on']) + self.runcmd(['parted', '-s', self.settings['image'], + 'set', '1', 'esp', 'on']) + if self.settings['bootoffset'] and self.settings['bootoffset'] is not '0': # turn v.small offsets into something at least possible to create. if self.settings['bootoffset'] < 1048576: @@ -333,6 +440,8 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth partoffset = self.settings['bootoffset'] / (1024 * 1024) self.message("Using bootoffset: %smib %s bytes" % (partoffset, self.settings['bootoffset'])) if self.settings['bootsize'] and self.settings['bootsize'] is not '0%': + if self.settings['grub'] and not partoffset: + partoffset = 1 bootsize = self.settings['bootsize'] / (1024 * 1024) bootsize += partoffset self.message("Using bootsize %smib: %s bytes" % (bootsize, self.settings['bootsize'])) @@ -342,6 +451,10 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth logging.debug("Starting root partition at %sMb", partoffset) self.runcmd(['parted', '-s', self.settings['image'], 'mkpart', 'primary', str(bootsize), extent]) + elif self.settings['use-uefi']: + bootsize = self.settings['esp-size'] / (1024 * 1024) + 1 + self.runcmd(['parted', '-s', self.settings['image'], + 'mkpart', 'primary', str(bootsize), extent]) else: self.runcmd(['parted', '-s', self.settings['image'], 'mkpart', 'primary', '0%', extent]) @@ -370,12 +483,25 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth def setup_kpartx(self): bootindex = None swapindex = None - out = self.runcmd(['kpartx', '-avs', self.settings['image']]) + if 'freebsd' in os.sys.platform: + out = self.runcmd(['mdconfig', '-a', '-t', 'vnode', '-f', + self.settings['image']]) + else: + out = self.runcmd(['kpartx', '-avs', self.settings['image']]) if self.settings['bootsize'] and self.settings['swap'] > 0: bootindex = 0 rootindex = 1 swapindex = 2 parts = 3 + elif self.settings['use-uefi']: + bootindex = 0 + rootindex = 1 + parts = 2 + elif self.settings['use-uefi'] and self.settings['swap'] > 0: + bootindex = 0 + rootindex = 1 + swapindex = 2 + parts = 3 elif self.settings['bootsize']: bootindex = 0 rootindex = 1 @@ -389,45 +515,131 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth parts = 1 boot = None swap = None - devices = [line.split()[2] - for line in out.splitlines() - if line.startswith('add map ')] + if 'freebsd' in os.sys.platform: + devices = glob.glob("/dev/%ss*" % out.strip()) + else: + devices = ['/dev/mapper/%s' % line.split()[2] + for line in out.splitlines() + if line.startswith('add map ')] if len(devices) != parts: msg = 'Surprising number of partitions - check output of losetup -a' - logging.debug("%s" % self.runcmd(['losetup', '-a'])) + logging.debug("%s", self.runcmd(['losetup', '-a'])) logging.debug("%s: devices=%s parts=%s", msg, devices, parts) raise cliapp.AppException(msg) - root = '/dev/mapper/%s' % devices[rootindex] - if self.settings['bootsize']: - boot = '/dev/mapper/%s' % devices[bootindex] + root = devices[rootindex] + if self.settings['bootsize'] or self.settings['use-uefi']: + boot = devices[bootindex] if self.settings['swap'] > 0: - swap = '/dev/mapper/%s' % devices[swapindex] + swap = devices[swapindex] return root, boot, swap + def _efi_packages(self): + packages = [] + pkg = self.efi_arch_table[self.settings['arch']]['package'] + self.message("Adding %s" % pkg) + packages.append(pkg) + extra = self.efi_arch_table[self.settings['arch']]['extra'] + if extra and isinstance(extra, str): + bin_pkg = self.efi_arch_table[str(extra)]['bin_package'] + self.message("Adding support for %s using %s" % (extra, bin_pkg)) + packages.append(bin_pkg) + return packages + + def _copy_efi_binary(self, efi_removable, efi_install): + logging.debug("using bootdir=%s", self.bootdir) + logging.debug("moving %s to %s", efi_removable, efi_install) + if efi_removable.startswith('/'): + efi_removable = efi_removable[1:] + if efi_install.startswith('/'): + efi_install = efi_install[1:] + efi_output = os.path.join(self.bootdir, efi_removable) + efi_input = os.path.join(self.bootdir, efi_install) + if not os.path.exists(efi_input): + logging.warning("%s does not exist (%s)", efi_input, efi_install) + raise cliapp.AppException("Missing %s" % efi_install) + if not os.path.exists(os.path.dirname(efi_output)): + os.makedirs(os.path.dirname(efi_output)) + logging.debug( + 'Moving UEFI support: %s -> %s', efi_input, efi_output) + if os.path.exists(efi_output): + os.unlink(efi_output) + os.rename(efi_input, efi_output) + + def configure_efi(self): + """ + Copy the bootloader file from the package into the location + so needs to be after grub and kernel already installed. + """ + self.message('Configuring EFI') + efi_removable = str(self.efi_arch_table[self.settings['arch']]['removable']) + efi_install = str(self.efi_arch_table[self.settings['arch']]['install']) + self.message('Installing UEFI support binary') + self._copy_efi_binary(efi_removable, efi_install) + + def configure_extra_efi(self): + extra = str(self.efi_arch_table[self.settings['arch']]['extra']) + if extra: + efi_removable = str(self.efi_arch_table[extra]['removable']) + efi_install = str(self.efi_arch_table[extra]['install']) + self.message('Copying UEFI support binary for %s' % extra) + self._copy_efi_binary(efi_removable, efi_install) + def mkfs(self, device, fstype): self.message('Creating filesystem %s' % fstype) self.runcmd(['mkfs', '-t', fstype, device]) - def debootstrap(self, rootdir): + def suite_to_codename(self, distro): + suite = self.debian_info.codename(distro, datetime.date.today()) + if not suite: + return distro + return suite + + def was_oldstable(self, limit): + suite = self.suite_to_codename(self.settings['distribution']) + # this check is only for debian + if not self.debian_info.valid(suite): + return False + return suite == self.debian_info.old(limit) + + def was_stable(self, limit): + suite = self.suite_to_codename(self.settings['distribution']) + # this check is only for debian + if not self.debian_info.valid(suite): + return False + return suite == self.debian_info.stable(limit) + + def debootstrap(self, rootdir): # pylint: disable=too-many-statements msg = "(%s)" % self.settings['variant'] if self.settings['variant'] else '' - self.message('Debootstrapping %s %s' % (self.settings['distribution'], msg)) + self.message( + 'Debootstrapping %s [%s] %s' % ( + self.settings['distribution'], self.settings['arch'], msg)) include = self.settings['package'] - if not self.settings['foreign']: + if not self.settings['foreign'] and not self.settings['no-acpid']: include.append('acpid') if self.settings['grub']: - include.append('grub-pc') + if self.settings['use-uefi']: + include.extend(self._efi_packages()) + else: + include.append('grub-pc') if not self.settings['no-kernel']: - if self.settings['arch'] == 'i386': - kernel_arch = '486' - elif self.settings['arch'] == 'armhf': - kernel_arch = 'armmp' + if self.settings['kernel-package']: + kernel_image = self.settings['kernel-package'] else: - kernel_arch = self.settings['arch'] - kernel_image = 'linux-image-%s' % kernel_arch + if self.settings['arch'] == 'i386': + # wheezy (which became oldstable on 04/25/2015) used '486' + if self.was_oldstable(datetime.date(2015, 4, 26)): + kernel_arch = '486' + else: + kernel_arch = '586' + elif self.settings['arch'] == 'armhf': + kernel_arch = 'armmp' + else: + kernel_arch = self.settings['arch'] + kernel_image = 'linux-image-%s' % kernel_arch include.append(kernel_image) if self.settings['sudo'] and 'sudo' not in include: @@ -440,7 +652,11 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth '--include=%s' % ','.join(include)) if self.settings['foreign']: args.append('--foreign') - if self.settings['variant']: + if self.settings['debootstrapopts']: + for opt in self.settings['debootstrapopts']: + for part in opt.split(' '): + args.append('--%s' % part) + elif self.settings['variant']: args.append('--variant') args.append(self.settings['variant']) args += [self.settings['distribution'], @@ -484,24 +700,33 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth def create_fstab(self, rootdir, rootdev, roottype, bootdev, boottype): # pylint: disable=too-many-arguments def fsuuid(device): - out = self.runcmd(['blkid', '-c', '/dev/null', '-o', 'value', - '-s', 'UUID', device]) - return out.splitlines()[0].strip() + if 'freebsd' in os.sys.platform: + out = self.runcmd(['grub-probe', '-d', device, '-t', 'fs_uuid']) + return "/dev/ufsid/%s" % out.strip() + else: + out = self.runcmd(['blkid', '-c', '/dev/null', '-o', 'value', + '-s', 'UUID', device]) + return "UUID=%s" % out.splitlines()[0].strip() if rootdev: - rootdevstr = 'UUID=%s' % fsuuid(rootdev) + rootdevstr = fsuuid(rootdev) else: rootdevstr = '/dev/sda1' - if bootdev: - bootdevstr = 'UUID=%s' % fsuuid(bootdev) + if bootdev and not self.settings['use-uefi']: + bootdevstr = fsuuid(bootdev) else: bootdevstr = None 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 'freebsd' in os.sys.platform: + f.write('proc /proc linprocfs rw 0 0\n') + f.write('sys /sys linsysfs rw 0 0\n') + f.write('fdesc /dev/fd fdescfs rw 0 0\n') + else: + f.write('proc /proc proc defaults 0 0\n') + f.write('%s / %s rw 0 1\n' % (rootdevstr, roottype)) if bootdevstr: f.write('%s /boot %s errors=remount-ro 0 2\n' % (bootdevstr, boottype)) if self.settings['swap'] > 0: @@ -544,11 +769,11 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth self.delete_password(rootdir, 'root') def create_users(self, rootdir): - def create_user(user): - self.runcmd(['chroot', rootdir, 'adduser', '--gecos', user, - '--disabled-password', user]) + def create_user(vmuser): + self.runcmd(['chroot', rootdir, 'adduser', '--gecos', vmuser, + '--disabled-password', vmuser]) if self.settings['sudo']: - self.runcmd(['chroot', rootdir, 'adduser', user, 'sudo']) + self.runcmd(['chroot', rootdir, 'adduser', vmuser, 'sudo']) for userpass in self.settings['user']: if '/' in userpass: @@ -576,19 +801,44 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth else: logging.debug('not removing non-existent %s', pathname) + def mask_udev_predictable_rules(self, rootdir): + """ + This can be reset later but to get networking working immediately + on boot, the interface we're going to use must be known without + reference to the eventual machine. + http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/ + """ + self.message('Disabling systemd predictable interface names') + udev_path = os.path.join( + 'etc', 'udev', 'rules.d', '80-net-setup-link.rules') + self.runcmd(['chroot', rootdir, 'ln', '-s', '/dev/null', udev_path]) + def setup_networking(self, rootdir): self.message('Setting up networking') - - f = open(os.path.join(rootdir, 'etc', 'network', 'interfaces'), 'w') - f.write('auto lo\n') - f.write('iface lo inet loopback\n') - - if self.settings['enable-dhcp']: - f.write('\n') - f.write('auto eth0\n') - f.write('iface eth0 inet dhcp\n') - - f.close() + ifc_file = os.path.join(rootdir, 'etc', 'network', 'interfaces') + ifc_d = os.path.join(rootdir, 'etc', 'network', 'interfaces.d') + + # unconditionally write for wheezy (which became oldstable 2015.04.25) + if self.was_oldstable(datetime.date(2015, 4, 26)): + with open(ifc_file, 'w') as netfile: + netfile.write('source /etc/network/interfaces.d/*\n') + elif not os.path.exists(ifc_file): + with open(ifc_file, 'a') as netfile: + netfile.write('source-directory /etc/network/interfaces.d\n') + + if not os.path.exists(ifc_d): + os.mkdir(ifc_d) + ethpath = os.path.join(ifc_d, 'setup') + with open(ethpath, 'w') as eth: + eth.write('auto lo\n') + eth.write('iface lo inet loopback\n') + + if self.settings['enable-dhcp']: + eth.write('\n') + eth.write('auto eth0\n') + eth.write('iface eth0 inet dhcp\n') + # force predictable interface names + self.mask_udev_predictable_rules(rootdir) def append_serial_console(self, rootdir): if self.settings['serial-console']: @@ -599,6 +849,7 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth with open(inittab, 'a') as f: f.write('\nS0:23:respawn:%s\n' % serial_command) + # pylint: disable=no-self-use def _grub_serial_console(self, rootdir): cmdline = 'GRUB_CMDLINE_LINUX_DEFAULT="console=tty0 console=tty1 console=ttyS0,38400n8"' terminal = 'GRUB_TERMINAL="serial gfxterm"' @@ -611,28 +862,73 @@ class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-meth cfg.write("%s\n" % terminal) cfg.write("%s\n" % command) + def _mount_wrapper(self, rootdir): + if 'freebsd' in os.sys.platform: + self.runcmd(['mount', 'dev', '-t', 'devfs', + '%s' % os.path.join(rootdir, 'dev')]) + self.runcmd(['mount', 'proc', '-t', 'linprocfs', + '%s' % os.path.join(rootdir, 'proc')]) + self.runcmd(['mount', 'sys', '-t', 'linsysfs', + '%s' % os.path.join(rootdir, 'sys')]) + else: + self.runcmd(['mount', '/dev', '-t', 'devfs', '-obind', + '%s' % os.path.join(rootdir, 'dev')]) + self.runcmd(['mount', '/proc', '-t', 'proc', '-obind', + '%s' % os.path.join(rootdir, 'proc')]) + self.runcmd(['mount', '/sys', '-t', 'sysfs', '-obind', + '%s' % os.path.join(rootdir, 'sys')]) + + def _umount_wrapper(self, rootdir): + self.runcmd(['umount', os.path.join(rootdir, 'sys')]) + self.runcmd(['umount', os.path.join(rootdir, 'proc')]) + self.runcmd(['umount', os.path.join(rootdir, 'dev')]) + + def install_grub_uefi(self, rootdir): + self.message("Configuring grub-uefi") + target = self.efi_arch_table[self.settings['arch']]['target'] + grub_opts = "--target=%s" % target + logging.debug("Running grub-install with options: %s", grub_opts) + self._mount_wrapper(rootdir) + try: + self.runcmd(['chroot', rootdir, 'update-grub']) + self.runcmd(['chroot', rootdir, 'grub-install', grub_opts]) + except cliapp.AppException as exc: + logging.warning(exc) + self.message( + "Failed to configure grub-uefi for %s" % + self.settings['arch']) + self._umount_wrapper(rootdir) + self.configure_efi() + extra = str(self.efi_arch_table[self.settings['arch']]['extra']) + if extra: + target = self.efi_arch_table[extra]['target'] + grub_opts = "--target=%s" % target + try: + self.runcmd(['chroot', rootdir, 'update-grub']) + self.runcmd(['chroot', rootdir, 'grub-install', grub_opts]) + except cliapp.AppException as exc: + logging.warning(exc) + self.message( + "Failed to configure grub-uefi for %s" % extra) + self.configure_extra_efi() + self._umount_wrapper(rootdir) + def install_grub2(self, rootdev, rootdir): 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]) - self.runcmd(['mount', '/dev', '-t', 'devfs', '-obind', - '%s' % os.path.join(rootdir, 'dev')]) - self.runcmd(['mount', '/proc', '-t', 'proc', '-obind', - '%s' % os.path.join(rootdir, 'proc')]) - self.runcmd(['mount', '/sys', '-t', 'sysfs', '-obind', - '%s' % os.path.join(rootdir, 'sys')]) + grub_opts = os.path.join('/dev', os.path.basename(rootdev)[:-2]) if self.settings['serial-console']: self._grub_serial_console(rootdir) - + logging.debug("Running grub-install with options: %s", grub_opts) + self._mount_wrapper(rootdir) try: self.runcmd(['chroot', rootdir, 'update-grub']) - self.runcmd(['chroot', rootdir, 'grub-install', install_dev]) - except cliapp.AppException: + self.runcmd(['chroot', rootdir, 'grub-install', grub_opts]) + except cliapp.AppException as exc: + logging.warning(exc) 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) + self._umount_wrapper(rootdir) def install_extlinux(self, rootdev, rootdir): if not os.path.exists("/usr/bin/extlinux"): @@ -708,13 +1004,30 @@ append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s if not os.path.exists('/usr/bin/mksquashfs'): logging.warning("Squash selected but mksquashfs not found!") return + logging.debug( + "%s usage: %s", self.settings['image'], + self.runcmd(['du', self.settings['image']])) self.message("Running mksquashfs") suffixed = "%s.squashfs" % self.settings['image'] - self.runcmd(['mksquashfs', self.settings['image'], - suffixed, - '-no-progress', '-comp', 'xz'], ignore_fail=False) - os.unlink(self.settings['image']) - self.settings['image'] = suffixed + if os.path.exists(suffixed): + os.unlink(suffixed) + msg = self.runcmd( + ['mksquashfs', self.settings['image'], + suffixed, + '-no-progress', '-comp', 'xz'], ignore_fail=False) + logging.debug(msg) + check_size = os.path.getsize(suffixed) + if check_size < (1024 * 1024): + logging.warning( + "%s appears to be too small! %s bytes", + suffixed, check_size) + else: + logging.debug("squashed size: %s", check_size) + os.unlink(self.settings['image']) + self.settings['image'] = suffixed + logging.debug( + "%s usage: %s", self.settings['image'], + self.runcmd(['du', self.settings['image']])) def cleanup_system(self): # Clean up after any errors. @@ -732,7 +1045,13 @@ append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s time.sleep(5) self.runcmd(['umount', mount_point], ignore_fail=False) - self.runcmd(['kpartx', '-d', self.settings['image']], ignore_fail=True) + if 'freebsd' in os.sys.platform: + out = self.runcmd(['mdconfig', '-l', '-f', self.settings['image']]) + for devid in out.split(): + self.runcmd(['mdconfig', '-d', '-u', devid], + ignore_fail=True) + else: + self.runcmd(['kpartx', '-d', self.settings['image']], ignore_fail=True) for dirname in self.remove_dirs: shutil.rmtree(dirname) @@ -749,11 +1068,15 @@ append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s script = example self.message('Running customize script %s' % script) logging.info("rootdir=%s image=%s", rootdir, self.settings['image']) - with open('/dev/tty', 'w') as tty: - try: + logging.debug( + "%s usage: %s", self.settings['image'], + self.runcmd(['du', self.settings['image']])) + try: + with open('/dev/tty', 'w') as tty: cliapp.runcmd([script, rootdir, self.settings['image']], stdout=tty, stderr=tty) - except IOError as e: - subprocess.call([script, rootdir, self.settings['image']]) + except IOError: + logging.debug('tty unavailable, trying in headless mode.') + subprocess.call([script, rootdir, self.settings['image']]) def create_tarball(self, rootdir): # Create a tarball of the disk's contents