]>
git.siccegge.de Git - forks/vmdebootstrap.git/blob - vmdebootstrap
2 # Copyright 2011-2013 Lars Wirzenius
3 # Copyright 2012 Codethink Limited
4 # Copyright 2014 Neil Williams <codehelp@debian.org>
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
32 # pylint: disable=invalid-name
35 class VmDebootstrap(cliapp
.Application
): # pylint: disable=too-many-public-methods
37 def __init__(self
, progname
=None, version
=__version__
, description
=None, epilog
=None):
38 super(VmDebootstrap
, self
).__init
__(progname
, version
, description
, epilog
)
40 self
.mount_points
= []
42 def add_settings(self
):
43 default_arch
= subprocess
.check_output(
44 ["dpkg", "--print-architecture"]).strip()
46 self
.settings
.boolean(
47 ['verbose'], 'report what is going on')
49 ['image'], 'put created disk image in FILE',
51 self
.settings
.bytesize(
53 'create a disk image of size SIZE (%default)',
56 self
.settings
.bytesize(
58 'create boot partition of size SIZE (%default)',
63 'specify file system type for /boot/',
65 self
.settings
.bytesize(
67 'Space to leave at start of the image for bootloader',
71 'Partition type to use for this image',
75 'specify file system type for /',
77 self
.settings
.bytesize(
79 'create swap space of size SIZE (min 256Mb)')
82 'set up foreign debootstrap environment using provided program (ie binfmt handler)')
85 'select debootstrap variant it not using the default')
86 self
.settings
.boolean(
92 "tar up the disk's contents in FILE",
96 'configure apt to use MIRROR',
100 'use MIRROR as package source (%default)',
102 default
='http://http.debian.net/debian/')
103 self
.settings
.string(
105 'architecture to use (%default)',
107 default
=default_arch
)
108 self
.settings
.string(
110 'release to use (%default)',
113 self
.settings
.string_list(
115 'install PACKAGE onto system')
116 self
.settings
.string_list(
118 'install package in DEB file onto system (not from mirror)',
120 self
.settings
.boolean(
122 'do not install a linux package')
123 self
.settings
.boolean(
125 'enable DHCP on eth0')
126 self
.settings
.string(
130 self
.settings
.boolean(
131 ['lock-root-password'],
132 'lock root account so they cannot login?')
133 self
.settings
.string(
135 'run SCRIPT after setting up system',
137 self
.settings
.string(
139 'set name to HOSTNAME (%default)',
142 self
.settings
.string_list(
144 'create USER with PASSWORD',
145 metavar
='USER/PASSWORD')
146 self
.settings
.boolean(
148 'configure image to use a serial console')
149 self
.settings
.string(
150 ['serial-console-command'],
151 'command to manage the serial console, appended to /etc/inittab (%default)',
153 default
='/sbin/getty -L ttyS0 115200 vt100')
154 self
.settings
.boolean(
156 'install sudo, and if user is created, add them to sudo group')
157 self
.settings
.string(
159 'the user who will own the image when the build is complete.')
160 self
.settings
.boolean(
162 'use squashfs on the final image.')
163 self
.settings
.boolean(
165 'Create an apt source based on the distribution and mirror selected.')
166 self
.settings
.boolean(
168 'Run install-mbr (default if extlinux used)')
169 self
.settings
.boolean(
171 'Install and configure grub2 - disables extlinux.')
172 self
.settings
.boolean(
174 'Do not fill the image with zeros to keep a sparse disk image',
176 self
.settings
.boolean(
178 'Create a list of package names included in the image.')
180 def process_args(self
, args
): # pylint: disable=too-many-branches,too-many-statements
181 if not self
.settings
['image'] and not self
.settings
['tarball']:
182 raise cliapp
.AppException(
183 'You must give disk image filename, or tarball filename')
184 if self
.settings
['image'] and not self
.settings
['size']:
185 raise cliapp
.AppException(
186 'If disk image is specified, you must give image size.')
191 roottype
= self
.settings
['roottype']
194 if self
.settings
['image']:
195 self
.create_empty_image()
196 self
.partition_image()
197 if self
.settings
['mbr'] or self
.settings
['extlinux']:
199 (rootdev
, bootdev
, swapdev
) = self
.setup_kpartx()
200 if self
.settings
['swap']:
201 self
.message("Creating swap space")
202 self
.runcmd(['mkswap', swapdev
])
203 self
.mkfs(rootdev
, fstype
=roottype
)
204 rootdir
= self
.mount(rootdev
)
206 if self
.settings
['boottype']:
207 boottype
= self
.settings
['boottype']
210 self
.mkfs(bootdev
, fstype
=boottype
)
211 bootdir
= '%s/%s' % (rootdir
, 'boot/')
213 self
.mount(bootdev
, bootdir
)
215 rootdir
= self
.mkdtemp()
216 self
.debootstrap(rootdir
)
217 self
.set_hostname(rootdir
)
218 self
.create_fstab(rootdir
, rootdev
, roottype
, bootdev
, boottype
)
219 self
.install_debs(rootdir
)
220 self
.cleanup_apt_cache(rootdir
)
221 self
.set_root_password(rootdir
)
222 self
.create_users(rootdir
)
223 self
.remove_udev_persistent_rules(rootdir
)
224 self
.setup_networking(rootdir
)
225 if self
.settings
['configure-apt'] or self
.settings
['apt-mirror']:
226 self
.configure_apt(rootdir
)
227 self
.customize(rootdir
)
228 self
.update_initramfs(rootdir
)
230 if self
.settings
['image']:
231 if self
.settings
['grub']:
232 self
.install_grub2(rootdev
, rootdir
)
233 elif self
.settings
['extlinux']:
234 self
.install_extlinux(rootdev
, rootdir
)
235 self
.append_serial_console(rootdir
)
236 self
.optimize_image(rootdir
)
237 if self
.settings
['squash']:
239 if self
.settings
['pkglist']:
240 self
.list_installed_pkgs(rootdir
)
242 if self
.settings
['foreign']:
243 os
.unlink('%s/usr/bin/%s' %
244 (rootdir
, os
.path
.basename(self
.settings
['foreign'])))
246 if self
.settings
['tarball']:
247 self
.create_tarball(rootdir
)
249 if self
.settings
['owner']:
251 except BaseException
, e
:
252 self
.message('EEEK! Something bad happened...')
254 db_log
= os
.path
.join(rootdir
, 'debootstrap', 'debootstrap.log')
255 if os
.path
.exists(db_log
):
256 shutil
.copy(db_log
, os
.getcwd())
258 self
.cleanup_system()
261 self
.cleanup_system()
263 def message(self
, msg
):
265 if self
.settings
['verbose']:
268 def runcmd(self
, argv
, stdin
='', ignore_fail
=False, env
=None, **kwargs
):
269 logging
.debug('runcmd: %s %s %s', argv
, env
, kwargs
)
270 p
= subprocess
.Popen(argv
, stdin
=subprocess
.PIPE
,
271 stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
273 out
, err
= p
.communicate(stdin
)
274 if p
.returncode
!= 0:
275 msg
= 'command failed: %s\n%s\n%s' % (argv
, out
, err
)
278 raise cliapp
.AppException(msg
)
282 dirname
= tempfile
.mkdtemp()
283 self
.remove_dirs
.append(dirname
)
284 logging
.debug('mkdir %s', dirname
)
287 def mount(self
, device
, path
=None):
289 mount_point
= self
.mkdtemp()
292 self
.message('Mounting %s on %s' % (device
, mount_point
))
293 self
.runcmd(['mount', device
, mount_point
])
294 self
.mount_points
.append(mount_point
)
295 logging
.debug('mounted %s on %s', device
, mount_point
)
298 def create_empty_image(self
):
299 self
.message('Creating disk image')
300 self
.runcmd(['qemu-img', 'create', '-f', 'raw',
301 self
.settings
['image'],
302 str(self
.settings
['size'])])
304 def partition_image(self
):
306 Uses fat16 (msdos) partitioning by default, use part-type to change.
307 If bootoffset is specified, the first actual partition
308 starts at that offset to allow customisation scripts to
309 put bootloader images into the space, e.g. u-boot.
311 self
.message('Creating partitions')
312 self
.runcmd(['parted', '-s', self
.settings
['image'],
313 'mklabel', self
.settings
['part-type']])
317 swap
= 256 * 1024 * 1024
318 if self
.settings
['swap']:
319 if self
.settings
['swap'] > swap
:
320 swap
= self
.settings
['swap']
322 # minimum 256Mb as default qemu ram is 128Mb
323 logging
.debug("Setting minimum 256Mb swap space")
324 extent
= "%s%%" % int(100 * (self
.settings
['size'] - swap
) / self
.settings
['size'])
325 if self
.settings
['bootoffset'] and self
.settings
['bootoffset'] is not '0':
326 # turn v.small offsets into something at least possible to create.
327 if self
.settings
['bootoffset'] < 1048576:
330 "Setting bootoffset %smib to allow for %s bytes",
331 partoffset
, self
.settings
['bootoffset'])
333 partoffset
= self
.settings
['bootoffset'] / (1024 * 1024)
334 self
.message("Using bootoffset: %smib %s bytes" % (partoffset
, self
.settings
['bootoffset']))
335 if self
.settings
['bootsize'] and self
.settings
['bootsize'] is not '0%':
336 bootsize
= self
.settings
['bootsize'] / (1024 * 1024)
337 bootsize
+= partoffset
338 self
.message("Using bootsize %smib: %s bytes" % (bootsize
, self
.settings
['bootsize']))
339 logging
.debug("Starting boot partition at %sMb", bootsize
)
340 self
.runcmd(['parted', '-s', self
.settings
['image'],
341 'mkpart', 'primary', 'fat16', str(partoffset
), str(bootsize
)])
342 logging
.debug("Starting root partition at %sMb", partoffset
)
343 self
.runcmd(['parted', '-s', self
.settings
['image'],
344 'mkpart', 'primary', str(bootsize
), extent
])
346 self
.runcmd(['parted', '-s', self
.settings
['image'],
347 'mkpart', 'primary', '0%', extent
])
348 self
.runcmd(['parted', '-s', self
.settings
['image'],
349 'set', '1', 'boot', 'on'])
350 if self
.settings
['swap']:
351 logging
.debug("Creating swap partition")
352 self
.runcmd(['parted', '-s', self
.settings
['image'],
353 'mkpart', 'primary', 'linux-swap', extent
, '100%'])
355 def update_initramfs(self
, rootdir
):
356 cmd
= os
.path
.join('usr', 'sbin', 'update-initramfs')
357 if os
.path
.exists(os
.path
.join(rootdir
, cmd
)):
358 self
.message("Updating the initramfs")
359 self
.runcmd(['chroot', rootdir
, cmd
, '-u'])
361 def install_mbr(self
):
362 if os
.path
.exists("/sbin/install-mbr"):
363 self
.message('Installing MBR')
364 self
.runcmd(['install-mbr', self
.settings
['image']])
366 msg
= "mbr enabled but /sbin/install-mbr not found" \
367 " - please install the mbr package."
368 raise cliapp
.AppException(msg
)
370 def setup_kpartx(self
):
373 out
= self
.runcmd(['kpartx', '-avs', self
.settings
['image']])
374 if self
.settings
['bootsize'] and self
.settings
['swap']:
379 elif self
.settings
['bootsize']:
383 elif self
.settings
['swap']:
392 devices
= [line
.split()[2]
393 for line
in out
.splitlines()
394 if line
.startswith('add map ')]
395 if len(devices
) != parts
:
396 msg
= 'Surprising number of partitions'
397 logging
.debug("%s: devices=%s parts=%s", msg
, devices
, parts
)
398 raise cliapp
.AppException(msg
)
399 root
= '/dev/mapper/%s' % devices
[rootindex
]
400 if self
.settings
['bootsize']:
401 boot
= '/dev/mapper/%s' % devices
[bootindex
]
402 if self
.settings
['swap']:
403 swap
= '/dev/mapper/%s' % devices
[swapindex
]
404 return root
, boot
, swap
406 def mkfs(self
, device
, fstype
):
407 self
.message('Creating filesystem %s' % fstype
)
408 self
.runcmd(['mkfs', '-t', fstype
, device
])
410 def debootstrap(self
, rootdir
):
411 msg
= "(%s)" % self
.settings
['variant'] if self
.settings
['variant'] else ''
412 self
.message('Debootstrapping %s %s' % (self
.settings
['distribution'], msg
))
414 include
= self
.settings
['package']
416 if not self
.settings
['foreign']:
417 include
.append('acpid')
419 if self
.settings
['grub']:
420 include
.append('grub2')
422 if not self
.settings
['no-kernel']:
423 if self
.settings
['arch'] == 'i386':
426 kernel_arch
= self
.settings
['arch']
427 kernel_image
= 'linux-image-%s' % kernel_arch
428 include
.append(kernel_image
)
430 if self
.settings
['sudo'] and 'sudo' not in include
:
431 include
.append('sudo')
433 args
= ['debootstrap', '--arch=%s' % self
.settings
['arch']]
435 if self
.settings
['package']:
437 '--include=%s' % ','.join(include
))
438 if self
.settings
['foreign']:
439 args
.append('--foreign')
440 if self
.settings
['variant']:
441 args
.append('--variant')
442 args
.append(self
.settings
['variant'])
443 args
+= [self
.settings
['distribution'],
444 rootdir
, self
.settings
['mirror']]
445 logging
.debug(" ".join(args
))
447 if self
.settings
['foreign']:
448 # set a noninteractive debconf environment for secondstage
450 "DEBIAN_FRONTEND": "noninteractive",
451 "DEBCONF_NONINTERACTIVE_SEEN": "true",
454 # add the mapping to the complete environment.
455 env
.update(os
.environ
)
456 # First copy the binfmt handler over
457 self
.message('Setting up binfmt handler')
458 shutil
.copy(self
.settings
['foreign'], '%s/usr/bin/' % rootdir
)
459 # Next, run the package install scripts etc.
460 self
.message('Running debootstrap second stage')
461 self
.runcmd(['chroot', rootdir
,
462 '/debootstrap/debootstrap', '--second-stage'],
465 def set_hostname(self
, rootdir
):
466 hostname
= self
.settings
['hostname']
467 with
open(os
.path
.join(rootdir
, 'etc', 'hostname'), 'w') as f
:
468 f
.write('%s\n' % hostname
)
470 etc_hosts
= os
.path
.join(rootdir
, 'etc', 'hosts')
472 with
open(etc_hosts
, 'r') as f
:
474 with
open(etc_hosts
, 'w') as f
:
475 for line
in data
.splitlines():
476 if line
.startswith('127.0.0.1'):
477 line
+= ' %s' % hostname
478 f
.write('%s\n' % line
)
482 def create_fstab(self
, rootdir
, rootdev
, roottype
, bootdev
, boottype
): # pylint: disable=too-many-arguments
484 out
= self
.runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
485 '-s', 'UUID', device
])
486 return out
.splitlines()[0].strip()
489 rootdevstr
= 'UUID=%s' % fsuuid(rootdev
)
491 rootdevstr
= '/dev/sda1'
494 bootdevstr
= 'UUID=%s' % fsuuid(bootdev
)
498 fstab
= os
.path
.join(rootdir
, 'etc', 'fstab')
499 with
open(fstab
, 'w') as f
:
500 f
.write('proc /proc proc defaults 0 0\n')
501 f
.write('%s / %s errors=remount-ro 0 1\n' % (rootdevstr
, roottype
))
503 f
.write('%s /boot %s errors=remount-ro 0 2\n' % (bootdevstr
, boottype
))
504 if self
.settings
['swap']:
505 f
.write("/dev/sda3 swap swap defaults 0 0\n")
506 elif self
.settings
['swap']:
507 f
.write("/dev/sda2 swap swap defaults 0 0\n")
509 def install_debs(self
, rootdir
):
510 if not self
.settings
['custom-package']:
512 self
.message('Installing custom packages')
513 tmp
= os
.path
.join(rootdir
, 'tmp', 'install_debs')
515 for deb
in self
.settings
['custom-package']:
516 shutil
.copy(deb
, tmp
)
517 filenames
= [os
.path
.join('/tmp/install_debs', os
.path
.basename(deb
))
518 for deb
in self
.settings
['custom-package']]
520 self
.runcmd_unchecked(['chroot', rootdir
, 'dpkg', '-i'] + filenames
)
521 logging
.debug('stdout:\n%s', out
)
522 logging
.debug('stderr:\n%s', err
)
523 out
= self
.runcmd(['chroot', rootdir
,
524 'apt-get', '-f', '--no-remove', 'install'])
525 logging
.debug('stdout:\n%s', out
)
528 def cleanup_apt_cache(self
, rootdir
):
529 out
= self
.runcmd(['chroot', rootdir
, 'apt-get', 'clean'])
530 logging
.debug('stdout:\n%s', out
)
532 def set_root_password(self
, rootdir
):
533 if self
.settings
['root-password']:
534 self
.message('Setting root password')
535 self
.set_password(rootdir
, 'root', self
.settings
['root-password'])
536 elif self
.settings
['lock-root-password']:
537 self
.message('Locking root password')
538 self
.runcmd(['chroot', rootdir
, 'passwd', '-l', 'root'])
540 self
.message('Give root an empty password')
541 self
.delete_password(rootdir
, 'root')
543 def create_users(self
, rootdir
):
544 def create_user(user
):
545 self
.runcmd(['chroot', rootdir
, 'adduser', '--gecos', user
,
546 '--disabled-password', user
])
547 if self
.settings
['sudo']:
548 self
.runcmd(['chroot', rootdir
, 'adduser', user
, 'sudo'])
550 for userpass
in self
.settings
['user']:
552 user
, password
= userpass
.split('/', 1)
554 self
.set_password(rootdir
, user
, password
)
556 create_user(userpass
)
557 self
.delete_password(rootdir
, userpass
)
559 def set_password(self
, rootdir
, user
, password
):
560 encrypted
= crypt
.crypt(password
, '..')
561 self
.runcmd(['chroot', rootdir
, 'usermod', '-p', encrypted
, user
])
563 def delete_password(self
, rootdir
, user
):
564 self
.runcmd(['chroot', rootdir
, 'passwd', '-d', user
])
566 def remove_udev_persistent_rules(self
, rootdir
):
567 self
.message('Removing udev persistent cd and net rules')
568 for x
in ['70-persistent-cd.rules', '70-persistent-net.rules']:
569 pathname
= os
.path
.join(rootdir
, 'etc', 'udev', 'rules.d', x
)
570 if os
.path
.exists(pathname
):
571 logging
.debug('rm %s', pathname
)
574 logging
.debug('not removing non-existent %s', pathname
)
576 def setup_networking(self
, rootdir
):
577 self
.message('Setting up networking')
579 f
= open(os
.path
.join(rootdir
, 'etc', 'network', 'interfaces'), 'w')
581 f
.write('iface lo inet loopback\n')
583 if self
.settings
['enable-dhcp']:
585 f
.write('auto eth0\n')
586 f
.write('iface eth0 inet dhcp\n')
590 def append_serial_console(self
, rootdir
):
591 if self
.settings
['serial-console']:
592 serial_command
= self
.settings
['serial-console-command']
593 logging
.debug('adding getty to serial console')
594 inittab
= os
.path
.join(rootdir
, 'etc/inittab')
595 # to autologin, serial_command can contain '-a root'
596 with
open(inittab
, 'a') as f
:
597 f
.write('\nS0:23:respawn:%s\n' % serial_command
)
599 def install_grub2(self
, rootdev
, rootdir
):
600 self
.message("Configuring grub2")
601 # rely on kpartx using consistent naming to map loop0p1 to loop0
602 install_dev
= os
.path
.join('/dev', os
.path
.basename(rootdev
)[:-2])
603 self
.runcmd(['mount', '/dev', '-t', 'devfs', '-obind',
604 '%s' % os
.path
.join(rootdir
, 'dev')])
605 self
.runcmd(['mount', '/proc', '-t', 'proc', '-obind',
606 '%s' % os
.path
.join(rootdir
, 'proc')])
607 self
.runcmd(['mount', '/sys', '-t', 'sysfs', '-obind',
608 '%s' % os
.path
.join(rootdir
, 'sys')])
610 cmdline
= 'GRUB_CMDLINE_LINUX_DEFAULT="console=tty0 console=tty1 console=ttyS0,38400n8"'
611 terminal
= 'GRUB_TERMINAL="serial gfxterm"'
612 command
= 'GRUB_SERIAL_COMMAND="serial --speed=38400 --unit=0 --parity=no --stop=1"'
613 grub_cfg
= os
.path
.join(rootdir
, 'etc', 'default', 'grub')
614 logging
.debug("Allowing serial output in grub config %s", grub_cfg
)
615 with
open(grub_cfg
, 'a+') as cfg
:
616 cfg
.write("# %s serial support\n" % os
.path
.basename(__file__
))
617 cfg
.write("%s\n" % cmdline
)
618 cfg
.write("%s\n" % terminal
)
619 cfg
.write("%s\n" % command
)
621 self
.runcmd(['chroot', rootdir
, 'update-grub'])
622 self
.runcmd(['chroot', rootdir
, 'grub-install', install_dev
])
623 except cliapp
.AppException
:
624 self
.message("Failed. Is grub2-common installed? Using extlinux.")
625 self
.runcmd(['umount', os
.path
.join(rootdir
, 'sys')])
626 self
.runcmd(['umount', os
.path
.join(rootdir
, 'proc')])
627 self
.runcmd(['umount', os
.path
.join(rootdir
, 'dev')])
628 self
.install_extlinux(rootdev
, rootdir
)
630 def install_extlinux(self
, rootdev
, rootdir
):
631 if not os
.path
.exists("/usr/bin/extlinux"):
632 self
.message("extlinux not installed, skipping.")
634 self
.message('Installing extlinux')
637 dirname
= os
.path
.join(rootdir
, 'boot')
638 basenames
= os
.listdir(dirname
)
639 logging
.debug('find: %s', basenames
)
640 for basename
in basenames
:
641 if re
.search(pattern
, basename
):
642 return os
.path
.join('boot', basename
)
643 raise cliapp
.AppException('Cannot find match: %s' % pattern
)
646 kernel_image
= find('vmlinuz-.*')
647 initrd_image
= find('initrd.img-.*')
648 except cliapp
.AppException
as e
:
649 self
.message("Unable to find kernel. Not installing extlinux.")
650 logging
.debug("No kernel found. %s. Skipping install of extlinux.", e
)
653 out
= self
.runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
654 '-s', 'UUID', rootdev
])
655 uuid
= out
.splitlines()[0].strip()
657 conf
= os
.path
.join(rootdir
, 'extlinux.conf')
658 logging
.debug('configure extlinux %s', conf
)
659 kserial
= 'console=ttyS0,115200' if self
.settings
['serial-console'] else ''
660 extserial
= 'serial 0 115200' if self
.settings
['serial-console'] else ''
667 append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s
670 'kernel': kernel_image
, # pylint: disable=bad-continuation
671 'initrd': initrd_image
, # pylint: disable=bad-continuation
672 'uuid': uuid
, # pylint: disable=bad-continuation
673 'kserial': kserial
, # pylint: disable=bad-continuation
674 'extserial': extserial
, # pylint: disable=bad-continuation
675 } # pylint: disable=bad-continuation
676 logging
.debug("extlinux config:\n%s", msg
)
678 # python multiline string substitution is just ugly.
679 # use an external file or live with the mangling, no point in
680 # mangling the string to remove spaces just to keep it pretty in source.
684 self
.runcmd(['extlinux', '--install', rootdir
])
685 self
.runcmd(['sync'])
688 def optimize_image(self
, rootdir
):
690 Filing up the image with zeros will increase its compression rate
692 if not self
.settings
['sparse']:
693 zeros
= os
.path
.join(rootdir
, 'ZEROS')
694 self
.runcmd_unchecked(['dd', 'if=/dev/zero', 'of=' + zeros
, 'bs=1M'])
695 self
.runcmd(['rm', '-f', zeros
])
699 Run squashfs on the image.
701 if not os
.path
.exists('/usr/bin/mksquashfs'):
702 logging
.warning("Squash selected but mksquashfs not found!")
704 self
.message("Running mksquashfs")
705 suffixed
= "%s.squashfs" % self
.settings
['image']
706 self
.runcmd(['mksquashfs', self
.settings
['image'],
708 '-no-progress', '-comp', 'xz'], ignore_fail
=False)
709 os
.unlink(self
.settings
['image'])
710 self
.settings
['image'] = suffixed
712 def cleanup_system(self
):
713 # Clean up after any errors.
715 self
.message('Cleaning up')
717 # Umount in the reverse mount order
718 if self
.settings
['image']:
719 for i
in range(len(self
.mount_points
) - 1, -1, -1):
720 mount_point
= self
.mount_points
[i
]
722 self
.runcmd(['umount', mount_point
], ignore_fail
=False)
723 except cliapp
.AppException
:
724 logging
.debug("umount failed, sleeping and trying again")
726 self
.runcmd(['umount', mount_point
], ignore_fail
=False)
728 self
.runcmd(['kpartx', '-d', self
.settings
['image']], ignore_fail
=True)
730 for dirname
in self
.remove_dirs
:
731 shutil
.rmtree(dirname
)
733 def customize(self
, rootdir
):
734 script
= self
.settings
['customize']
737 if not os
.path
.exists(script
):
738 example
= os
.path
.join("/usr/share/vmdebootstrap/examples/", script
)
739 if not os
.path
.exists(example
):
740 self
.message("Unable to find %s" % script
)
743 self
.message('Running customize script %s' % script
)
744 logging
.info("rootdir=%s image=%s", rootdir
, self
.settings
['image'])
745 with
open('/dev/tty', 'w') as tty
:
746 cliapp
.runcmd([script
, rootdir
, self
.settings
['image']], stdout
=tty
, stderr
=tty
)
748 def create_tarball(self
, rootdir
):
749 # Create a tarball of the disk's contents
750 # shell out to runcmd since it more easily handles rootdir
751 self
.message('Creating tarball of disk contents')
752 self
.runcmd(['tar', '-cf', self
.settings
['tarball'], '-C', rootdir
, '.'])
755 # Change image owner after completed build
756 if self
.settings
['image']:
757 filename
= self
.settings
['image']
758 elif self
.settings
['tarball']:
759 filename
= self
.settings
['tarball']
762 self
.message("Changing owner to %s" % self
.settings
["owner"])
763 subprocess
.call(["chown", self
.settings
["owner"], filename
])
765 def list_installed_pkgs(self
, rootdir
):
766 # output the list of installed packages for sources identification
767 self
.message("Creating a list of installed binary package names")
768 out
= self
.runcmd(['chroot', rootdir
,
769 'dpkg-query', '-W', "-f='${Package}.deb\n'"])
770 with
open('dpkg.list', 'w') as dpkg
:
773 def configure_apt(self
, rootdir
):
774 # use the distribution and mirror to create an apt source
775 self
.message("Configuring apt to use distribution and mirror")
776 conf
= os
.path
.join(rootdir
, 'etc', 'apt', 'sources.list.d', 'base.list')
777 logging
.debug('configure apt %s', conf
)
778 mirror
= self
.settings
['mirror']
779 if self
.settings
['apt-mirror']:
780 mirror
= self
.settings
['apt-mirror']
781 self
.message("Setting apt mirror to %s" % mirror
)
782 os
.unlink(os
.path
.join(rootdir
, 'etc', 'apt', 'sources.list'))
784 line
= 'deb %s %s main\n' % (mirror
, self
.settings
['distribution'])
786 line
= '#deb-src %s %s main\n' % (mirror
, self
.settings
['distribution'])
789 # ensure the apt sources have valid lists
790 self
.runcmd(['chroot', rootdir
, 'apt-get', '-qq', 'update'])
792 if __name__
== '__main__':
793 VmDebootstrap(version
=__version__
).run()