X-Git-Url: https://git.siccegge.de//index.cgi?a=blobdiff_plain;f=vmdebootstrap;h=5f01f5a37e1788862b36ecbf4ff66d5f9662c8b9;hb=6a5784a44511ec930ae96e1ba4dc181a77ba4e8f;hp=e129b215362b864c0a84ee0781a574f06855ad32;hpb=684c745d5e289990e6c7c23d20d217619c52d73b;p=forks%2Fvmdebootstrap.git diff --git a/vmdebootstrap b/vmdebootstrap index e129b21..5f01f5a 100755 --- a/vmdebootstrap +++ b/vmdebootstrap @@ -25,6 +25,9 @@ import subprocess import tempfile +__version__ = '0.1.0' + + class VmDebootstrap(cliapp.Application): def add_settings(self): @@ -37,6 +40,20 @@ class VmDebootstrap(cliapp.Application): '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') + self.settings.boolean(['no-extlinux'], 'do not install extlinux') + 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', @@ -53,6 +70,7 @@ class VmDebootstrap(cliapp.Application): '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') @@ -70,36 +88,66 @@ class VmDebootstrap(cliapp.Application): 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') def process_args(self, args): - if not self.settings['image']: - raise cliapp.AppException('You must give image filename.') - if not self.settings['size']: - raise cliapp.AppException('You must give image size.') + if not self.settings['image'] and not self.settings['tarball']: + 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 = [] try: - self.create_empty_image() - self.partition_image() - self.install_mbr() - rootdev = self.setup_kpartx() - self.mkfs(rootdev) - rootdir = self.mount(rootdev) + if self.settings['image']: + self.create_empty_image() + self.partition_image() + self.install_mbr() + (rootdev,bootdev) = self.setup_kpartx() + self.mkfs(rootdev) + rootdir = self.mount(rootdev) + if bootdev: + if self.settings['boottype']: + fstype = self.settings['boottype'] + else: + fstype = 'ext2' + self.mkfs(bootdev, type=fstype) + bootdir = '%s/%s' % (rootdir, 'boot/') + os.mkdir(bootdir) + bootdir = self.mount(bootdev, bootdir) + else: + rootdir = self.mkdtemp() self.debootstrap(rootdir) self.set_hostname(rootdir) self.create_fstab(rootdir) self.install_debs(rootdir) + self.cleanup_apt_cache(rootdir) self.set_root_password(rootdir) self.create_users(rootdir) self.remove_udev_persistent_rules(rootdir) self.setup_networking(rootdir) - self.install_extlinux(rootdev, rootdir) self.customize(rootdir) + if self.settings['image']: + if not self.settings['no-extlinux']: + self.install_extlinux(rootdev, rootdir) + self.optimize_image(rootdir) + + if self.settings['foreign']: + os.unlink('%s/usr/bin/%s' % + (rootdir, os.path.basename(self.settings['foreign']))) + + if self.settings['tarball']: + self.create_tarball(rootdir) except BaseException, e: self.message('EEEK! Something bad happened...') self.cleanup_system() @@ -108,6 +156,7 @@ class VmDebootstrap(cliapp.Application): self.cleanup_system() def message(self, msg): + logging.info(msg) if self.settings['verbose']: print msg @@ -130,9 +179,12 @@ class VmDebootstrap(cliapp.Application): logging.debug('mkdir %s' % dirname) return dirname - def mount(self, device): + def mount(self, device, path=None): self.message('Mounting %s' % device) - mount_point = self.mkdtemp() + if not path: + mount_point = self.mkdtemp() + else: + mount_point = path self.runcmd(['mount', device, mount_point]) self.mount_points.append(mount_point) logging.debug('mounted %s on %s' % (device, mount_point)) @@ -148,8 +200,14 @@ class VmDebootstrap(cliapp.Application): self.message('Creating partitions') 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)) + self.runcmd(['parted', '-s', self.settings['image'], + 'mkpart', 'primary', 'fat16', '0', bootsize]) + else: + bootsize='0%' self.runcmd(['parted', '-s', self.settings['image'], - 'mkpart', 'primary', '0%', '100%']) + 'mkpart', 'primary', bootsize, '100%']) self.runcmd(['parted', '-s', self.settings['image'], 'set', '1', 'boot', 'on']) @@ -159,36 +217,66 @@ class VmDebootstrap(cliapp.Application): def setup_kpartx(self): out = self.runcmd(['kpartx', '-av', self.settings['image']]) + if self.settings['bootsize']: + bootindex = 0 + rootindex = 1 + parts = 2 + else: + rootindex = 0 + parts = 1 + boot = None devices = [line.split()[2] for line in out.splitlines() if line.startswith('add map ')] - if len(devices) != 1: + if len(devices) != parts: raise cliapp.AppException('Surprising number of partitions') - return '/dev/mapper/%s' % devices[0] + root = '/dev/mapper/%s' % devices[rootindex] + if self.settings['bootsize']: + boot = '/dev/mapper/%s' % devices[bootindex] + return (root,boot) - def mkfs(self, device): + def mkfs(self, device, type='ext2'): self.message('Creating filesystem') - self.runcmd(['mkfs', '-t', 'ext2', device]) + self.runcmd(['mkfs', '-t', type, device]) def debootstrap(self, rootdir): self.message('Debootstrapping') - if self.settings['arch'] == 'i386': - kernel_arch = '686' + if self.settings['foreign']: + necessary_packages = [] else: - kernel_arch = self.settings['arch'] - kernel_image = 'linux-image-2.6-%s' % kernel_arch + necessary_packages = ['acpid'] + + include = self.settings['package'] + + if not self.settings['no-kernel']: + if self.settings['arch'] == 'i386': + kernel_arch = '486' + else: + kernel_arch = self.settings['arch'] + kernel_image = 'linux-image-%s' % kernel_arch + include.append(kernel_image) - include = [kernel_image] + self.settings['package'] if self.settings['sudo'] and 'sudo' not in include: include.append('sudo') - self.runcmd(['debootstrap', - '--arch=%s' % self.settings['arch'], - '--include=%s' % ','.join(include), - self.settings['distribution'], - rootdir, - self.settings['mirror']]) + args = ['debootstrap', '--arch=%s' % self.settings['arch']] + args.append( + '--include=%s' % ','.join(necessary_packages + include)) + if self.settings['foreign']: + args.append('--foreign') + if self.settings['variant']: + args.append('--variant') + args.append(self.settings['variant']) + args += [self.settings['distribution'], + rootdir, self.settings['mirror']] + self.runcmd(args) + if self.settings['foreign']: + # First copy the binfmt handler over + shutil.copy(self.settings['foreign'], '%s/usr/bin/' % rootdir) + # Next, run the package install scripts etc. + self.runcmd(['chroot', rootdir, + '/debootstrap/debootstrap', '--second-stage']) def set_hostname(self, rootdir): hostname = self.settings['hostname'] @@ -229,6 +317,10 @@ class VmDebootstrap(cliapp.Application): 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) + def set_root_password(self, rootdir): if self.settings['root-password']: self.message('Setting root password') @@ -282,7 +374,7 @@ class VmDebootstrap(cliapp.Application): if self.settings['enable-dhcp']: f.write('\n') - f.write('allow-hotplug eth0\n') + f.write('auto eth0\n') f.write('iface eth0 inet dhcp\n') f.close() @@ -315,7 +407,7 @@ timeout 1 label linux kernel %(kernel)s -append initrd=%(initrd)s root=UUID=%(uuid)s ro quiet %(kserial)s +append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s %(extserial)s ''' % { 'kernel': kernel_image, @@ -328,24 +420,37 @@ append initrd=%(initrd)s root=UUID=%(uuid)s ro quiet %(kserial)s f.close() if self.settings['serial-console']: + serial_command = self.settings['serial-console-command'] logging.debug('adding getty to serial console') inittab = os.path.join(rootdir, 'etc/inittab') with open(inittab, 'a') as f: - f.write('\nS0:23:respawn:/sbin/getty -L ttyS0 115200 vt100\n') + f.write('\nS0:23:respawn:%s\n' % serial_command) self.runcmd(['extlinux', '--install', rootdir]) self.runcmd(['sync']) import time; time.sleep(2) - + + def optimize_image(self, rootdir): + """ + 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]) + + def cleanup_system(self): # Clean up after any errors. self.message('Cleaning up') - for mount_point in self.mount_points: - self.runcmd(['umount', mount_point], ignore_fail=True) + # Umount in the reverse mount order + if self.settings['image']: + for i in xrange(len(self.mount_points) - 1, -1, -1): + mount_point = self.mount_points[i] + self.runcmd(['umount', mount_point], ignore_fail=True) - self.runcmd(['kpartx', '-d', self.settings['image']], ignore_fail=True) + self.runcmd(['kpartx', '-d', self.settings['image']], ignore_fail=True) for dirname in self.remove_dirs: shutil.rmtree(dirname) @@ -354,9 +459,16 @@ append initrd=%(initrd)s root=UUID=%(uuid)s ro quiet %(kserial)s script = self.settings['customize'] if script: self.message('Running customize script %s' % script) - self.runcmd([script, rootdir]) + with open('/dev/tty', 'w') as tty: + cliapp.runcmd([script, rootdir], stdout=tty, stderr=tty) + + def create_tarball(self, rootdir): + # Create a tarball of the disk's contents + # shell out to runcmd since it more easily handles rootdir + self.message('Creating tarball of disk contents') + self.runcmd(['tar', '-cf', self.settings['tarball'], '-C', rootdir, '.']) if __name__ == '__main__': - VmDebootstrap().run() + VmDebootstrap(version=__version__).run()