]>
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 if self
.settings
['grub'] and not partoffset
:
338 bootsize
= self
.settings
['bootsize'] / (1024 * 1024)
339 bootsize
+= partoffset
340 self
.message("Using bootsize %smib: %s bytes" % (bootsize
, self
.settings
['bootsize']))
341 logging
.debug("Starting boot partition at %sMb", bootsize
)
342 self
.runcmd(['parted', '-s', self
.settings
['image'],
343 'mkpart', 'primary', 'fat16', str(partoffset
), str(bootsize
)])
344 logging
.debug("Starting root partition at %sMb", partoffset
)
345 self
.runcmd(['parted', '-s', self
.settings
['image'],
346 'mkpart', 'primary', str(bootsize
), extent
])
348 self
.runcmd(['parted', '-s', self
.settings
['image'],
349 'mkpart', 'primary', '0%', extent
])
350 self
.runcmd(['parted', '-s', self
.settings
['image'],
351 'set', '1', 'boot', 'on'])
352 if self
.settings
['swap'] > 0:
353 logging
.debug("Creating swap partition")
354 self
.runcmd(['parted', '-s', self
.settings
['image'],
355 'mkpart', 'primary', 'linux-swap', extent
, '100%'])
357 def update_initramfs(self
, rootdir
):
358 cmd
= os
.path
.join('usr', 'sbin', 'update-initramfs')
359 if os
.path
.exists(os
.path
.join(rootdir
, cmd
)):
360 self
.message("Updating the initramfs")
361 self
.runcmd(['chroot', rootdir
, cmd
, '-u'])
363 def install_mbr(self
):
364 if os
.path
.exists("/sbin/install-mbr"):
365 self
.message('Installing MBR')
366 self
.runcmd(['install-mbr', self
.settings
['image']])
368 msg
= "mbr enabled but /sbin/install-mbr not found" \
369 " - please install the mbr package."
370 raise cliapp
.AppException(msg
)
372 def setup_kpartx(self
):
375 out
= self
.runcmd(['kpartx', '-avs', self
.settings
['image']])
376 if self
.settings
['bootsize'] and self
.settings
['swap'] > 0:
381 elif self
.settings
['bootsize']:
385 elif self
.settings
['swap'] > 0:
394 devices
= [line
.split()[2]
395 for line
in out
.splitlines()
396 if line
.startswith('add map ')]
397 if len(devices
) != parts
:
398 msg
= 'Surprising number of partitions - check output of losetup -a'
399 logging
.debug("%s", self
.runcmd(['losetup', '-a']))
400 logging
.debug("%s: devices=%s parts=%s", msg
, devices
, parts
)
401 raise cliapp
.AppException(msg
)
402 root
= '/dev/mapper/%s' % devices
[rootindex
]
403 if self
.settings
['bootsize']:
404 boot
= '/dev/mapper/%s' % devices
[bootindex
]
405 if self
.settings
['swap'] > 0:
406 swap
= '/dev/mapper/%s' % devices
[swapindex
]
407 return root
, boot
, swap
409 def mkfs(self
, device
, fstype
):
410 self
.message('Creating filesystem %s' % fstype
)
411 self
.runcmd(['mkfs', '-t', fstype
, device
])
413 def debootstrap(self
, rootdir
):
414 msg
= "(%s)" % self
.settings
['variant'] if self
.settings
['variant'] else ''
415 self
.message('Debootstrapping %s %s' % (self
.settings
['distribution'], msg
))
417 include
= self
.settings
['package']
419 if not self
.settings
['foreign']:
420 include
.append('acpid')
422 if self
.settings
['grub']:
423 include
.append('grub-pc')
425 if not self
.settings
['no-kernel']:
426 if self
.settings
['arch'] == 'i386':
428 elif self
.settings
['arch'] == 'armhf':
429 kernel_arch
= 'armmp'
431 kernel_arch
= self
.settings
['arch']
432 kernel_image
= 'linux-image-%s' % kernel_arch
433 include
.append(kernel_image
)
435 if self
.settings
['sudo'] and 'sudo' not in include
:
436 include
.append('sudo')
438 args
= ['debootstrap', '--arch=%s' % self
.settings
['arch']]
440 if self
.settings
['package']:
442 '--include=%s' % ','.join(include
))
443 if self
.settings
['foreign']:
444 args
.append('--foreign')
445 if self
.settings
['variant']:
446 args
.append('--variant')
447 args
.append(self
.settings
['variant'])
448 args
+= [self
.settings
['distribution'],
449 rootdir
, self
.settings
['mirror']]
450 logging
.debug(" ".join(args
))
452 if self
.settings
['foreign']:
453 # set a noninteractive debconf environment for secondstage
455 "DEBIAN_FRONTEND": "noninteractive",
456 "DEBCONF_NONINTERACTIVE_SEEN": "true",
459 # add the mapping to the complete environment.
460 env
.update(os
.environ
)
461 # First copy the binfmt handler over
462 self
.message('Setting up binfmt handler')
463 shutil
.copy(self
.settings
['foreign'], '%s/usr/bin/' % rootdir
)
464 # Next, run the package install scripts etc.
465 self
.message('Running debootstrap second stage')
466 self
.runcmd(['chroot', rootdir
,
467 '/debootstrap/debootstrap', '--second-stage'],
470 def set_hostname(self
, rootdir
):
471 hostname
= self
.settings
['hostname']
472 with
open(os
.path
.join(rootdir
, 'etc', 'hostname'), 'w') as f
:
473 f
.write('%s\n' % hostname
)
475 etc_hosts
= os
.path
.join(rootdir
, 'etc', 'hosts')
477 with
open(etc_hosts
, 'r') as f
:
479 with
open(etc_hosts
, 'w') as f
:
480 for line
in data
.splitlines():
481 if line
.startswith('127.0.0.1'):
482 line
+= ' %s' % hostname
483 f
.write('%s\n' % line
)
487 def create_fstab(self
, rootdir
, rootdev
, roottype
, bootdev
, boottype
): # pylint: disable=too-many-arguments
489 out
= self
.runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
490 '-s', 'UUID', device
])
491 return out
.splitlines()[0].strip()
494 rootdevstr
= 'UUID=%s' % fsuuid(rootdev
)
496 rootdevstr
= '/dev/sda1'
499 bootdevstr
= 'UUID=%s' % fsuuid(bootdev
)
503 fstab
= os
.path
.join(rootdir
, 'etc', 'fstab')
504 with
open(fstab
, 'w') as f
:
505 f
.write('proc /proc proc defaults 0 0\n')
506 f
.write('%s / %s errors=remount-ro 0 1\n' % (rootdevstr
, roottype
))
508 f
.write('%s /boot %s errors=remount-ro 0 2\n' % (bootdevstr
, boottype
))
509 if self
.settings
['swap'] > 0:
510 f
.write("/dev/sda3 swap swap defaults 0 0\n")
511 elif self
.settings
['swap'] > 0:
512 f
.write("/dev/sda2 swap swap defaults 0 0\n")
514 def install_debs(self
, rootdir
):
515 if not self
.settings
['custom-package']:
517 self
.message('Installing custom packages')
518 tmp
= os
.path
.join(rootdir
, 'tmp', 'install_debs')
520 for deb
in self
.settings
['custom-package']:
521 shutil
.copy(deb
, tmp
)
522 filenames
= [os
.path
.join('/tmp/install_debs', os
.path
.basename(deb
))
523 for deb
in self
.settings
['custom-package']]
525 self
.runcmd_unchecked(['chroot', rootdir
, 'dpkg', '-i'] + filenames
)
526 logging
.debug('stdout:\n%s', out
)
527 logging
.debug('stderr:\n%s', err
)
528 out
= self
.runcmd(['chroot', rootdir
,
529 'apt-get', '-f', '--no-remove', 'install'])
530 logging
.debug('stdout:\n%s', out
)
533 def cleanup_apt_cache(self
, rootdir
):
534 out
= self
.runcmd(['chroot', rootdir
, 'apt-get', 'clean'])
535 logging
.debug('stdout:\n%s', out
)
537 def set_root_password(self
, rootdir
):
538 if self
.settings
['root-password']:
539 self
.message('Setting root password')
540 self
.set_password(rootdir
, 'root', self
.settings
['root-password'])
541 elif self
.settings
['lock-root-password']:
542 self
.message('Locking root password')
543 self
.runcmd(['chroot', rootdir
, 'passwd', '-l', 'root'])
545 self
.message('Give root an empty password')
546 self
.delete_password(rootdir
, 'root')
548 def create_users(self
, rootdir
):
549 def create_user(user
):
550 self
.runcmd(['chroot', rootdir
, 'adduser', '--gecos', user
,
551 '--disabled-password', user
])
552 if self
.settings
['sudo']:
553 self
.runcmd(['chroot', rootdir
, 'adduser', user
, 'sudo'])
555 for userpass
in self
.settings
['user']:
557 user
, password
= userpass
.split('/', 1)
559 self
.set_password(rootdir
, user
, password
)
561 create_user(userpass
)
562 self
.delete_password(rootdir
, userpass
)
564 def set_password(self
, rootdir
, user
, password
):
565 encrypted
= crypt
.crypt(password
, '..')
566 self
.runcmd(['chroot', rootdir
, 'usermod', '-p', encrypted
, user
])
568 def delete_password(self
, rootdir
, user
):
569 self
.runcmd(['chroot', rootdir
, 'passwd', '-d', user
])
571 def remove_udev_persistent_rules(self
, rootdir
):
572 self
.message('Removing udev persistent cd and net rules')
573 for x
in ['70-persistent-cd.rules', '70-persistent-net.rules']:
574 pathname
= os
.path
.join(rootdir
, 'etc', 'udev', 'rules.d', x
)
575 if os
.path
.exists(pathname
):
576 logging
.debug('rm %s', pathname
)
579 logging
.debug('not removing non-existent %s', pathname
)
581 def setup_networking(self
, rootdir
):
582 self
.message('Setting up networking')
584 f
= open(os
.path
.join(rootdir
, 'etc', 'network', 'interfaces'), 'w')
586 f
.write('iface lo inet loopback\n')
588 if self
.settings
['enable-dhcp']:
590 f
.write('auto eth0\n')
591 f
.write('iface eth0 inet dhcp\n')
595 def append_serial_console(self
, rootdir
):
596 if self
.settings
['serial-console']:
597 serial_command
= self
.settings
['serial-console-command']
598 logging
.debug('adding getty to serial console')
599 inittab
= os
.path
.join(rootdir
, 'etc/inittab')
600 # to autologin, serial_command can contain '-a root'
601 with
open(inittab
, 'a') as f
:
602 f
.write('\nS0:23:respawn:%s\n' % serial_command
)
604 # pylint: disable=no-self-use
605 def _grub_serial_console(self
, rootdir
):
606 cmdline
= 'GRUB_CMDLINE_LINUX_DEFAULT="console=tty0 console=tty1 console=ttyS0,38400n8"'
607 terminal
= 'GRUB_TERMINAL="serial gfxterm"'
608 command
= 'GRUB_SERIAL_COMMAND="serial --speed=38400 --unit=0 --parity=no --stop=1"'
609 grub_cfg
= os
.path
.join(rootdir
, 'etc', 'default', 'grub')
610 logging
.debug("Allowing serial output in grub config %s", grub_cfg
)
611 with
open(grub_cfg
, 'a+') as cfg
:
612 cfg
.write("# %s serial support\n" % os
.path
.basename(__file__
))
613 cfg
.write("%s\n" % cmdline
)
614 cfg
.write("%s\n" % terminal
)
615 cfg
.write("%s\n" % command
)
617 def install_grub2(self
, rootdev
, rootdir
):
618 self
.message("Configuring grub2")
619 # rely on kpartx using consistent naming to map loop0p1 to loop0
620 install_dev
= os
.path
.join('/dev', os
.path
.basename(rootdev
)[:-2])
621 self
.runcmd(['mount', '/dev', '-t', 'devfs', '-obind',
622 '%s' % os
.path
.join(rootdir
, 'dev')])
623 self
.runcmd(['mount', '/proc', '-t', 'proc', '-obind',
624 '%s' % os
.path
.join(rootdir
, 'proc')])
625 self
.runcmd(['mount', '/sys', '-t', 'sysfs', '-obind',
626 '%s' % os
.path
.join(rootdir
, 'sys')])
627 if self
.settings
['serial-console']:
628 self
._grub
_serial
_console
(rootdir
)
631 self
.runcmd(['chroot', rootdir
, 'update-grub'])
632 self
.runcmd(['chroot', rootdir
, 'grub-install', install_dev
])
633 except cliapp
.AppException
:
634 self
.message("Failed. Is grub2-common installed? Using extlinux.")
635 self
.install_extlinux(rootdev
, rootdir
)
636 self
.runcmd(['umount', os
.path
.join(rootdir
, 'sys')])
637 self
.runcmd(['umount', os
.path
.join(rootdir
, 'proc')])
638 self
.runcmd(['umount', os
.path
.join(rootdir
, 'dev')])
640 def install_extlinux(self
, rootdev
, rootdir
):
641 if not os
.path
.exists("/usr/bin/extlinux"):
642 self
.message("extlinux not installed, skipping.")
644 self
.message('Installing extlinux')
647 dirname
= os
.path
.join(rootdir
, 'boot')
648 basenames
= os
.listdir(dirname
)
649 logging
.debug('find: %s', basenames
)
650 for basename
in basenames
:
651 if re
.search(pattern
, basename
):
652 return os
.path
.join('boot', basename
)
653 raise cliapp
.AppException('Cannot find match: %s' % pattern
)
656 kernel_image
= find('vmlinuz-.*')
657 initrd_image
= find('initrd.img-.*')
658 except cliapp
.AppException
as e
:
659 self
.message("Unable to find kernel. Not installing extlinux.")
660 logging
.debug("No kernel found. %s. Skipping install of extlinux.", e
)
663 out
= self
.runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
664 '-s', 'UUID', rootdev
])
665 uuid
= out
.splitlines()[0].strip()
667 conf
= os
.path
.join(rootdir
, 'extlinux.conf')
668 logging
.debug('configure extlinux %s', conf
)
669 kserial
= 'console=ttyS0,115200' if self
.settings
['serial-console'] else ''
670 extserial
= 'serial 0 115200' if self
.settings
['serial-console'] else ''
677 append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s
680 'kernel': kernel_image
, # pylint: disable=bad-continuation
681 'initrd': initrd_image
, # pylint: disable=bad-continuation
682 'uuid': uuid
, # pylint: disable=bad-continuation
683 'kserial': kserial
, # pylint: disable=bad-continuation
684 'extserial': extserial
, # pylint: disable=bad-continuation
685 } # pylint: disable=bad-continuation
686 logging
.debug("extlinux config:\n%s", msg
)
688 # python multiline string substitution is just ugly.
689 # use an external file or live with the mangling, no point in
690 # mangling the string to remove spaces just to keep it pretty in source.
694 self
.runcmd(['extlinux', '--install', rootdir
])
695 self
.runcmd(['sync'])
698 def optimize_image(self
, rootdir
):
700 Filing up the image with zeros will increase its compression rate
702 if not self
.settings
['sparse']:
703 zeros
= os
.path
.join(rootdir
, 'ZEROS')
704 self
.runcmd_unchecked(['dd', 'if=/dev/zero', 'of=' + zeros
, 'bs=1M'])
705 self
.runcmd(['rm', '-f', zeros
])
709 Run squashfs on the image.
711 if not os
.path
.exists('/usr/bin/mksquashfs'):
712 logging
.warning("Squash selected but mksquashfs not found!")
714 self
.message("Running mksquashfs")
715 suffixed
= "%s.squashfs" % self
.settings
['image']
716 self
.runcmd(['mksquashfs', self
.settings
['image'],
718 '-no-progress', '-comp', 'xz'], ignore_fail
=False)
719 os
.unlink(self
.settings
['image'])
720 self
.settings
['image'] = suffixed
722 def cleanup_system(self
):
723 # Clean up after any errors.
725 self
.message('Cleaning up')
727 # Umount in the reverse mount order
728 if self
.settings
['image']:
729 for i
in range(len(self
.mount_points
) - 1, -1, -1):
730 mount_point
= self
.mount_points
[i
]
732 self
.runcmd(['umount', mount_point
], ignore_fail
=False)
733 except cliapp
.AppException
:
734 logging
.debug("umount failed, sleeping and trying again")
736 self
.runcmd(['umount', mount_point
], ignore_fail
=False)
738 self
.runcmd(['kpartx', '-d', self
.settings
['image']], ignore_fail
=True)
740 for dirname
in self
.remove_dirs
:
741 shutil
.rmtree(dirname
)
743 def customize(self
, rootdir
):
744 script
= self
.settings
['customize']
747 if not os
.path
.exists(script
):
748 example
= os
.path
.join("/usr/share/vmdebootstrap/examples/", script
)
749 if not os
.path
.exists(example
):
750 self
.message("Unable to find %s" % script
)
753 self
.message('Running customize script %s' % script
)
754 logging
.info("rootdir=%s image=%s", rootdir
, self
.settings
['image'])
755 with
open('/dev/tty', 'w') as tty
:
757 cliapp
.runcmd([script
, rootdir
, self
.settings
['image']], stdout
=tty
, stderr
=tty
)
759 subprocess
.call([script
, rootdir
, self
.settings
['image']])
761 def create_tarball(self
, rootdir
):
762 # Create a tarball of the disk's contents
763 # shell out to runcmd since it more easily handles rootdir
764 self
.message('Creating tarball of disk contents')
765 self
.runcmd(['tar', '-cf', self
.settings
['tarball'], '-C', rootdir
, '.'])
768 # Change image owner after completed build
769 if self
.settings
['image']:
770 filename
= self
.settings
['image']
771 elif self
.settings
['tarball']:
772 filename
= self
.settings
['tarball']
775 self
.message("Changing owner to %s" % self
.settings
["owner"])
776 subprocess
.call(["chown", self
.settings
["owner"], filename
])
778 def list_installed_pkgs(self
, rootdir
):
779 # output the list of installed packages for sources identification
780 self
.message("Creating a list of installed binary package names")
781 out
= self
.runcmd(['chroot', rootdir
,
782 'dpkg-query', '-W', "-f='${Package}.deb\n'"])
783 with
open('dpkg.list', 'w') as dpkg
:
786 def configure_apt(self
, rootdir
):
787 # use the distribution and mirror to create an apt source
788 self
.message("Configuring apt to use distribution and mirror")
789 conf
= os
.path
.join(rootdir
, 'etc', 'apt', 'sources.list.d', 'base.list')
790 logging
.debug('configure apt %s', conf
)
791 mirror
= self
.settings
['mirror']
792 if self
.settings
['apt-mirror']:
793 mirror
= self
.settings
['apt-mirror']
794 self
.message("Setting apt mirror to %s" % mirror
)
795 os
.unlink(os
.path
.join(rootdir
, 'etc', 'apt', 'sources.list'))
797 line
= 'deb %s %s main\n' % (mirror
, self
.settings
['distribution'])
799 line
= '#deb-src %s %s main\n' % (mirror
, self
.settings
['distribution'])
802 # ensure the apt sources have valid lists
803 self
.runcmd(['chroot', rootdir
, 'apt-get', '-qq', 'update'])
805 if __name__
== '__main__':
806 VmDebootstrap(version
=__version__
).run()