]>
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 install_grub2(self
, rootdev
, rootdir
):
603 self
.message("Configuring grub2")
604 # rely on kpartx using consistent naming to map loop0p1 to loop0
605 install_dev
= os
.path
.join('/dev', os
.path
.basename(rootdev
)[:-2])
606 self
.runcmd(['mount', '/dev', '-t', 'devfs', '-obind',
607 '%s' % os
.path
.join(rootdir
, 'dev')])
608 self
.runcmd(['mount', '/proc', '-t', 'proc', '-obind',
609 '%s' % os
.path
.join(rootdir
, 'proc')])
610 self
.runcmd(['mount', '/sys', '-t', 'sysfs', '-obind',
611 '%s' % os
.path
.join(rootdir
, 'sys')])
613 cmdline
= 'GRUB_CMDLINE_LINUX_DEFAULT="console=tty0 console=tty1 console=ttyS0,38400n8"'
614 terminal
= 'GRUB_TERMINAL="serial gfxterm"'
615 command
= 'GRUB_SERIAL_COMMAND="serial --speed=38400 --unit=0 --parity=no --stop=1"'
616 grub_cfg
= os
.path
.join(rootdir
, 'etc', 'default', 'grub')
617 logging
.debug("Allowing serial output in grub config %s", grub_cfg
)
618 with
open(grub_cfg
, 'a+') as cfg
:
619 cfg
.write("# %s serial support\n" % os
.path
.basename(__file__
))
620 cfg
.write("%s\n" % cmdline
)
621 cfg
.write("%s\n" % terminal
)
622 cfg
.write("%s\n" % command
)
624 self
.runcmd(['chroot', rootdir
, 'update-grub'])
625 self
.runcmd(['chroot', rootdir
, 'grub-install', install_dev
])
626 except cliapp
.AppException
:
627 self
.message("Failed. Is grub2-common installed? Using extlinux.")
628 self
.runcmd(['umount', os
.path
.join(rootdir
, 'sys')])
629 self
.runcmd(['umount', os
.path
.join(rootdir
, 'proc')])
630 self
.runcmd(['umount', os
.path
.join(rootdir
, 'dev')])
631 self
.install_extlinux(rootdev
, rootdir
)
633 def install_extlinux(self
, rootdev
, rootdir
):
634 if not os
.path
.exists("/usr/bin/extlinux"):
635 self
.message("extlinux not installed, skipping.")
637 self
.message('Installing extlinux')
640 dirname
= os
.path
.join(rootdir
, 'boot')
641 basenames
= os
.listdir(dirname
)
642 logging
.debug('find: %s', basenames
)
643 for basename
in basenames
:
644 if re
.search(pattern
, basename
):
645 return os
.path
.join('boot', basename
)
646 raise cliapp
.AppException('Cannot find match: %s' % pattern
)
649 kernel_image
= find('vmlinuz-.*')
650 initrd_image
= find('initrd.img-.*')
651 except cliapp
.AppException
as e
:
652 self
.message("Unable to find kernel. Not installing extlinux.")
653 logging
.debug("No kernel found. %s. Skipping install of extlinux.", e
)
656 out
= self
.runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
657 '-s', 'UUID', rootdev
])
658 uuid
= out
.splitlines()[0].strip()
660 conf
= os
.path
.join(rootdir
, 'extlinux.conf')
661 logging
.debug('configure extlinux %s', conf
)
662 kserial
= 'console=ttyS0,115200' if self
.settings
['serial-console'] else ''
663 extserial
= 'serial 0 115200' if self
.settings
['serial-console'] else ''
670 append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s
673 'kernel': kernel_image
, # pylint: disable=bad-continuation
674 'initrd': initrd_image
, # pylint: disable=bad-continuation
675 'uuid': uuid
, # pylint: disable=bad-continuation
676 'kserial': kserial
, # pylint: disable=bad-continuation
677 'extserial': extserial
, # pylint: disable=bad-continuation
678 } # pylint: disable=bad-continuation
679 logging
.debug("extlinux config:\n%s", msg
)
681 # python multiline string substitution is just ugly.
682 # use an external file or live with the mangling, no point in
683 # mangling the string to remove spaces just to keep it pretty in source.
687 self
.runcmd(['extlinux', '--install', rootdir
])
688 self
.runcmd(['sync'])
691 def optimize_image(self
, rootdir
):
693 Filing up the image with zeros will increase its compression rate
695 if not self
.settings
['sparse']:
696 zeros
= os
.path
.join(rootdir
, 'ZEROS')
697 self
.runcmd_unchecked(['dd', 'if=/dev/zero', 'of=' + zeros
, 'bs=1M'])
698 self
.runcmd(['rm', '-f', zeros
])
702 Run squashfs on the image.
704 if not os
.path
.exists('/usr/bin/mksquashfs'):
705 logging
.warning("Squash selected but mksquashfs not found!")
707 self
.message("Running mksquashfs")
708 suffixed
= "%s.squashfs" % self
.settings
['image']
709 self
.runcmd(['mksquashfs', self
.settings
['image'],
711 '-no-progress', '-comp', 'xz'], ignore_fail
=False)
712 os
.unlink(self
.settings
['image'])
713 self
.settings
['image'] = suffixed
715 def cleanup_system(self
):
716 # Clean up after any errors.
718 self
.message('Cleaning up')
720 # Umount in the reverse mount order
721 if self
.settings
['image']:
722 for i
in range(len(self
.mount_points
) - 1, -1, -1):
723 mount_point
= self
.mount_points
[i
]
725 self
.runcmd(['umount', mount_point
], ignore_fail
=False)
726 except cliapp
.AppException
:
727 logging
.debug("umount failed, sleeping and trying again")
729 self
.runcmd(['umount', mount_point
], ignore_fail
=False)
731 self
.runcmd(['kpartx', '-d', self
.settings
['image']], ignore_fail
=True)
733 for dirname
in self
.remove_dirs
:
734 shutil
.rmtree(dirname
)
736 def customize(self
, rootdir
):
737 script
= self
.settings
['customize']
740 if not os
.path
.exists(script
):
741 example
= os
.path
.join("/usr/share/vmdebootstrap/examples/", script
)
742 if not os
.path
.exists(example
):
743 self
.message("Unable to find %s" % script
)
746 self
.message('Running customize script %s' % script
)
747 logging
.info("rootdir=%s image=%s", rootdir
, self
.settings
['image'])
748 with
open('/dev/tty', 'w') as tty
:
750 cliapp
.runcmd([script
, rootdir
, self
.settings
['image']], stdout
=tty
, stderr
=tty
)
752 subprocess
.call([script
, rootdir
, self
.settings
['image']])
754 def create_tarball(self
, rootdir
):
755 # Create a tarball of the disk's contents
756 # shell out to runcmd since it more easily handles rootdir
757 self
.message('Creating tarball of disk contents')
758 self
.runcmd(['tar', '-cf', self
.settings
['tarball'], '-C', rootdir
, '.'])
761 # Change image owner after completed build
762 if self
.settings
['image']:
763 filename
= self
.settings
['image']
764 elif self
.settings
['tarball']:
765 filename
= self
.settings
['tarball']
768 self
.message("Changing owner to %s" % self
.settings
["owner"])
769 subprocess
.call(["chown", self
.settings
["owner"], filename
])
771 def list_installed_pkgs(self
, rootdir
):
772 # output the list of installed packages for sources identification
773 self
.message("Creating a list of installed binary package names")
774 out
= self
.runcmd(['chroot', rootdir
,
775 'dpkg-query', '-W', "-f='${Package}.deb\n'"])
776 with
open('dpkg.list', 'w') as dpkg
:
779 def configure_apt(self
, rootdir
):
780 # use the distribution and mirror to create an apt source
781 self
.message("Configuring apt to use distribution and mirror")
782 conf
= os
.path
.join(rootdir
, 'etc', 'apt', 'sources.list.d', 'base.list')
783 logging
.debug('configure apt %s', conf
)
784 mirror
= self
.settings
['mirror']
785 if self
.settings
['apt-mirror']:
786 mirror
= self
.settings
['apt-mirror']
787 self
.message("Setting apt mirror to %s" % mirror
)
788 os
.unlink(os
.path
.join(rootdir
, 'etc', 'apt', 'sources.list'))
790 line
= 'deb %s %s main\n' % (mirror
, self
.settings
['distribution'])
792 line
= '#deb-src %s %s main\n' % (mirror
, self
.settings
['distribution'])
795 # ensure the apt sources have valid lists
796 self
.runcmd(['chroot', rootdir
, 'apt-get', '-qq', 'update'])
798 if __name__
== '__main__':
799 VmDebootstrap(version
=__version__
).run()