]>
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/>.
29 from distro_info
import DebianDistroInfo
, UbuntuDistroInfo
34 # pylint: disable=invalid-name,line-too-long,missing-docstring,too-many-branches
37 class VmDebootstrap(cliapp
.Application
): # pylint: disable=too-many-public-methods
39 def __init__(self
, progname
=None, version
=__version__
, description
=None, epilog
=None):
40 super(VmDebootstrap
, self
).__init
__(progname
, version
, description
, epilog
)
42 self
.mount_points
= []
43 self
.debian_info
= DebianDistroInfo()
44 self
.ubuntu_info
= UbuntuDistroInfo()
46 def add_settings(self
):
47 default_arch
= subprocess
.check_output(
48 ["dpkg", "--print-architecture"]).strip()
50 self
.settings
.boolean(
51 ['verbose'], 'report what is going on')
53 ['image'], 'put created disk image in FILE',
55 self
.settings
.bytesize(
57 'create a disk image of size SIZE (%default)',
60 self
.settings
.bytesize(
62 'create boot partition of size SIZE (%default)',
67 'specify file system type for /boot/',
69 self
.settings
.bytesize(
71 'Space to leave at start of the image for bootloader',
75 'Partition type to use for this image',
79 'specify file system type for /',
81 self
.settings
.bytesize(
83 'create swap space of size SIZE (min 256Mb)')
86 'set up foreign debootstrap environment using provided program (ie binfmt handler)')
89 'select debootstrap variant it not using the default')
90 self
.settings
.boolean(
96 "tar up the disk's contents in FILE",
100 'configure apt to use MIRROR',
102 self
.settings
.string(
104 'use MIRROR as package source (%default)',
106 default
='http://http.debian.net/debian/')
107 self
.settings
.string(
109 'architecture to use (%default)',
111 default
=default_arch
)
112 self
.settings
.string(
114 'release to use (%default)',
117 self
.settings
.string_list(
119 'install PACKAGE onto system')
120 self
.settings
.string_list(
122 'install package in DEB file onto system (not from mirror)',
124 self
.settings
.boolean(
126 'do not install a linux package')
127 self
.settings
.string(
129 'install PACKAGE instead of the default kernel package',
131 self
.settings
.boolean(
133 'enable DHCP on eth0')
134 self
.settings
.string(
138 self
.settings
.boolean(
139 ['lock-root-password'],
140 'lock root account so they cannot login?')
141 self
.settings
.string(
143 'run SCRIPT after setting up system',
145 self
.settings
.string(
147 'set name to HOSTNAME (%default)',
150 self
.settings
.string_list(
152 'create USER with PASSWORD',
153 metavar
='USER/PASSWORD')
154 self
.settings
.boolean(
156 'configure image to use a serial console')
157 self
.settings
.string(
158 ['serial-console-command'],
159 'command to manage the serial console, appended to /etc/inittab (%default)',
161 default
='/sbin/getty -L ttyS0 115200 vt100')
162 self
.settings
.boolean(
164 'install sudo, and if user is created, add them to sudo group')
165 self
.settings
.string(
167 'the user who will own the image when the build is complete.')
168 self
.settings
.boolean(
170 'use squashfs on the final image.')
171 self
.settings
.boolean(
173 'Create an apt source based on the distribution and mirror selected.')
174 self
.settings
.boolean(
176 'Run install-mbr (default if extlinux used)')
177 self
.settings
.boolean(
179 'Install and configure grub2 - disables extlinux.')
180 self
.settings
.boolean(
182 'Do not fill the image with zeros to keep a sparse disk image',
184 self
.settings
.boolean(
186 'Create a list of package names included in the image.')
187 self
.settings
.boolean(
189 'do not install the acpid package',
192 def process_args(self
, args
): # pylint: disable=too-many-branches,too-many-statements
193 if not self
.settings
['image'] and not self
.settings
['tarball']:
194 raise cliapp
.AppException(
195 'You must give disk image filename, or tarball filename')
196 if self
.settings
['image'] and not self
.settings
['size']:
197 raise cliapp
.AppException(
198 'If disk image is specified, you must give image size.')
199 if not self
.debian_info
.valid(self
.settings
['distribution']):
200 if not self
.ubuntu_info
.valid(self
.settings
['distribution']):
201 raise cliapp
.AppException(
202 '%s is not a valid Debian or Ubuntu suite or codename.'
203 % self
.settings
['distribution'])
207 roottype
= self
.settings
['roottype']
210 if self
.settings
['image']:
211 self
.create_empty_image()
212 self
.partition_image()
213 if self
.settings
['mbr'] or self
.settings
['extlinux']:
215 (rootdev
, bootdev
, swapdev
) = self
.setup_kpartx()
216 if self
.settings
['swap'] > 0:
217 self
.message("Creating swap space")
218 self
.runcmd(['mkswap', swapdev
])
219 self
.mkfs(rootdev
, fstype
=roottype
)
220 rootdir
= self
.mount(rootdev
)
222 if self
.settings
['boottype']:
223 boottype
= self
.settings
['boottype']
226 self
.mkfs(bootdev
, fstype
=boottype
)
227 bootdir
= '%s/%s' % (rootdir
, 'boot/')
229 self
.mount(bootdev
, bootdir
)
231 rootdir
= self
.mkdtemp()
232 self
.debootstrap(rootdir
)
233 self
.set_hostname(rootdir
)
234 self
.create_fstab(rootdir
, rootdev
, roottype
, bootdev
, boottype
)
235 self
.install_debs(rootdir
)
236 self
.cleanup_apt_cache(rootdir
)
237 self
.set_root_password(rootdir
)
238 self
.create_users(rootdir
)
239 self
.remove_udev_persistent_rules(rootdir
)
240 self
.setup_networking(rootdir
)
241 if self
.settings
['configure-apt'] or self
.settings
['apt-mirror']:
242 self
.configure_apt(rootdir
)
243 self
.customize(rootdir
)
244 self
.update_initramfs(rootdir
)
246 if self
.settings
['image']:
247 if self
.settings
['grub']:
248 self
.install_grub2(rootdev
, rootdir
)
249 elif self
.settings
['extlinux']:
250 self
.install_extlinux(rootdev
, rootdir
)
251 self
.append_serial_console(rootdir
)
252 self
.optimize_image(rootdir
)
253 if self
.settings
['squash']:
255 if self
.settings
['pkglist']:
256 self
.list_installed_pkgs(rootdir
)
258 if self
.settings
['foreign']:
259 os
.unlink('%s/usr/bin/%s' %
260 (rootdir
, os
.path
.basename(self
.settings
['foreign'])))
262 if self
.settings
['tarball']:
263 self
.create_tarball(rootdir
)
265 if self
.settings
['owner']:
267 except BaseException
as e
:
268 self
.message('EEEK! Something bad happened...')
270 db_log
= os
.path
.join(rootdir
, 'debootstrap', 'debootstrap.log')
271 if os
.path
.exists(db_log
):
272 shutil
.copy(db_log
, os
.getcwd())
274 self
.cleanup_system()
277 self
.cleanup_system()
279 def message(self
, msg
):
281 if self
.settings
['verbose']:
284 def runcmd(self
, argv
, stdin
='', ignore_fail
=False, env
=None, **kwargs
):
285 logging
.debug('runcmd: %s %s %s', argv
, env
, kwargs
)
286 p
= subprocess
.Popen(argv
, stdin
=subprocess
.PIPE
,
287 stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
289 out
, err
= p
.communicate(stdin
)
290 if p
.returncode
!= 0:
291 msg
= 'command failed: %s\n%s\n%s' % (argv
, out
, err
)
294 raise cliapp
.AppException(msg
)
298 dirname
= tempfile
.mkdtemp()
299 self
.remove_dirs
.append(dirname
)
300 logging
.debug('mkdir %s', dirname
)
303 def mount(self
, device
, path
=None):
305 mount_point
= self
.mkdtemp()
308 self
.message('Mounting %s on %s' % (device
, mount_point
))
309 self
.runcmd(['mount', device
, mount_point
])
310 self
.mount_points
.append(mount_point
)
311 logging
.debug('mounted %s on %s', device
, mount_point
)
314 def create_empty_image(self
):
315 self
.message('Creating disk image')
316 self
.runcmd(['qemu-img', 'create', '-f', 'raw',
317 self
.settings
['image'],
318 str(self
.settings
['size'])])
320 def partition_image(self
):
322 Uses fat16 (msdos) partitioning by default, use part-type to change.
323 If bootoffset is specified, the first actual partition
324 starts at that offset to allow customisation scripts to
325 put bootloader images into the space, e.g. u-boot.
327 self
.message('Creating partitions')
328 self
.runcmd(['parted', '-s', self
.settings
['image'],
329 'mklabel', self
.settings
['part-type']])
333 swap
= 256 * 1024 * 1024
334 if self
.settings
['swap'] > 0:
335 if self
.settings
['swap'] > swap
:
336 swap
= self
.settings
['swap']
338 # minimum 256Mb as default qemu ram is 128Mb
339 logging
.debug("Setting minimum 256Mb swap space")
340 extent
= "%s%%" % int(100 * (self
.settings
['size'] - swap
) / self
.settings
['size'])
341 if self
.settings
['bootoffset'] and self
.settings
['bootoffset'] is not '0':
342 # turn v.small offsets into something at least possible to create.
343 if self
.settings
['bootoffset'] < 1048576:
346 "Setting bootoffset %smib to allow for %s bytes",
347 partoffset
, self
.settings
['bootoffset'])
349 partoffset
= self
.settings
['bootoffset'] / (1024 * 1024)
350 self
.message("Using bootoffset: %smib %s bytes" % (partoffset
, self
.settings
['bootoffset']))
351 if self
.settings
['bootsize'] and self
.settings
['bootsize'] is not '0%':
352 if self
.settings
['grub'] and not partoffset
:
354 bootsize
= self
.settings
['bootsize'] / (1024 * 1024)
355 bootsize
+= partoffset
356 self
.message("Using bootsize %smib: %s bytes" % (bootsize
, self
.settings
['bootsize']))
357 logging
.debug("Starting boot partition at %sMb", bootsize
)
358 self
.runcmd(['parted', '-s', self
.settings
['image'],
359 'mkpart', 'primary', 'fat16', str(partoffset
), str(bootsize
)])
360 logging
.debug("Starting root partition at %sMb", partoffset
)
361 self
.runcmd(['parted', '-s', self
.settings
['image'],
362 'mkpart', 'primary', str(bootsize
), extent
])
364 self
.runcmd(['parted', '-s', self
.settings
['image'],
365 'mkpart', 'primary', '0%', extent
])
366 self
.runcmd(['parted', '-s', self
.settings
['image'],
367 'set', '1', 'boot', 'on'])
368 if self
.settings
['swap'] > 0:
369 logging
.debug("Creating swap partition")
370 self
.runcmd(['parted', '-s', self
.settings
['image'],
371 'mkpart', 'primary', 'linux-swap', extent
, '100%'])
373 def update_initramfs(self
, rootdir
):
374 cmd
= os
.path
.join('usr', 'sbin', 'update-initramfs')
375 if os
.path
.exists(os
.path
.join(rootdir
, cmd
)):
376 self
.message("Updating the initramfs")
377 self
.runcmd(['chroot', rootdir
, cmd
, '-u'])
379 def install_mbr(self
):
380 if os
.path
.exists("/sbin/install-mbr"):
381 self
.message('Installing MBR')
382 self
.runcmd(['install-mbr', self
.settings
['image']])
384 msg
= "mbr enabled but /sbin/install-mbr not found" \
385 " - please install the mbr package."
386 raise cliapp
.AppException(msg
)
388 def setup_kpartx(self
):
391 out
= self
.runcmd(['kpartx', '-avs', self
.settings
['image']])
392 if self
.settings
['bootsize'] and self
.settings
['swap'] > 0:
397 elif self
.settings
['bootsize']:
401 elif self
.settings
['swap'] > 0:
410 devices
= [line
.split()[2]
411 for line
in out
.splitlines()
412 if line
.startswith('add map ')]
413 if len(devices
) != parts
:
414 msg
= 'Surprising number of partitions - check output of losetup -a'
415 logging
.debug("%s", self
.runcmd(['losetup', '-a']))
416 logging
.debug("%s: devices=%s parts=%s", msg
, devices
, parts
)
417 raise cliapp
.AppException(msg
)
418 root
= '/dev/mapper/%s' % devices
[rootindex
]
419 if self
.settings
['bootsize']:
420 boot
= '/dev/mapper/%s' % devices
[bootindex
]
421 if self
.settings
['swap'] > 0:
422 swap
= '/dev/mapper/%s' % devices
[swapindex
]
423 return root
, boot
, swap
425 def mkfs(self
, device
, fstype
):
426 self
.message('Creating filesystem %s' % fstype
)
427 self
.runcmd(['mkfs', '-t', fstype
, device
])
429 def suite_to_codename(self
, distro
):
430 suite
= self
.debian_info
.codename(distro
, datetime
.date
.today())
435 def was_oldstable(self
, limit
):
436 suite
= self
.suite_to_codename(self
.settings
['distribution'])
437 # this check is only for debian
438 if not self
.debian_info
.valid(suite
):
440 return suite
== self
.debian_info
.old(limit
)
442 def was_stable(self
, limit
):
443 suite
= self
.suite_to_codename(self
.settings
['distribution'])
444 # this check is only for debian
445 if not self
.debian_info
.valid(suite
):
447 return suite
== self
.debian_info
.stable(limit
)
449 def debootstrap(self
, rootdir
):
450 msg
= "(%s)" % self
.settings
['variant'] if self
.settings
['variant'] else ''
451 self
.message('Debootstrapping %s %s' % (self
.settings
['distribution'], msg
))
453 include
= self
.settings
['package']
455 if not self
.settings
['foreign'] and not self
.settings
['no-acpid']:
456 include
.append('acpid')
458 if self
.settings
['grub']:
459 include
.append('grub-pc')
461 if not self
.settings
['no-kernel']:
462 if self
.settings
['kernel-package']:
463 kernel_image
= self
.settings
['kernel-package']
465 if self
.settings
['arch'] == 'i386':
466 # wheezy (which became oldstable on 04/25/2015) used '486'
467 if self
.was_oldstable(datetime
.date(2015, 4, 26)):
471 elif self
.settings
['arch'] == 'armhf':
472 kernel_arch
= 'armmp'
474 kernel_arch
= self
.settings
['arch']
475 kernel_image
= 'linux-image-%s' % kernel_arch
476 include
.append(kernel_image
)
478 if self
.settings
['sudo'] and 'sudo' not in include
:
479 include
.append('sudo')
481 args
= ['debootstrap', '--arch=%s' % self
.settings
['arch']]
483 if self
.settings
['package']:
485 '--include=%s' % ','.join(include
))
486 if self
.settings
['foreign']:
487 args
.append('--foreign')
488 if self
.settings
['variant']:
489 args
.append('--variant')
490 args
.append(self
.settings
['variant'])
491 args
+= [self
.settings
['distribution'],
492 rootdir
, self
.settings
['mirror']]
493 logging
.debug(" ".join(args
))
495 if self
.settings
['foreign']:
496 # set a noninteractive debconf environment for secondstage
498 "DEBIAN_FRONTEND": "noninteractive",
499 "DEBCONF_NONINTERACTIVE_SEEN": "true",
502 # add the mapping to the complete environment.
503 env
.update(os
.environ
)
504 # First copy the binfmt handler over
505 self
.message('Setting up binfmt handler')
506 shutil
.copy(self
.settings
['foreign'], '%s/usr/bin/' % rootdir
)
507 # Next, run the package install scripts etc.
508 self
.message('Running debootstrap second stage')
509 self
.runcmd(['chroot', rootdir
,
510 '/debootstrap/debootstrap', '--second-stage'],
513 def set_hostname(self
, rootdir
):
514 hostname
= self
.settings
['hostname']
515 with
open(os
.path
.join(rootdir
, 'etc', 'hostname'), 'w') as f
:
516 f
.write('%s\n' % hostname
)
518 etc_hosts
= os
.path
.join(rootdir
, 'etc', 'hosts')
520 with
open(etc_hosts
, 'r') as f
:
522 with
open(etc_hosts
, 'w') as f
:
523 for line
in data
.splitlines():
524 if line
.startswith('127.0.0.1'):
525 line
+= ' %s' % hostname
526 f
.write('%s\n' % line
)
530 def create_fstab(self
, rootdir
, rootdev
, roottype
, bootdev
, boottype
): # pylint: disable=too-many-arguments
532 out
= self
.runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
533 '-s', 'UUID', device
])
534 return out
.splitlines()[0].strip()
537 rootdevstr
= 'UUID=%s' % fsuuid(rootdev
)
539 rootdevstr
= '/dev/sda1'
542 bootdevstr
= 'UUID=%s' % fsuuid(bootdev
)
546 fstab
= os
.path
.join(rootdir
, 'etc', 'fstab')
547 with
open(fstab
, 'w') as f
:
548 f
.write('proc /proc proc defaults 0 0\n')
549 f
.write('%s / %s errors=remount-ro 0 1\n' % (rootdevstr
, roottype
))
551 f
.write('%s /boot %s errors=remount-ro 0 2\n' % (bootdevstr
, boottype
))
552 if self
.settings
['swap'] > 0:
553 f
.write("/dev/sda3 swap swap defaults 0 0\n")
554 elif self
.settings
['swap'] > 0:
555 f
.write("/dev/sda2 swap swap defaults 0 0\n")
557 def install_debs(self
, rootdir
):
558 if not self
.settings
['custom-package']:
560 self
.message('Installing custom packages')
561 tmp
= os
.path
.join(rootdir
, 'tmp', 'install_debs')
563 for deb
in self
.settings
['custom-package']:
564 shutil
.copy(deb
, tmp
)
565 filenames
= [os
.path
.join('/tmp/install_debs', os
.path
.basename(deb
))
566 for deb
in self
.settings
['custom-package']]
568 self
.runcmd_unchecked(['chroot', rootdir
, 'dpkg', '-i'] + filenames
)
569 logging
.debug('stdout:\n%s', out
)
570 logging
.debug('stderr:\n%s', err
)
571 out
= self
.runcmd(['chroot', rootdir
,
572 'apt-get', '-f', '--no-remove', 'install'])
573 logging
.debug('stdout:\n%s', out
)
576 def cleanup_apt_cache(self
, rootdir
):
577 out
= self
.runcmd(['chroot', rootdir
, 'apt-get', 'clean'])
578 logging
.debug('stdout:\n%s', out
)
580 def set_root_password(self
, rootdir
):
581 if self
.settings
['root-password']:
582 self
.message('Setting root password')
583 self
.set_password(rootdir
, 'root', self
.settings
['root-password'])
584 elif self
.settings
['lock-root-password']:
585 self
.message('Locking root password')
586 self
.runcmd(['chroot', rootdir
, 'passwd', '-l', 'root'])
588 self
.message('Give root an empty password')
589 self
.delete_password(rootdir
, 'root')
591 def create_users(self
, rootdir
):
592 def create_user(user
):
593 self
.runcmd(['chroot', rootdir
, 'adduser', '--gecos', user
,
594 '--disabled-password', user
])
595 if self
.settings
['sudo']:
596 self
.runcmd(['chroot', rootdir
, 'adduser', user
, 'sudo'])
598 for userpass
in self
.settings
['user']:
600 user
, password
= userpass
.split('/', 1)
602 self
.set_password(rootdir
, user
, password
)
604 create_user(userpass
)
605 self
.delete_password(rootdir
, userpass
)
607 def set_password(self
, rootdir
, user
, password
):
608 encrypted
= crypt
.crypt(password
, '..')
609 self
.runcmd(['chroot', rootdir
, 'usermod', '-p', encrypted
, user
])
611 def delete_password(self
, rootdir
, user
):
612 self
.runcmd(['chroot', rootdir
, 'passwd', '-d', user
])
614 def remove_udev_persistent_rules(self
, rootdir
):
615 self
.message('Removing udev persistent cd and net rules')
616 for x
in ['70-persistent-cd.rules', '70-persistent-net.rules']:
617 pathname
= os
.path
.join(rootdir
, 'etc', 'udev', 'rules.d', x
)
618 if os
.path
.exists(pathname
):
619 logging
.debug('rm %s', pathname
)
622 logging
.debug('not removing non-existent %s', pathname
)
624 def setup_networking(self
, rootdir
):
625 self
.message('Setting up networking')
627 f
= open(os
.path
.join(rootdir
, 'etc', 'network', 'interfaces'), 'w')
629 f
.write('iface lo inet loopback\n')
631 if self
.settings
['enable-dhcp']:
633 f
.write('auto eth0\n')
634 f
.write('iface eth0 inet dhcp\n')
638 def append_serial_console(self
, rootdir
):
639 if self
.settings
['serial-console']:
640 serial_command
= self
.settings
['serial-console-command']
641 logging
.debug('adding getty to serial console')
642 inittab
= os
.path
.join(rootdir
, 'etc/inittab')
643 # to autologin, serial_command can contain '-a root'
644 with
open(inittab
, 'a') as f
:
645 f
.write('\nS0:23:respawn:%s\n' % serial_command
)
647 # pylint: disable=no-self-use
648 def _grub_serial_console(self
, rootdir
):
649 cmdline
= 'GRUB_CMDLINE_LINUX_DEFAULT="console=tty0 console=tty1 console=ttyS0,38400n8"'
650 terminal
= 'GRUB_TERMINAL="serial gfxterm"'
651 command
= 'GRUB_SERIAL_COMMAND="serial --speed=38400 --unit=0 --parity=no --stop=1"'
652 grub_cfg
= os
.path
.join(rootdir
, 'etc', 'default', 'grub')
653 logging
.debug("Allowing serial output in grub config %s", grub_cfg
)
654 with
open(grub_cfg
, 'a+') as cfg
:
655 cfg
.write("# %s serial support\n" % os
.path
.basename(__file__
))
656 cfg
.write("%s\n" % cmdline
)
657 cfg
.write("%s\n" % terminal
)
658 cfg
.write("%s\n" % command
)
660 def install_grub2(self
, rootdev
, rootdir
):
661 self
.message("Configuring grub2")
662 # rely on kpartx using consistent naming to map loop0p1 to loop0
663 install_dev
= os
.path
.join('/dev', os
.path
.basename(rootdev
)[:-2])
664 self
.runcmd(['mount', '/dev', '-t', 'devfs', '-obind',
665 '%s' % os
.path
.join(rootdir
, 'dev')])
666 self
.runcmd(['mount', '/proc', '-t', 'proc', '-obind',
667 '%s' % os
.path
.join(rootdir
, 'proc')])
668 self
.runcmd(['mount', '/sys', '-t', 'sysfs', '-obind',
669 '%s' % os
.path
.join(rootdir
, 'sys')])
670 if self
.settings
['serial-console']:
671 self
._grub
_serial
_console
(rootdir
)
674 self
.runcmd(['chroot', rootdir
, 'update-grub'])
675 self
.runcmd(['chroot', rootdir
, 'grub-install', install_dev
])
676 except cliapp
.AppException
:
677 self
.message("Failed. Is grub2-common installed? Using extlinux.")
678 self
.install_extlinux(rootdev
, rootdir
)
679 self
.runcmd(['umount', os
.path
.join(rootdir
, 'sys')])
680 self
.runcmd(['umount', os
.path
.join(rootdir
, 'proc')])
681 self
.runcmd(['umount', os
.path
.join(rootdir
, 'dev')])
683 def install_extlinux(self
, rootdev
, rootdir
):
684 if not os
.path
.exists("/usr/bin/extlinux"):
685 self
.message("extlinux not installed, skipping.")
687 self
.message('Installing extlinux')
690 dirname
= os
.path
.join(rootdir
, 'boot')
691 basenames
= os
.listdir(dirname
)
692 logging
.debug('find: %s', basenames
)
693 for basename
in basenames
:
694 if re
.search(pattern
, basename
):
695 return os
.path
.join('boot', basename
)
696 raise cliapp
.AppException('Cannot find match: %s' % pattern
)
699 kernel_image
= find('vmlinuz-.*')
700 initrd_image
= find('initrd.img-.*')
701 except cliapp
.AppException
as e
:
702 self
.message("Unable to find kernel. Not installing extlinux.")
703 logging
.debug("No kernel found. %s. Skipping install of extlinux.", e
)
706 out
= self
.runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
707 '-s', 'UUID', rootdev
])
708 uuid
= out
.splitlines()[0].strip()
710 conf
= os
.path
.join(rootdir
, 'extlinux.conf')
711 logging
.debug('configure extlinux %s', conf
)
712 kserial
= 'console=ttyS0,115200' if self
.settings
['serial-console'] else ''
713 extserial
= 'serial 0 115200' if self
.settings
['serial-console'] else ''
720 append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s
723 'kernel': kernel_image
, # pylint: disable=bad-continuation
724 'initrd': initrd_image
, # pylint: disable=bad-continuation
725 'uuid': uuid
, # pylint: disable=bad-continuation
726 'kserial': kserial
, # pylint: disable=bad-continuation
727 'extserial': extserial
, # pylint: disable=bad-continuation
728 } # pylint: disable=bad-continuation
729 logging
.debug("extlinux config:\n%s", msg
)
731 # python multiline string substitution is just ugly.
732 # use an external file or live with the mangling, no point in
733 # mangling the string to remove spaces just to keep it pretty in source.
737 self
.runcmd(['extlinux', '--install', rootdir
])
738 self
.runcmd(['sync'])
741 def optimize_image(self
, rootdir
):
743 Filing up the image with zeros will increase its compression rate
745 if not self
.settings
['sparse']:
746 zeros
= os
.path
.join(rootdir
, 'ZEROS')
747 self
.runcmd_unchecked(['dd', 'if=/dev/zero', 'of=' + zeros
, 'bs=1M'])
748 self
.runcmd(['rm', '-f', zeros
])
752 Run squashfs on the image.
754 if not os
.path
.exists('/usr/bin/mksquashfs'):
755 logging
.warning("Squash selected but mksquashfs not found!")
757 self
.message("Running mksquashfs")
758 suffixed
= "%s.squashfs" % self
.settings
['image']
759 self
.runcmd(['mksquashfs', self
.settings
['image'],
761 '-no-progress', '-comp', 'xz'], ignore_fail
=False)
762 os
.unlink(self
.settings
['image'])
763 self
.settings
['image'] = suffixed
765 def cleanup_system(self
):
766 # Clean up after any errors.
768 self
.message('Cleaning up')
770 # Umount in the reverse mount order
771 if self
.settings
['image']:
772 for i
in range(len(self
.mount_points
) - 1, -1, -1):
773 mount_point
= self
.mount_points
[i
]
775 self
.runcmd(['umount', mount_point
], ignore_fail
=False)
776 except cliapp
.AppException
:
777 logging
.debug("umount failed, sleeping and trying again")
779 self
.runcmd(['umount', mount_point
], ignore_fail
=False)
781 self
.runcmd(['kpartx', '-d', self
.settings
['image']], ignore_fail
=True)
783 for dirname
in self
.remove_dirs
:
784 shutil
.rmtree(dirname
)
786 def customize(self
, rootdir
):
787 script
= self
.settings
['customize']
790 if not os
.path
.exists(script
):
791 example
= os
.path
.join("/usr/share/vmdebootstrap/examples/", script
)
792 if not os
.path
.exists(example
):
793 self
.message("Unable to find %s" % script
)
796 self
.message('Running customize script %s' % script
)
797 logging
.info("rootdir=%s image=%s", rootdir
, self
.settings
['image'])
798 with
open('/dev/tty', 'w') as tty
:
800 cliapp
.runcmd([script
, rootdir
, self
.settings
['image']], stdout
=tty
, stderr
=tty
)
802 subprocess
.call([script
, rootdir
, self
.settings
['image']])
804 def create_tarball(self
, rootdir
):
805 # Create a tarball of the disk's contents
806 # shell out to runcmd since it more easily handles rootdir
807 self
.message('Creating tarball of disk contents')
808 self
.runcmd(['tar', '-cf', self
.settings
['tarball'], '-C', rootdir
, '.'])
811 # Change image owner after completed build
812 if self
.settings
['image']:
813 filename
= self
.settings
['image']
814 elif self
.settings
['tarball']:
815 filename
= self
.settings
['tarball']
818 self
.message("Changing owner to %s" % self
.settings
["owner"])
819 subprocess
.call(["chown", self
.settings
["owner"], filename
])
821 def list_installed_pkgs(self
, rootdir
):
822 # output the list of installed packages for sources identification
823 self
.message("Creating a list of installed binary package names")
824 out
= self
.runcmd(['chroot', rootdir
,
825 'dpkg-query', '-W', "-f='${Package}.deb\n'"])
826 with
open('dpkg.list', 'w') as dpkg
:
829 def configure_apt(self
, rootdir
):
830 # use the distribution and mirror to create an apt source
831 self
.message("Configuring apt to use distribution and mirror")
832 conf
= os
.path
.join(rootdir
, 'etc', 'apt', 'sources.list.d', 'base.list')
833 logging
.debug('configure apt %s', conf
)
834 mirror
= self
.settings
['mirror']
835 if self
.settings
['apt-mirror']:
836 mirror
= self
.settings
['apt-mirror']
837 self
.message("Setting apt mirror to %s" % mirror
)
838 os
.unlink(os
.path
.join(rootdir
, 'etc', 'apt', 'sources.list'))
840 line
= 'deb %s %s main\n' % (mirror
, self
.settings
['distribution'])
842 line
= '#deb-src %s %s main\n' % (mirror
, self
.settings
['distribution'])
845 # ensure the apt sources have valid lists
846 self
.runcmd(['chroot', rootdir
, 'apt-get', '-qq', 'update'])
848 if __name__
== '__main__':
849 VmDebootstrap(version
=__version__
).run()