]>
git.siccegge.de Git - forks/vmdebootstrap.git/blob - vmdebootstrap
a9127aed213f76249023f37ea1b1126ce3a0cd7b
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('grub2')
423 if not self
.settings
['no-kernel']:
424 if self
.settings
['arch'] == 'i386':
427 kernel_arch
= self
.settings
['arch']
428 kernel_image
= 'linux-image-%s' % kernel_arch
429 include
.append(kernel_image
)
431 if self
.settings
['sudo'] and 'sudo' not in include
:
432 include
.append('sudo')
434 args
= ['debootstrap', '--arch=%s' % self
.settings
['arch']]
436 if self
.settings
['package']:
438 '--include=%s' % ','.join(include
))
439 if self
.settings
['foreign']:
440 args
.append('--foreign')
441 if self
.settings
['variant']:
442 args
.append('--variant')
443 args
.append(self
.settings
['variant'])
444 args
+= [self
.settings
['distribution'],
445 rootdir
, self
.settings
['mirror']]
446 logging
.debug(" ".join(args
))
448 if self
.settings
['foreign']:
449 # set a noninteractive debconf environment for secondstage
451 "DEBIAN_FRONTEND": "noninteractive",
452 "DEBCONF_NONINTERACTIVE_SEEN": "true",
455 # add the mapping to the complete environment.
456 env
.update(os
.environ
)
457 # First copy the binfmt handler over
458 self
.message('Setting up binfmt handler')
459 shutil
.copy(self
.settings
['foreign'], '%s/usr/bin/' % rootdir
)
460 # Next, run the package install scripts etc.
461 self
.message('Running debootstrap second stage')
462 self
.runcmd(['chroot', rootdir
,
463 '/debootstrap/debootstrap', '--second-stage'],
466 def set_hostname(self
, rootdir
):
467 hostname
= self
.settings
['hostname']
468 with
open(os
.path
.join(rootdir
, 'etc', 'hostname'), 'w') as f
:
469 f
.write('%s\n' % hostname
)
471 etc_hosts
= os
.path
.join(rootdir
, 'etc', 'hosts')
473 with
open(etc_hosts
, 'r') as f
:
475 with
open(etc_hosts
, 'w') as f
:
476 for line
in data
.splitlines():
477 if line
.startswith('127.0.0.1'):
478 line
+= ' %s' % hostname
479 f
.write('%s\n' % line
)
483 def create_fstab(self
, rootdir
, rootdev
, roottype
, bootdev
, boottype
): # pylint: disable=too-many-arguments
485 out
= self
.runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
486 '-s', 'UUID', device
])
487 return out
.splitlines()[0].strip()
490 rootdevstr
= 'UUID=%s' % fsuuid(rootdev
)
492 rootdevstr
= '/dev/sda1'
495 bootdevstr
= 'UUID=%s' % fsuuid(bootdev
)
499 fstab
= os
.path
.join(rootdir
, 'etc', 'fstab')
500 with
open(fstab
, 'w') as f
:
501 f
.write('proc /proc proc defaults 0 0\n')
502 f
.write('%s / %s errors=remount-ro 0 1\n' % (rootdevstr
, roottype
))
504 f
.write('%s /boot %s errors=remount-ro 0 2\n' % (bootdevstr
, boottype
))
505 if self
.settings
['swap'] > 0:
506 f
.write("/dev/sda3 swap swap defaults 0 0\n")
507 elif self
.settings
['swap'] > 0:
508 f
.write("/dev/sda2 swap swap defaults 0 0\n")
510 def install_debs(self
, rootdir
):
511 if not self
.settings
['custom-package']:
513 self
.message('Installing custom packages')
514 tmp
= os
.path
.join(rootdir
, 'tmp', 'install_debs')
516 for deb
in self
.settings
['custom-package']:
517 shutil
.copy(deb
, tmp
)
518 filenames
= [os
.path
.join('/tmp/install_debs', os
.path
.basename(deb
))
519 for deb
in self
.settings
['custom-package']]
521 self
.runcmd_unchecked(['chroot', rootdir
, 'dpkg', '-i'] + filenames
)
522 logging
.debug('stdout:\n%s', out
)
523 logging
.debug('stderr:\n%s', err
)
524 out
= self
.runcmd(['chroot', rootdir
,
525 'apt-get', '-f', '--no-remove', 'install'])
526 logging
.debug('stdout:\n%s', out
)
529 def cleanup_apt_cache(self
, rootdir
):
530 out
= self
.runcmd(['chroot', rootdir
, 'apt-get', 'clean'])
531 logging
.debug('stdout:\n%s', out
)
533 def set_root_password(self
, rootdir
):
534 if self
.settings
['root-password']:
535 self
.message('Setting root password')
536 self
.set_password(rootdir
, 'root', self
.settings
['root-password'])
537 elif self
.settings
['lock-root-password']:
538 self
.message('Locking root password')
539 self
.runcmd(['chroot', rootdir
, 'passwd', '-l', 'root'])
541 self
.message('Give root an empty password')
542 self
.delete_password(rootdir
, 'root')
544 def create_users(self
, rootdir
):
545 def create_user(user
):
546 self
.runcmd(['chroot', rootdir
, 'adduser', '--gecos', user
,
547 '--disabled-password', user
])
548 if self
.settings
['sudo']:
549 self
.runcmd(['chroot', rootdir
, 'adduser', user
, 'sudo'])
551 for userpass
in self
.settings
['user']:
553 user
, password
= userpass
.split('/', 1)
555 self
.set_password(rootdir
, user
, password
)
557 create_user(userpass
)
558 self
.delete_password(rootdir
, userpass
)
560 def set_password(self
, rootdir
, user
, password
):
561 encrypted
= crypt
.crypt(password
, '..')
562 self
.runcmd(['chroot', rootdir
, 'usermod', '-p', encrypted
, user
])
564 def delete_password(self
, rootdir
, user
):
565 self
.runcmd(['chroot', rootdir
, 'passwd', '-d', user
])
567 def remove_udev_persistent_rules(self
, rootdir
):
568 self
.message('Removing udev persistent cd and net rules')
569 for x
in ['70-persistent-cd.rules', '70-persistent-net.rules']:
570 pathname
= os
.path
.join(rootdir
, 'etc', 'udev', 'rules.d', x
)
571 if os
.path
.exists(pathname
):
572 logging
.debug('rm %s', pathname
)
575 logging
.debug('not removing non-existent %s', pathname
)
577 def setup_networking(self
, rootdir
):
578 self
.message('Setting up networking')
580 f
= open(os
.path
.join(rootdir
, 'etc', 'network', 'interfaces'), 'w')
582 f
.write('iface lo inet loopback\n')
584 if self
.settings
['enable-dhcp']:
586 f
.write('auto eth0\n')
587 f
.write('iface eth0 inet dhcp\n')
591 def append_serial_console(self
, rootdir
):
592 if self
.settings
['serial-console']:
593 serial_command
= self
.settings
['serial-console-command']
594 logging
.debug('adding getty to serial console')
595 inittab
= os
.path
.join(rootdir
, 'etc/inittab')
596 # to autologin, serial_command can contain '-a root'
597 with
open(inittab
, 'a') as f
:
598 f
.write('\nS0:23:respawn:%s\n' % serial_command
)
600 def install_grub2(self
, rootdev
, rootdir
):
601 self
.message("Configuring grub2")
602 # rely on kpartx using consistent naming to map loop0p1 to loop0
603 install_dev
= os
.path
.join('/dev', os
.path
.basename(rootdev
)[:-2])
604 self
.runcmd(['mount', '/dev', '-t', 'devfs', '-obind',
605 '%s' % os
.path
.join(rootdir
, 'dev')])
606 self
.runcmd(['mount', '/proc', '-t', 'proc', '-obind',
607 '%s' % os
.path
.join(rootdir
, 'proc')])
608 self
.runcmd(['mount', '/sys', '-t', 'sysfs', '-obind',
609 '%s' % os
.path
.join(rootdir
, 'sys')])
611 cmdline
= 'GRUB_CMDLINE_LINUX_DEFAULT="console=tty0 console=tty1 console=ttyS0,38400n8"'
612 terminal
= 'GRUB_TERMINAL="serial gfxterm"'
613 command
= 'GRUB_SERIAL_COMMAND="serial --speed=38400 --unit=0 --parity=no --stop=1"'
614 grub_cfg
= os
.path
.join(rootdir
, 'etc', 'default', 'grub')
615 logging
.debug("Allowing serial output in grub config %s", grub_cfg
)
616 with
open(grub_cfg
, 'a+') as cfg
:
617 cfg
.write("# %s serial support\n" % os
.path
.basename(__file__
))
618 cfg
.write("%s\n" % cmdline
)
619 cfg
.write("%s\n" % terminal
)
620 cfg
.write("%s\n" % command
)
622 self
.runcmd(['chroot', rootdir
, 'update-grub'])
623 self
.runcmd(['chroot', rootdir
, 'grub-install', install_dev
])
624 except cliapp
.AppException
:
625 self
.message("Failed. Is grub2-common installed? Using extlinux.")
626 self
.runcmd(['umount', os
.path
.join(rootdir
, 'sys')])
627 self
.runcmd(['umount', os
.path
.join(rootdir
, 'proc')])
628 self
.runcmd(['umount', os
.path
.join(rootdir
, 'dev')])
629 self
.install_extlinux(rootdev
, rootdir
)
631 def install_extlinux(self
, rootdev
, rootdir
):
632 if not os
.path
.exists("/usr/bin/extlinux"):
633 self
.message("extlinux not installed, skipping.")
635 self
.message('Installing extlinux')
638 dirname
= os
.path
.join(rootdir
, 'boot')
639 basenames
= os
.listdir(dirname
)
640 logging
.debug('find: %s', basenames
)
641 for basename
in basenames
:
642 if re
.search(pattern
, basename
):
643 return os
.path
.join('boot', basename
)
644 raise cliapp
.AppException('Cannot find match: %s' % pattern
)
647 kernel_image
= find('vmlinuz-.*')
648 initrd_image
= find('initrd.img-.*')
649 except cliapp
.AppException
as e
:
650 self
.message("Unable to find kernel. Not installing extlinux.")
651 logging
.debug("No kernel found. %s. Skipping install of extlinux.", e
)
654 out
= self
.runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
655 '-s', 'UUID', rootdev
])
656 uuid
= out
.splitlines()[0].strip()
658 conf
= os
.path
.join(rootdir
, 'extlinux.conf')
659 logging
.debug('configure extlinux %s', conf
)
660 kserial
= 'console=ttyS0,115200' if self
.settings
['serial-console'] else ''
661 extserial
= 'serial 0 115200' if self
.settings
['serial-console'] else ''
668 append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s
671 'kernel': kernel_image
, # pylint: disable=bad-continuation
672 'initrd': initrd_image
, # pylint: disable=bad-continuation
673 'uuid': uuid
, # pylint: disable=bad-continuation
674 'kserial': kserial
, # pylint: disable=bad-continuation
675 'extserial': extserial
, # pylint: disable=bad-continuation
676 } # pylint: disable=bad-continuation
677 logging
.debug("extlinux config:\n%s", msg
)
679 # python multiline string substitution is just ugly.
680 # use an external file or live with the mangling, no point in
681 # mangling the string to remove spaces just to keep it pretty in source.
685 self
.runcmd(['extlinux', '--install', rootdir
])
686 self
.runcmd(['sync'])
689 def optimize_image(self
, rootdir
):
691 Filing up the image with zeros will increase its compression rate
693 if not self
.settings
['sparse']:
694 zeros
= os
.path
.join(rootdir
, 'ZEROS')
695 self
.runcmd_unchecked(['dd', 'if=/dev/zero', 'of=' + zeros
, 'bs=1M'])
696 self
.runcmd(['rm', '-f', zeros
])
700 Run squashfs on the image.
702 if not os
.path
.exists('/usr/bin/mksquashfs'):
703 logging
.warning("Squash selected but mksquashfs not found!")
705 self
.message("Running mksquashfs")
706 suffixed
= "%s.squashfs" % self
.settings
['image']
707 self
.runcmd(['mksquashfs', self
.settings
['image'],
709 '-no-progress', '-comp', 'xz'], ignore_fail
=False)
710 os
.unlink(self
.settings
['image'])
711 self
.settings
['image'] = suffixed
713 def cleanup_system(self
):
714 # Clean up after any errors.
716 self
.message('Cleaning up')
718 # Umount in the reverse mount order
719 if self
.settings
['image']:
720 for i
in range(len(self
.mount_points
) - 1, -1, -1):
721 mount_point
= self
.mount_points
[i
]
723 self
.runcmd(['umount', mount_point
], ignore_fail
=False)
724 except cliapp
.AppException
:
725 logging
.debug("umount failed, sleeping and trying again")
727 self
.runcmd(['umount', mount_point
], ignore_fail
=False)
729 self
.runcmd(['kpartx', '-d', self
.settings
['image']], ignore_fail
=True)
731 for dirname
in self
.remove_dirs
:
732 shutil
.rmtree(dirname
)
734 def customize(self
, rootdir
):
735 script
= self
.settings
['customize']
738 if not os
.path
.exists(script
):
739 example
= os
.path
.join("/usr/share/vmdebootstrap/examples/", script
)
740 if not os
.path
.exists(example
):
741 self
.message("Unable to find %s" % script
)
744 self
.message('Running customize script %s' % script
)
745 logging
.info("rootdir=%s image=%s", rootdir
, self
.settings
['image'])
746 with
open('/dev/tty', 'w') as tty
:
748 cliapp
.runcmd([script
, rootdir
, self
.settings
['image']], stdout
=tty
, stderr
=tty
)
750 subprocess
.call([script
, rootdir
, self
.settings
['image']])
752 def create_tarball(self
, rootdir
):
753 # Create a tarball of the disk's contents
754 # shell out to runcmd since it more easily handles rootdir
755 self
.message('Creating tarball of disk contents')
756 self
.runcmd(['tar', '-cf', self
.settings
['tarball'], '-C', rootdir
, '.'])
759 # Change image owner after completed build
760 if self
.settings
['image']:
761 filename
= self
.settings
['image']
762 elif self
.settings
['tarball']:
763 filename
= self
.settings
['tarball']
766 self
.message("Changing owner to %s" % self
.settings
["owner"])
767 subprocess
.call(["chown", self
.settings
["owner"], filename
])
769 def list_installed_pkgs(self
, rootdir
):
770 # output the list of installed packages for sources identification
771 self
.message("Creating a list of installed binary package names")
772 out
= self
.runcmd(['chroot', rootdir
,
773 'dpkg-query', '-W', "-f='${Package}.deb\n'"])
774 with
open('dpkg.list', 'w') as dpkg
:
777 def configure_apt(self
, rootdir
):
778 # use the distribution and mirror to create an apt source
779 self
.message("Configuring apt to use distribution and mirror")
780 conf
= os
.path
.join(rootdir
, 'etc', 'apt', 'sources.list.d', 'base.list')
781 logging
.debug('configure apt %s', conf
)
782 mirror
= self
.settings
['mirror']
783 if self
.settings
['apt-mirror']:
784 mirror
= self
.settings
['apt-mirror']
785 self
.message("Setting apt mirror to %s" % mirror
)
786 os
.unlink(os
.path
.join(rootdir
, 'etc', 'apt', 'sources.list'))
788 line
= 'deb %s %s main\n' % (mirror
, self
.settings
['distribution'])
790 line
= '#deb-src %s %s main\n' % (mirror
, self
.settings
['distribution'])
793 # ensure the apt sources have valid lists
794 self
.runcmd(['chroot', rootdir
, 'apt-get', '-qq', 'update'])
796 if __name__
== '__main__':
797 VmDebootstrap(version
=__version__
).run()