X-Git-Url: https://git.siccegge.de//index.cgi?a=blobdiff_plain;f=vmdebootstrap;h=6f104da5ea487aa824b3673e013ecfb34c644e23;hb=41ebf61957c1c3d168b5e048e465c6a7b0b57a1c;hp=4ca922b3e0a54df0e9d535ce536404c146975f1a;hpb=9577a75198cb3ad459bf51ae95e5019b42eeddd3;p=forks%2Fvmdebootstrap.git diff --git a/vmdebootstrap b/vmdebootstrap index 4ca922b..6f104da 100755 --- a/vmdebootstrap +++ b/vmdebootstrap @@ -1,6 +1,7 @@ -#!/usr/bin/python +#! /usr/bin/python # Copyright 2011-2013 Lars Wirzenius # Copyright 2012 Codethink Limited +# Copyright 2014 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,104 +21,164 @@ import crypt import logging import os import re -import time import shutil import subprocess import tempfile import time -__version__ = '0.3' +__version__ = '0.6' + +# pylint: disable=invalid-name + +class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-methods -class VmDebootstrap(cliapp.Application): + def __init__(self, progname=None, version=__version__, description=None, epilog=None): + super(VmDebootstrap, self).__init__(progname, version, description, epilog) + self.remove_dirs = [] + self.mount_points = [] def add_settings(self): - default_arch = 'amd64' - - self.settings.boolean(['verbose'], 'report what is going on') - self.settings.string(['image'], 'put created disk image in FILE', - metavar='FILE') - self.settings.bytesize(['size'], - 'create a disk image of size SIZE (%default)', - metavar='SIZE', - default='1G') - self.settings.bytesize(['bootsize'], - 'create boot partition of size SIZE (%default)', - metavar='BOOTSIZE', - default='0%') - self.settings.string(['boottype'], - 'specify file system type for /boot/', - default='ext2') - self.settings.string(['foreign'], - 'set up foreign debootstrap environment using provided program (ie binfmt handler)') - self.settings.string(['variant'], - 'select debootstrap variant it not using the default') + default_arch = subprocess.check_output( + ["dpkg", "--print-architecture"]).strip() + + self.settings.boolean( + ['verbose'], 'report what is going on') + self.settings.string( + ['image'], 'put created disk image in FILE', + metavar='FILE') + self.settings.bytesize( + ['size'], + 'create a disk image of size SIZE (%default)', + metavar='SIZE', + default='1G') + self.settings.bytesize( + ['bootsize'], + 'create boot partition of size SIZE (%default)', + metavar='BOOTSIZE', + default='0%') + self.settings.string( + ['boottype'], + 'specify file system type for /boot/', + default='ext2') + self.settings.bytesize( + ['bootoffset'], + 'Space to leave at start of the image for bootloader', + default='0') + self.settings.string( + ['part-type'], + 'Partition type to use for this image', + default='msdos') + self.settings.string( + ['foreign'], + 'set up foreign debootstrap environment using provided program (ie binfmt handler)') + self.settings.string( + ['variant'], + 'select debootstrap variant it not using the default') self.settings.boolean( ['extlinux'], 'install extlinux?', default=True) - self.settings.string(['tarball'], "tar up the disk's contents in FILE", - metavar='FILE') - self.settings.string(['mirror'], - 'use MIRROR as package source (%default)', - metavar='URL', - default='http://cdn.debian.net/debian/') - self.settings.string(['arch'], 'architecture to use (%default)', - metavar='ARCH', - default=default_arch) - self.settings.string(['distribution'], - 'release to use (%default)', - metavar='NAME', - default='stable') - self.settings.string_list(['package'], 'install PACKAGE onto system') - self.settings.string_list(['custom-package'], - 'install package in DEB file onto system ' - '(not from mirror)', - metavar='DEB') - self.settings.boolean(['no-kernel'], 'do not install a linux package') - self.settings.boolean(['enable-dhcp'], 'enable DHCP on eth0') - self.settings.string(['root-password'], 'set root password', - metavar='PASSWORD') - self.settings.boolean(['lock-root-password'], - 'lock root account so they cannot login?') - self.settings.string(['customize'], - 'run SCRIPT after setting up system', - metavar='SCRIPT') - self.settings.string(['hostname'], - 'set name to HOSTNAME (%default)', - metavar='HOSTNAME', - default='debian') - self.settings.string_list(['user'], - 'create USER with PASSWORD', - metavar='USER/PASSWORD') - self.settings.boolean(['serial-console'], - 'configure image to use a serial console') - self.settings.string(['serial-console-command'], - 'command to manage the serial console, appended ' - 'to /etc/inittab (%default)', - metavar='COMMAND', - default='/sbin/getty -L ttyS0 115200 vt100') - self.settings.boolean(['sudo'], - 'install sudo, and if user is created, add them ' - 'to sudo group') - self.settings.string(['owner'], - 'the user who will own the image when the build ' - 'is complete.') - self.settings.boolean(['squash'], - 'use squashfs on the final image.') - - def process_args(self, args): + self.settings.string( + ['tarball'], + "tar up the disk's contents in FILE", + metavar='FILE') + self.settings.string( + ['apt-mirror'], + 'configure apt to use MIRROR', + metavar='URL') + self.settings.string( + ['mirror'], + 'use MIRROR as package source (%default)', + metavar='URL', + default='http://http.debian.net/debian/') + self.settings.string( + ['arch'], + 'architecture to use (%default)', + metavar='ARCH', + default=default_arch) + self.settings.string( + ['distribution'], + 'release to use (%default)', + metavar='NAME', + default='stable') + self.settings.string_list( + ['package'], + 'install PACKAGE onto system') + self.settings.string_list( + ['custom-package'], + 'install package in DEB file onto system (not from mirror)', + metavar='DEB') + self.settings.boolean( + ['no-kernel'], + 'do not install a linux package') + self.settings.boolean( + ['enable-dhcp'], + 'enable DHCP on eth0') + self.settings.string( + ['root-password'], + 'set root password', + metavar='PASSWORD') + self.settings.boolean( + ['lock-root-password'], + 'lock root account so they cannot login?') + self.settings.string( + ['customize'], + 'run SCRIPT after setting up system', + metavar='SCRIPT') + self.settings.string( + ['hostname'], + 'set name to HOSTNAME (%default)', + metavar='HOSTNAME', + default='debian') + self.settings.string_list( + ['user'], + 'create USER with PASSWORD', + metavar='USER/PASSWORD') + self.settings.boolean( + ['serial-console'], + 'configure image to use a serial console') + self.settings.string( + ['serial-console-command'], + 'command to manage the serial console, appended to /etc/inittab (%default)', + metavar='COMMAND', + default='/sbin/getty -L ttyS0 115200 vt100') + self.settings.boolean( + ['sudo'], + 'install sudo, and if user is created, add them to sudo group') + self.settings.string( + ['owner'], + 'the user who will own the image when the build is complete.') + self.settings.boolean( + ['squash'], + 'use squashfs on the final image.') + self.settings.boolean( + ['configure-apt'], + 'Create an apt source based on the distribution and mirror selected.') + self.settings.boolean( + ['mbr'], + 'Run install-mbr (no longer done by default)') + self.settings.boolean( + ['grub'], + 'Install and configure grub2 - disables extlinux.') + self.settings.boolean( + ['sparse'], + 'Dont fill the image with zeros to keep a sparse disk image', + default=False) + self.settings.boolean( + ['pkglist'], + 'Create a list of package names included in the image.') + + def process_args(self, args): # pylint: disable=too-many-branches,too-many-statements if not self.settings['image'] and not self.settings['tarball']: - raise cliapp.AppException('You must give disk image filename, ' - 'or tarball filename') + raise cliapp.AppException( + 'You must give disk image filename, or tarball filename') if self.settings['image'] and not self.settings['size']: - raise cliapp.AppException('If disk image is specified, ' - 'You must give image size.') - - self.remove_dirs = [] - self.mount_points = [] + raise cliapp.AppException( + 'If disk image is specified, you must give image size.') + rootdir = None try: rootdev = None roottype = 'ext4' @@ -126,19 +187,20 @@ class VmDebootstrap(cliapp.Application): if self.settings['image']: self.create_empty_image() self.partition_image() - self.install_mbr() + if self.settings['mbr']: + self.install_mbr() (rootdev, bootdev) = self.setup_kpartx() - self.mkfs(rootdev, type=roottype) + self.mkfs(rootdev, fstype=roottype) rootdir = self.mount(rootdev) if bootdev: if self.settings['boottype']: boottype = self.settings['boottype'] else: boottype = 'ext2' - self.mkfs(bootdev, type=boottype) + self.mkfs(bootdev, fstype=boottype) bootdir = '%s/%s' % (rootdir, 'boot/') os.mkdir(bootdir) - bootdir = self.mount(bootdev, bootdir) + self.mount(bootdev, bootdir) else: rootdir = self.mkdtemp() self.debootstrap(rootdir) @@ -150,14 +212,22 @@ class VmDebootstrap(cliapp.Application): self.create_users(rootdir) self.remove_udev_persistent_rules(rootdir) self.setup_networking(rootdir) + if self.settings['configure-apt'] or self.settings['apt-mirror']: + self.configure_apt(rootdir) self.customize(rootdir) + self.update_initramfs(rootdir) + if self.settings['image']: - if self.settings['extlinux']: + if self.settings['grub']: + self.install_grub2(rootdev, rootdir) + elif self.settings['extlinux']: self.install_extlinux(rootdev, rootdir) self.append_serial_console(rootdir) self.optimize_image(rootdir) if self.settings['squash']: self.squash() + if self.settings['pkglist']: + self.list_installed_pkgs(rootdir) if self.settings['foreign']: os.unlink('%s/usr/bin/%s' % @@ -167,9 +237,13 @@ class VmDebootstrap(cliapp.Application): self.create_tarball(rootdir) if self.settings['owner']: - self.chown(rootdir) + self.chown() except BaseException, e: self.message('EEEK! Something bad happened...') + if rootdir: + db_log = os.path.join(rootdir, 'debootstrap', 'debootstrap.log') + if os.path.exists(db_log): + shutil.copy(db_log, os.getcwd()) self.message(e) self.cleanup_system() raise @@ -181,11 +255,11 @@ class VmDebootstrap(cliapp.Application): if self.settings['verbose']: print msg - def runcmd(self, argv, stdin='', ignore_fail=False, **kwargs): - logging.debug('runcmd: %s %s' % (argv, kwargs)) + def runcmd(self, argv, stdin='', ignore_fail=False, env=None, **kwargs): + logging.debug('runcmd: %s %s %s', argv, env, kwargs) p = subprocess.Popen(argv, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - **kwargs) + env=env, **kwargs) out, err = p.communicate(stdin) if p.returncode != 0: msg = 'command failed: %s\n%s\n%s' % (argv, out, err) @@ -197,7 +271,7 @@ class VmDebootstrap(cliapp.Application): def mkdtemp(self): dirname = tempfile.mkdtemp() self.remove_dirs.append(dirname) - logging.debug('mkdir %s' % dirname) + logging.debug('mkdir %s', dirname) return dirname def mount(self, device, path=None): @@ -208,7 +282,7 @@ class VmDebootstrap(cliapp.Application): self.message('Mounting %s on %s' % (device, mount_point)) self.runcmd(['mount', device, mount_point]) self.mount_points.append(mount_point) - logging.debug('mounted %s on %s' % (device, mount_point)) + logging.debug('mounted %s on %s', device, mount_point) return mount_point def create_empty_image(self): @@ -218,25 +292,57 @@ class VmDebootstrap(cliapp.Application): str(self.settings['size'])]) def partition_image(self): + """ + Uses fat16 (msdos) partitioning by default, use part-type to change. + If bootoffset is specified, the first actual partition + starts at that offset to allow customisation scripts to + put bootloader images into the space, e.g. u-boot. + """ self.message('Creating partitions') self.runcmd(['parted', '-s', self.settings['image'], - 'mklabel', 'msdos']) + 'mklabel', self.settings['part-type']]) + partoffset = 0 + bootsize = 0 + 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: + partoffset = 1 + logging.info( + "Setting bootoffset %smib to allow for %s bytes", + partoffset, self.settings['bootoffset']) + else: + 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%': - bootsize = str(self.settings['bootsize'] / (1024 * 1024)) + bootsize = self.settings['bootsize'] / (1024 * 1024) + bootsize += partoffset + self.message("Using bootsize %smib: %s bytes" % (bootsize, self.settings['bootsize'])) + logging.debug("Starting boot partition at %sMb", bootsize) + self.runcmd(['parted', '-s', self.settings['image'], + 'mkpart', 'primary', 'fat16', str(partoffset), str(bootsize)]) + if partoffset == 0: self.runcmd(['parted', '-s', self.settings['image'], - 'mkpart', 'primary', 'fat16', '0', bootsize]) + 'mkpart', 'primary', '0%', '100%']) else: - bootsize = '0%' - self.runcmd(['parted', '-s', self.settings['image'], - 'mkpart', 'primary', bootsize, '100%']) + logging.debug("Starting root partition at %sMb", partoffset) + self.runcmd(['parted', '-s', self.settings['image'], + 'mkpart', 'primary', str(bootsize), '100%']) self.runcmd(['parted', '-s', self.settings['image'], 'set', '1', 'boot', 'on']) + def update_initramfs(self, rootdir): + cmd = os.path.join('usr', 'sbin', 'update-initramfs') + if os.path.exists(os.path.join(rootdir, cmd)): + self.message("Updating the initramfs") + self.runcmd(['chroot', rootdir, cmd, '-u']) + def install_mbr(self): - self.message('Installing MBR') - self.runcmd(['install-mbr', self.settings['image']]) + if os.path.exists("/sbin/install-mbr"): + self.message('Installing MBR') + self.runcmd(['install-mbr', self.settings['image']]) def setup_kpartx(self): + bootindex = None out = self.runcmd(['kpartx', '-avs', self.settings['image']]) if self.settings['bootsize']: bootindex = 0 @@ -245,7 +351,7 @@ class VmDebootstrap(cliapp.Application): else: rootindex = 0 parts = 1 - boot = None + boot = None devices = [line.split()[2] for line in out.splitlines() if line.startswith('add map ')] @@ -254,20 +360,24 @@ class VmDebootstrap(cliapp.Application): root = '/dev/mapper/%s' % devices[rootindex] if self.settings['bootsize']: boot = '/dev/mapper/%s' % devices[bootindex] - return (root, boot) + return root, boot - def mkfs(self, device, type): - self.message('Creating filesystem %s' % type) - self.runcmd(['mkfs', '-t', type, device]) + def mkfs(self, device, fstype): + self.message('Creating filesystem %s' % fstype) + self.runcmd(['mkfs', '-t', fstype, device]) def debootstrap(self, rootdir): - self.message('Debootstrapping') + msg = "(%s)" % self.settings['variant'] if self.settings['variant'] else '' + self.message('Debootstrapping %s %s' % (self.settings['distribution'], msg)) if self.settings['foreign']: necessary_packages = [] else: necessary_packages = ['acpid'] + if self.settings['grub']: + necessary_packages.append('grub2') + include = self.settings['package'] if not self.settings['no-kernel']: @@ -282,8 +392,12 @@ class VmDebootstrap(cliapp.Application): include.append('sudo') args = ['debootstrap', '--arch=%s' % self.settings['arch']] - args.append( - '--include=%s' % ','.join(necessary_packages + include)) + if self.settings['package']: + args.append( + '--include=%s' % ','.join(include)) + if len(necessary_packages) > 0: + args.append( + '--include=%s' % ','.join(necessary_packages)) if self.settings['foreign']: args.append('--foreign') if self.settings['variant']: @@ -291,13 +405,25 @@ class VmDebootstrap(cliapp.Application): args.append(self.settings['variant']) args += [self.settings['distribution'], rootdir, self.settings['mirror']] + logging.debug(" ".join(args)) self.runcmd(args) if self.settings['foreign']: + # set a noninteractive debconf environment for secondstage + env = { + "DEBIAN_FRONTEND": "noninteractive", + "DEBCONF_NONINTERACTIVE_SEEN": "true", + "LC_ALL": "C" + } + # add the mapping to the complete environment. + env.update(os.environ) # First copy the binfmt handler over + self.message('Setting up binfmt handler') shutil.copy(self.settings['foreign'], '%s/usr/bin/' % rootdir) # Next, run the package install scripts etc. + self.message('Running debootstrap second stage') self.runcmd(['chroot', rootdir, - '/debootstrap/debootstrap', '--second-stage']) + '/debootstrap/debootstrap', '--second-stage'], + env=env) def set_hostname(self, rootdir): hostname = self.settings['hostname'] @@ -313,10 +439,10 @@ class VmDebootstrap(cliapp.Application): if line.startswith('127.0.0.1'): line += ' %s' % hostname f.write('%s\n' % line) - except IOError, e: + except IOError: pass - def create_fstab(self, rootdir, rootdev, roottype, bootdev, boottype): + 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]) @@ -349,18 +475,18 @@ class VmDebootstrap(cliapp.Application): shutil.copy(deb, tmp) filenames = [os.path.join('/tmp/install_debs', os.path.basename(deb)) for deb in self.settings['custom-package']] - out, err, exit = \ + out, err, _ = \ self.runcmd_unchecked(['chroot', rootdir, 'dpkg', '-i'] + filenames) - logging.debug('stdout:\n%s' % out) - logging.debug('stderr:\n%s' % err) + logging.debug('stdout:\n%s', out) + logging.debug('stderr:\n%s', err) out = self.runcmd(['chroot', rootdir, - 'apt-get', '-f', '--no-remove', 'install']) - logging.debug('stdout:\n%s' % out) + 'apt-get', '-f', '--no-remove', 'install']) + logging.debug('stdout:\n%s', out) shutil.rmtree(tmp) def cleanup_apt_cache(self, rootdir): out = self.runcmd(['chroot', rootdir, 'apt-get', 'clean']) - logging.debug('stdout:\n%s' % out) + logging.debug('stdout:\n%s', out) def set_root_password(self, rootdir): if self.settings['root-password']: @@ -401,10 +527,10 @@ class VmDebootstrap(cliapp.Application): for x in ['70-persistent-cd.rules', '70-persistent-net.rules']: pathname = os.path.join(rootdir, 'etc', 'udev', 'rules.d', x) if os.path.exists(pathname): - logging.debug('rm %s' % pathname) + logging.debug('rm %s', pathname) os.remove(pathname) else: - logging.debug('not removing non-existent %s' % pathname) + logging.debug('not removing non-existent %s', pathname) def setup_networking(self, rootdir): self.message('Setting up networking') @@ -425,30 +551,62 @@ class VmDebootstrap(cliapp.Application): serial_command = self.settings['serial-console-command'] logging.debug('adding getty to serial console') inittab = os.path.join(rootdir, 'etc/inittab') + # to autologin, serial_command can contain '-a root' with open(inittab, 'a') as f: f.write('\nS0:23:respawn:%s\n' % serial_command) + 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')]) + try: + self.runcmd(['chroot', rootdir, 'update-grub']) + self.runcmd(['chroot', rootdir, 'grub-install', install_dev]) + except cliapp.AppException: + 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) + def install_extlinux(self, rootdev, rootdir): + if not os.path.exists("/usr/bin/extlinux"): + self.message("extlinux not installed, skipping.") + return self.message('Installing extlinux') def find(pattern): dirname = os.path.join(rootdir, 'boot') basenames = os.listdir(dirname) - logging.debug('find: %s' % basenames) + logging.debug('find: %s', basenames) for basename in basenames: if re.search(pattern, basename): return os.path.join('boot', basename) raise cliapp.AppException('Cannot find match: %s' % pattern) - kernel_image = find('vmlinuz-.*') - initrd_image = find('initrd.img-.*') + try: + kernel_image = find('vmlinuz-.*') + initrd_image = find('initrd.img-.*') + except cliapp.AppException as e: + self.message("Unable to find kernel. Not installing extlinux.") + logging.debug("No kernel found. %s. Skipping install of extlinux.", e) + return out = self.runcmd(['blkid', '-c', '/dev/null', '-o', 'value', '-s', 'UUID', rootdev]) uuid = out.splitlines()[0].strip() conf = os.path.join(rootdir, 'extlinux.conf') - logging.debug('configure extlinux %s' % conf) + logging.debug('configure extlinux %s', conf) + # python multiline string substitution is just ugly. + # use an external file or live with the mangling, no point in + # mangling the string to remove spaces just to keep it pretty in source. f = open(conf, 'w') f.write(''' default linux @@ -459,14 +617,14 @@ kernel %(kernel)s append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s %(extserial)s ''' % { - 'kernel': kernel_image, - 'initrd': initrd_image, - 'uuid': uuid, - 'kserial': - 'console=ttyS0,115200' if self.settings['serial-console'] else '', - 'extserial': 'serial 0 115200' if self.settings['serial-console'] else '', - }) - f.close() + 'kernel': kernel_image, # pylint: disable=bad-continuation + 'initrd': initrd_image, # pylint: disable=bad-continuation + 'uuid': uuid, # pylint: disable=bad-continuation + 'kserial': # pylint: disable=bad-continuation + 'console=ttyS0,115200' if self.settings['serial-console'] else '', # pylint: disable=bad-continuation + 'extserial': 'serial 0 115200' if self.settings['serial-console'] else '', # pylint: disable=bad-continuation + }) # pylint: disable=bad-continuation + f.close() # pylint: disable=bad-continuation self.runcmd(['extlinux', '--install', rootdir]) self.runcmd(['sync']) @@ -476,9 +634,10 @@ append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s """ Filing up the image with zeros will increase its compression rate """ - zeros = os.path.join(rootdir, 'ZEROS') - self.runcmd_unchecked(['dd', 'if=/dev/zero', 'of=' + zeros, 'bs=1M']) - self.runcmd(['rm', '-f', zeros]) + if not self.settings['sparse']: + zeros = os.path.join(rootdir, 'ZEROS') + self.runcmd_unchecked(['dd', 'if=/dev/zero', 'of=' + zeros, 'bs=1M']) + self.runcmd(['rm', '-f', zeros]) def squash(self): """ @@ -502,7 +661,7 @@ append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s # Umount in the reverse mount order if self.settings['image']: - for i in xrange(len(self.mount_points) - 1, -1, -1): + for i in range(len(self.mount_points) - 1, -1, -1): mount_point = self.mount_points[i] try: self.runcmd(['umount', mount_point], ignore_fail=False) @@ -518,10 +677,18 @@ append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s def customize(self, rootdir): script = self.settings['customize'] - if script: - self.message('Running customize script %s' % script) - with open('/dev/tty', 'w') as tty: - cliapp.runcmd([script, rootdir], stdout=tty, stderr=tty) + if not script: + return + if not os.path.exists(script): + example = os.path.join("/usr/share/vmdebootstrap/examples/", script) + if not os.path.exists(example): + self.message("Unable to find %s" % script) + return + 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: + cliapp.runcmd([script, rootdir, self.settings['image']], stdout=tty, stderr=tty) def create_tarball(self, rootdir): # Create a tarball of the disk's contents @@ -529,12 +696,37 @@ append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s self.message('Creating tarball of disk contents') self.runcmd(['tar', '-cf', self.settings['tarball'], '-C', rootdir, '.']) - def chown(self, rootdir): + def chown(self): # Change image owner after completed build self.message("Changing owner to %s" % self.settings["owner"]) subprocess.call(["chown", self.settings["owner"], self.settings["image"]]) + def list_installed_pkgs(self, rootdir): + # output the list of installed packages for sources identification + self.message("Creating a list of installed binary package names") + out = self.runcmd(['chroot', rootdir, + 'dpkg-query', '-W' "-f='${Package}.deb\n'"]) + with open('dpkg.list', 'w') as dpkg: + dpkg.write(out) + + def configure_apt(self, rootdir): + # use the distribution and mirror to create an apt source + self.message("Configuring apt to use distribution and mirror") + conf = os.path.join(rootdir, 'etc', 'apt', 'sources.list.d', 'base.list') + logging.debug('configure apt %s', conf) + mirror = self.settings['mirror'] + if self.settings['apt-mirror']: + mirror = self.settings['apt-mirror'] + self.message("Setting apt mirror to %s" % mirror) + os.unlink(os.path.join(rootdir, 'etc', 'apt', 'sources.list')) + f = open(conf, 'w') + line = 'deb %s %s main\n' % (mirror, self.settings['distribution']) + f.write(line) + line = '#deb-src %s %s main\n' % (mirror, self.settings['distribution']) + f.write(line) + f.close() + if __name__ == '__main__': VmDebootstrap(version=__version__).run()