]>
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'] > 0:
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
as 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'] > 0:
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'] > 0:
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'] > 0:
379 elif self
.settings
['bootsize']:
383 elif self
.settings
['swap'] > 0:
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 - check output of losetup -a'
397 logging
.debug("%s" % self
.runcmd(['losetup', '-a']))
398 logging
.debug("%s: devices=%s parts=%s", msg
, devices
, parts
)
399 raise cliapp
.AppException(msg
)
400 root
= '/dev/mapper/%s' % devices
[rootindex
]
401 if self
.settings
['bootsize']:
402 boot
= '/dev/mapper/%s' % devices
[bootindex
]
403 if self
.settings
['swap'] > 0:
404 swap
= '/dev/mapper/%s' % devices
[swapindex
]
405 return root
, boot
, swap
407 def mkfs(self
, device
, fstype
):
408 self
.message('Creating filesystem %s' % fstype
)
409 self
.runcmd(['mkfs', '-t', fstype
, device
])
411 def debootstrap(self
, rootdir
):
412 msg
= "(%s)" % self
.settings
['variant'] if self
.settings
['variant'] else ''
413 self
.message('Debootstrapping %s %s' % (self
.settings
['distribution'], msg
))
415 include
= self
.settings
['package']
417 if not self
.settings
['foreign']:
418 include
.append('acpid')
420 if self
.settings
['grub']:
421 include
.append('grub-pc')
423 if not self
.settings
['no-kernel']:
424 if self
.settings
['arch'] == 'i386':
426 elif self
.settings
['arch'] == 'armhf':
427 kernel_arch
= 'armmp'
429 kernel_arch
= self
.settings
['arch']
430 kernel_image
= 'linux-image-%s' % kernel_arch
431 include
.append(kernel_image
)
433 if self
.settings
['sudo'] and 'sudo' not in include
:
434 include
.append('sudo')
436 args
= ['debootstrap', '--arch=%s' % self
.settings
['arch']]
438 if self
.settings
['package']:
440 '--include=%s' % ','.join(include
))
441 if self
.settings
['foreign']:
442 args
.append('--foreign')
443 if self
.settings
['variant']:
444 args
.append('--variant')
445 args
.append(self
.settings
['variant'])
446 args
+= [self
.settings
['distribution'],
447 rootdir
, self
.settings
['mirror']]
448 logging
.debug(" ".join(args
))
450 if self
.settings
['foreign']:
451 # set a noninteractive debconf environment for secondstage
453 "DEBIAN_FRONTEND": "noninteractive",
454 "DEBCONF_NONINTERACTIVE_SEEN": "true",
457 # add the mapping to the complete environment.
458 env
.update(os
.environ
)
459 # First copy the binfmt handler over
460 self
.message('Setting up binfmt handler')
461 shutil
.copy(self
.settings
['foreign'], '%s/usr/bin/' % rootdir
)
462 # Next, run the package install scripts etc.
463 self
.message('Running debootstrap second stage')
464 self
.runcmd(['chroot', rootdir
,
465 '/debootstrap/debootstrap', '--second-stage'],
468 def set_hostname(self
, rootdir
):
469 hostname
= self
.settings
['hostname']
470 with
open(os
.path
.join(rootdir
, 'etc', 'hostname'), 'w') as f
:
471 f
.write('%s\n' % hostname
)
473 etc_hosts
= os
.path
.join(rootdir
, 'etc', 'hosts')
475 with
open(etc_hosts
, 'r') as f
:
477 with
open(etc_hosts
, 'w') as f
:
478 for line
in data
.splitlines():
479 if line
.startswith('127.0.0.1'):
480 line
+= ' %s' % hostname
481 f
.write('%s\n' % line
)
485 def create_fstab(self
, rootdir
, rootdev
, roottype
, bootdev
, boottype
): # pylint: disable=too-many-arguments
487 out
= self
.runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
488 '-s', 'UUID', device
])
489 return out
.splitlines()[0].strip()
492 rootdevstr
= 'UUID=%s' % fsuuid(rootdev
)
494 rootdevstr
= '/dev/sda1'
497 bootdevstr
= 'UUID=%s' % fsuuid(bootdev
)
501 fstab
= os
.path
.join(rootdir
, 'etc', 'fstab')
502 with
open(fstab
, 'w') as f
:
503 f
.write('proc /proc proc defaults 0 0\n')
504 f
.write('%s / %s errors=remount-ro 0 1\n' % (rootdevstr
, roottype
))
506 f
.write('%s /boot %s errors=remount-ro 0 2\n' % (bootdevstr
, boottype
))
507 if self
.settings
['swap'] > 0:
508 f
.write("/dev/sda3 swap swap defaults 0 0\n")
509 elif self
.settings
['swap'] > 0:
510 f
.write("/dev/sda2 swap swap defaults 0 0\n")
512 def install_debs(self
, rootdir
):
513 if not self
.settings
['custom-package']:
515 self
.message('Installing custom packages')
516 tmp
= os
.path
.join(rootdir
, 'tmp', 'install_debs')
518 for deb
in self
.settings
['custom-package']:
519 shutil
.copy(deb
, tmp
)
520 filenames
= [os
.path
.join('/tmp/install_debs', os
.path
.basename(deb
))
521 for deb
in self
.settings
['custom-package']]
523 self
.runcmd_unchecked(['chroot', rootdir
, 'dpkg', '-i'] + filenames
)
524 logging
.debug('stdout:\n%s', out
)
525 logging
.debug('stderr:\n%s', err
)
526 out
= self
.runcmd(['chroot', rootdir
,
527 'apt-get', '-f', '--no-remove', 'install'])
528 logging
.debug('stdout:\n%s', out
)
531 def cleanup_apt_cache(self
, rootdir
):
532 out
= self
.runcmd(['chroot', rootdir
, 'apt-get', 'clean'])
533 logging
.debug('stdout:\n%s', out
)
535 def set_root_password(self
, rootdir
):
536 if self
.settings
['root-password']:
537 self
.message('Setting root password')
538 self
.set_password(rootdir
, 'root', self
.settings
['root-password'])
539 elif self
.settings
['lock-root-password']:
540 self
.message('Locking root password')
541 self
.runcmd(['chroot', rootdir
, 'passwd', '-l', 'root'])
543 self
.message('Give root an empty password')
544 self
.delete_password(rootdir
, 'root')
546 def create_users(self
, rootdir
):
547 def create_user(user
):
548 self
.runcmd(['chroot', rootdir
, 'adduser', '--gecos', user
,
549 '--disabled-password', user
])
550 if self
.settings
['sudo']:
551 self
.runcmd(['chroot', rootdir
, 'adduser', user
, 'sudo'])
553 for userpass
in self
.settings
['user']:
555 user
, password
= userpass
.split('/', 1)
557 self
.set_password(rootdir
, user
, password
)
559 create_user(userpass
)
560 self
.delete_password(rootdir
, userpass
)
562 def set_password(self
, rootdir
, user
, password
):
563 encrypted
= crypt
.crypt(password
, '..')
564 self
.runcmd(['chroot', rootdir
, 'usermod', '-p', encrypted
, user
])
566 def delete_password(self
, rootdir
, user
):
567 self
.runcmd(['chroot', rootdir
, 'passwd', '-d', user
])
569 def remove_udev_persistent_rules(self
, rootdir
):
570 self
.message('Removing udev persistent cd and net rules')
571 for x
in ['70-persistent-cd.rules', '70-persistent-net.rules']:
572 pathname
= os
.path
.join(rootdir
, 'etc', 'udev', 'rules.d', x
)
573 if os
.path
.exists(pathname
):
574 logging
.debug('rm %s', pathname
)
577 logging
.debug('not removing non-existent %s', pathname
)
579 def setup_networking(self
, rootdir
):
580 self
.message('Setting up networking')
582 f
= open(os
.path
.join(rootdir
, 'etc', 'network', 'interfaces'), 'w')
584 f
.write('iface lo inet loopback\n')
586 if self
.settings
['enable-dhcp']:
588 f
.write('auto eth0\n')
589 f
.write('iface eth0 inet dhcp\n')
593 def append_serial_console(self
, rootdir
):
594 if self
.settings
['serial-console']:
595 serial_command
= self
.settings
['serial-console-command']
596 logging
.debug('adding getty to serial console')
597 inittab
= os
.path
.join(rootdir
, 'etc/inittab')
598 # to autologin, serial_command can contain '-a root'
599 with
open(inittab
, 'a') as f
:
600 f
.write('\nS0:23:respawn:%s\n' % serial_command
)
602 def _grub_serial_console(self
, rootdir
):
603 cmdline
= 'GRUB_CMDLINE_LINUX_DEFAULT="console=tty0 console=tty1 console=ttyS0,38400n8"'
604 terminal
= 'GRUB_TERMINAL="serial gfxterm"'
605 command
= 'GRUB_SERIAL_COMMAND="serial --speed=38400 --unit=0 --parity=no --stop=1"'
606 grub_cfg
= os
.path
.join(rootdir
, 'etc', 'default', 'grub')
607 logging
.debug("Allowing serial output in grub config %s", grub_cfg
)
608 with
open(grub_cfg
, 'a+') as cfg
:
609 cfg
.write("# %s serial support\n" % os
.path
.basename(__file__
))
610 cfg
.write("%s\n" % cmdline
)
611 cfg
.write("%s\n" % terminal
)
612 cfg
.write("%s\n" % command
)
614 def install_grub2(self
, rootdev
, rootdir
):
615 self
.message("Configuring grub2")
616 # rely on kpartx using consistent naming to map loop0p1 to loop0
617 install_dev
= os
.path
.join('/dev', os
.path
.basename(rootdev
)[:-2])
618 self
.runcmd(['mount', '/dev', '-t', 'devfs', '-obind',
619 '%s' % os
.path
.join(rootdir
, 'dev')])
620 self
.runcmd(['mount', '/proc', '-t', 'proc', '-obind',
621 '%s' % os
.path
.join(rootdir
, 'proc')])
622 self
.runcmd(['mount', '/sys', '-t', 'sysfs', '-obind',
623 '%s' % os
.path
.join(rootdir
, 'sys')])
624 if self
.settings
['serial-console']:
625 self
._grub
_serial
_console
(rootdir
)
628 self
.runcmd(['chroot', rootdir
, 'update-grub'])
629 self
.runcmd(['chroot', rootdir
, 'grub-install', install_dev
])
630 except cliapp
.AppException
:
631 self
.message("Failed. Is grub2-common installed? Using extlinux.")
632 self
.install_extlinux(rootdev
, rootdir
)
633 self
.runcmd(['umount', os
.path
.join(rootdir
, 'sys')])
634 self
.runcmd(['umount', os
.path
.join(rootdir
, 'proc')])
635 self
.runcmd(['umount', os
.path
.join(rootdir
, 'dev')])
637 def install_extlinux(self
, rootdev
, rootdir
):
638 if not os
.path
.exists("/usr/bin/extlinux"):
639 self
.message("extlinux not installed, skipping.")
641 self
.message('Installing extlinux')
644 dirname
= os
.path
.join(rootdir
, 'boot')
645 basenames
= os
.listdir(dirname
)
646 logging
.debug('find: %s', basenames
)
647 for basename
in basenames
:
648 if re
.search(pattern
, basename
):
649 return os
.path
.join('boot', basename
)
650 raise cliapp
.AppException('Cannot find match: %s' % pattern
)
653 kernel_image
= find('vmlinuz-.*')
654 initrd_image
= find('initrd.img-.*')
655 except cliapp
.AppException
as e
:
656 self
.message("Unable to find kernel. Not installing extlinux.")
657 logging
.debug("No kernel found. %s. Skipping install of extlinux.", e
)
660 out
= self
.runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
661 '-s', 'UUID', rootdev
])
662 uuid
= out
.splitlines()[0].strip()
664 conf
= os
.path
.join(rootdir
, 'extlinux.conf')
665 logging
.debug('configure extlinux %s', conf
)
666 kserial
= 'console=ttyS0,115200' if self
.settings
['serial-console'] else ''
667 extserial
= 'serial 0 115200' if self
.settings
['serial-console'] else ''
674 append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s
677 'kernel': kernel_image
, # pylint: disable=bad-continuation
678 'initrd': initrd_image
, # pylint: disable=bad-continuation
679 'uuid': uuid
, # pylint: disable=bad-continuation
680 'kserial': kserial
, # pylint: disable=bad-continuation
681 'extserial': extserial
, # pylint: disable=bad-continuation
682 } # pylint: disable=bad-continuation
683 logging
.debug("extlinux config:\n%s", msg
)
685 # python multiline string substitution is just ugly.
686 # use an external file or live with the mangling, no point in
687 # mangling the string to remove spaces just to keep it pretty in source.
691 self
.runcmd(['extlinux', '--install', rootdir
])
692 self
.runcmd(['sync'])
695 def optimize_image(self
, rootdir
):
697 Filing up the image with zeros will increase its compression rate
699 if not self
.settings
['sparse']:
700 zeros
= os
.path
.join(rootdir
, 'ZEROS')
701 self
.runcmd_unchecked(['dd', 'if=/dev/zero', 'of=' + zeros
, 'bs=1M'])
702 self
.runcmd(['rm', '-f', zeros
])
706 Run squashfs on the image.
708 if not os
.path
.exists('/usr/bin/mksquashfs'):
709 logging
.warning("Squash selected but mksquashfs not found!")
711 self
.message("Running mksquashfs")
712 suffixed
= "%s.squashfs" % self
.settings
['image']
713 self
.runcmd(['mksquashfs', self
.settings
['image'],
715 '-no-progress', '-comp', 'xz'], ignore_fail
=False)
716 os
.unlink(self
.settings
['image'])
717 self
.settings
['image'] = suffixed
719 def cleanup_system(self
):
720 # Clean up after any errors.
722 self
.message('Cleaning up')
724 # Umount in the reverse mount order
725 if self
.settings
['image']:
726 for i
in range(len(self
.mount_points
) - 1, -1, -1):
727 mount_point
= self
.mount_points
[i
]
729 self
.runcmd(['umount', mount_point
], ignore_fail
=False)
730 except cliapp
.AppException
:
731 logging
.debug("umount failed, sleeping and trying again")
733 self
.runcmd(['umount', mount_point
], ignore_fail
=False)
735 self
.runcmd(['kpartx', '-d', self
.settings
['image']], ignore_fail
=True)
737 for dirname
in self
.remove_dirs
:
738 shutil
.rmtree(dirname
)
740 def customize(self
, rootdir
):
741 script
= self
.settings
['customize']
744 if not os
.path
.exists(script
):
745 example
= os
.path
.join("/usr/share/vmdebootstrap/examples/", script
)
746 if not os
.path
.exists(example
):
747 self
.message("Unable to find %s" % script
)
750 self
.message('Running customize script %s' % script
)
751 logging
.info("rootdir=%s image=%s", rootdir
, self
.settings
['image'])
752 with
open('/dev/tty', 'w') as tty
:
754 cliapp
.runcmd([script
, rootdir
, self
.settings
['image']], stdout
=tty
, stderr
=tty
)
756 subprocess
.call([script
, rootdir
, self
.settings
['image']])
758 def create_tarball(self
, rootdir
):
759 # Create a tarball of the disk's contents
760 # shell out to runcmd since it more easily handles rootdir
761 self
.message('Creating tarball of disk contents')
762 self
.runcmd(['tar', '-cf', self
.settings
['tarball'], '-C', rootdir
, '.'])
765 # Change image owner after completed build
766 if self
.settings
['image']:
767 filename
= self
.settings
['image']
768 elif self
.settings
['tarball']:
769 filename
= self
.settings
['tarball']
772 self
.message("Changing owner to %s" % self
.settings
["owner"])
773 subprocess
.call(["chown", self
.settings
["owner"], filename
])
775 def list_installed_pkgs(self
, rootdir
):
776 # output the list of installed packages for sources identification
777 self
.message("Creating a list of installed binary package names")
778 out
= self
.runcmd(['chroot', rootdir
,
779 'dpkg-query', '-W', "-f='${Package}.deb\n'"])
780 with
open('dpkg.list', 'w') as dpkg
:
783 def configure_apt(self
, rootdir
):
784 # use the distribution and mirror to create an apt source
785 self
.message("Configuring apt to use distribution and mirror")
786 conf
= os
.path
.join(rootdir
, 'etc', 'apt', 'sources.list.d', 'base.list')
787 logging
.debug('configure apt %s', conf
)
788 mirror
= self
.settings
['mirror']
789 if self
.settings
['apt-mirror']:
790 mirror
= self
.settings
['apt-mirror']
791 self
.message("Setting apt mirror to %s" % mirror
)
792 os
.unlink(os
.path
.join(rootdir
, 'etc', 'apt', 'sources.list'))
794 line
= 'deb %s %s main\n' % (mirror
, self
.settings
['distribution'])
796 line
= '#deb-src %s %s main\n' % (mirror
, self
.settings
['distribution'])
799 # ensure the apt sources have valid lists
800 self
.runcmd(['chroot', rootdir
, 'apt-get', '-qq', 'update'])
802 if __name__
== '__main__':
803 VmDebootstrap(version
=__version__
).run()