]>
git.siccegge.de Git - forks/vmdebootstrap.git/blob - vmdebootstrap
add3d44165bfd3b1ea14a8a52fb79709b787f8c9
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.')
188 def process_args(self
, args
): # pylint: disable=too-many-branches,too-many-statements
189 if not self
.settings
['image'] and not self
.settings
['tarball']:
190 raise cliapp
.AppException(
191 'You must give disk image filename, or tarball filename')
192 if self
.settings
['image'] and not self
.settings
['size']:
193 raise cliapp
.AppException(
194 'If disk image is specified, you must give image size.')
195 if not self
.debian_info
.valid(self
.settings
['distribution']):
196 if not self
.ubuntu_info
.valid(self
.settings
['distribution']):
197 raise cliapp
.AppException(
198 '%s is not a valid Debian or Ubuntu suite or codename.'
199 % self
.settings
['distribution'])
203 roottype
= self
.settings
['roottype']
206 if self
.settings
['image']:
207 self
.create_empty_image()
208 self
.partition_image()
209 if self
.settings
['mbr'] or self
.settings
['extlinux']:
211 (rootdev
, bootdev
, swapdev
) = self
.setup_kpartx()
212 if self
.settings
['swap'] > 0:
213 self
.message("Creating swap space")
214 self
.runcmd(['mkswap', swapdev
])
215 self
.mkfs(rootdev
, fstype
=roottype
)
216 rootdir
= self
.mount(rootdev
)
218 if self
.settings
['boottype']:
219 boottype
= self
.settings
['boottype']
222 self
.mkfs(bootdev
, fstype
=boottype
)
223 bootdir
= '%s/%s' % (rootdir
, 'boot/')
225 self
.mount(bootdev
, bootdir
)
227 rootdir
= self
.mkdtemp()
228 self
.debootstrap(rootdir
)
229 self
.set_hostname(rootdir
)
230 self
.create_fstab(rootdir
, rootdev
, roottype
, bootdev
, boottype
)
231 self
.install_debs(rootdir
)
232 self
.cleanup_apt_cache(rootdir
)
233 self
.set_root_password(rootdir
)
234 self
.create_users(rootdir
)
235 self
.remove_udev_persistent_rules(rootdir
)
236 self
.setup_networking(rootdir
)
237 if self
.settings
['configure-apt'] or self
.settings
['apt-mirror']:
238 self
.configure_apt(rootdir
)
239 self
.customize(rootdir
)
240 self
.update_initramfs(rootdir
)
242 if self
.settings
['image']:
243 if self
.settings
['grub']:
244 self
.install_grub2(rootdev
, rootdir
)
245 elif self
.settings
['extlinux']:
246 self
.install_extlinux(rootdev
, rootdir
)
247 self
.append_serial_console(rootdir
)
248 self
.optimize_image(rootdir
)
249 if self
.settings
['squash']:
251 if self
.settings
['pkglist']:
252 self
.list_installed_pkgs(rootdir
)
254 if self
.settings
['foreign']:
255 os
.unlink('%s/usr/bin/%s' %
256 (rootdir
, os
.path
.basename(self
.settings
['foreign'])))
258 if self
.settings
['tarball']:
259 self
.create_tarball(rootdir
)
261 if self
.settings
['owner']:
263 except BaseException
as e
:
264 self
.message('EEEK! Something bad happened...')
266 db_log
= os
.path
.join(rootdir
, 'debootstrap', 'debootstrap.log')
267 if os
.path
.exists(db_log
):
268 shutil
.copy(db_log
, os
.getcwd())
270 self
.cleanup_system()
273 self
.cleanup_system()
275 def message(self
, msg
):
277 if self
.settings
['verbose']:
280 def runcmd(self
, argv
, stdin
='', ignore_fail
=False, env
=None, **kwargs
):
281 logging
.debug('runcmd: %s %s %s', argv
, env
, kwargs
)
282 p
= subprocess
.Popen(argv
, stdin
=subprocess
.PIPE
,
283 stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
285 out
, err
= p
.communicate(stdin
)
286 if p
.returncode
!= 0:
287 msg
= 'command failed: %s\n%s\n%s' % (argv
, out
, err
)
290 raise cliapp
.AppException(msg
)
294 dirname
= tempfile
.mkdtemp()
295 self
.remove_dirs
.append(dirname
)
296 logging
.debug('mkdir %s', dirname
)
299 def mount(self
, device
, path
=None):
301 mount_point
= self
.mkdtemp()
304 self
.message('Mounting %s on %s' % (device
, mount_point
))
305 self
.runcmd(['mount', device
, mount_point
])
306 self
.mount_points
.append(mount_point
)
307 logging
.debug('mounted %s on %s', device
, mount_point
)
310 def create_empty_image(self
):
311 self
.message('Creating disk image')
312 self
.runcmd(['qemu-img', 'create', '-f', 'raw',
313 self
.settings
['image'],
314 str(self
.settings
['size'])])
316 def partition_image(self
):
318 Uses fat16 (msdos) partitioning by default, use part-type to change.
319 If bootoffset is specified, the first actual partition
320 starts at that offset to allow customisation scripts to
321 put bootloader images into the space, e.g. u-boot.
323 self
.message('Creating partitions')
324 self
.runcmd(['parted', '-s', self
.settings
['image'],
325 'mklabel', self
.settings
['part-type']])
329 swap
= 256 * 1024 * 1024
330 if self
.settings
['swap'] > 0:
331 if self
.settings
['swap'] > swap
:
332 swap
= self
.settings
['swap']
334 # minimum 256Mb as default qemu ram is 128Mb
335 logging
.debug("Setting minimum 256Mb swap space")
336 extent
= "%s%%" % int(100 * (self
.settings
['size'] - swap
) / self
.settings
['size'])
337 if self
.settings
['bootoffset'] and self
.settings
['bootoffset'] is not '0':
338 # turn v.small offsets into something at least possible to create.
339 if self
.settings
['bootoffset'] < 1048576:
342 "Setting bootoffset %smib to allow for %s bytes",
343 partoffset
, self
.settings
['bootoffset'])
345 partoffset
= self
.settings
['bootoffset'] / (1024 * 1024)
346 self
.message("Using bootoffset: %smib %s bytes" % (partoffset
, self
.settings
['bootoffset']))
347 if self
.settings
['bootsize'] and self
.settings
['bootsize'] is not '0%':
348 if self
.settings
['grub'] and not partoffset
:
350 bootsize
= self
.settings
['bootsize'] / (1024 * 1024)
351 bootsize
+= partoffset
352 self
.message("Using bootsize %smib: %s bytes" % (bootsize
, self
.settings
['bootsize']))
353 logging
.debug("Starting boot partition at %sMb", bootsize
)
354 self
.runcmd(['parted', '-s', self
.settings
['image'],
355 'mkpart', 'primary', 'fat16', str(partoffset
), str(bootsize
)])
356 logging
.debug("Starting root partition at %sMb", partoffset
)
357 self
.runcmd(['parted', '-s', self
.settings
['image'],
358 'mkpart', 'primary', str(bootsize
), extent
])
360 self
.runcmd(['parted', '-s', self
.settings
['image'],
361 'mkpart', 'primary', '0%', extent
])
362 self
.runcmd(['parted', '-s', self
.settings
['image'],
363 'set', '1', 'boot', 'on'])
364 if self
.settings
['swap'] > 0:
365 logging
.debug("Creating swap partition")
366 self
.runcmd(['parted', '-s', self
.settings
['image'],
367 'mkpart', 'primary', 'linux-swap', extent
, '100%'])
369 def update_initramfs(self
, rootdir
):
370 cmd
= os
.path
.join('usr', 'sbin', 'update-initramfs')
371 if os
.path
.exists(os
.path
.join(rootdir
, cmd
)):
372 self
.message("Updating the initramfs")
373 self
.runcmd(['chroot', rootdir
, cmd
, '-u'])
375 def install_mbr(self
):
376 if os
.path
.exists("/sbin/install-mbr"):
377 self
.message('Installing MBR')
378 self
.runcmd(['install-mbr', self
.settings
['image']])
380 msg
= "mbr enabled but /sbin/install-mbr not found" \
381 " - please install the mbr package."
382 raise cliapp
.AppException(msg
)
384 def setup_kpartx(self
):
387 out
= self
.runcmd(['kpartx', '-avs', self
.settings
['image']])
388 if self
.settings
['bootsize'] and self
.settings
['swap'] > 0:
393 elif self
.settings
['bootsize']:
397 elif self
.settings
['swap'] > 0:
406 devices
= [line
.split()[2]
407 for line
in out
.splitlines()
408 if line
.startswith('add map ')]
409 if len(devices
) != parts
:
410 msg
= 'Surprising number of partitions - check output of losetup -a'
411 logging
.debug("%s", self
.runcmd(['losetup', '-a']))
412 logging
.debug("%s: devices=%s parts=%s", msg
, devices
, parts
)
413 raise cliapp
.AppException(msg
)
414 root
= '/dev/mapper/%s' % devices
[rootindex
]
415 if self
.settings
['bootsize']:
416 boot
= '/dev/mapper/%s' % devices
[bootindex
]
417 if self
.settings
['swap'] > 0:
418 swap
= '/dev/mapper/%s' % devices
[swapindex
]
419 return root
, boot
, swap
421 def mkfs(self
, device
, fstype
):
422 self
.message('Creating filesystem %s' % fstype
)
423 self
.runcmd(['mkfs', '-t', fstype
, device
])
425 def suite_to_codename(self
, distro
):
426 suite
= self
.debian_info
.codename(distro
, datetime
.date
.today())
431 def was_oldstable(self
, limit
):
432 suite
= self
.suite_to_codename(self
.settings
['distribution'])
433 # this check is only for debian
434 if not self
.debian_info
.valid(suite
):
436 return suite
== self
.debian_info
.old(limit
)
438 def was_stable(self
, limit
):
439 suite
= self
.suite_to_codename(self
.settings
['distribution'])
440 # this check is only for debian
441 if not self
.debian_info
.valid(suite
):
443 return suite
== self
.debian_info
.stable(limit
)
445 def debootstrap(self
, rootdir
):
446 msg
= "(%s)" % self
.settings
['variant'] if self
.settings
['variant'] else ''
447 self
.message('Debootstrapping %s %s' % (self
.settings
['distribution'], msg
))
449 include
= self
.settings
['package']
451 if not self
.settings
['foreign']:
452 include
.append('acpid')
454 if self
.settings
['grub']:
455 include
.append('grub-pc')
457 if not self
.settings
['no-kernel']:
458 if self
.settings
['kernel-package']:
459 kernel_image
= self
.settings
['kernel-package']
461 if self
.settings
['arch'] == 'i386':
462 # wheezy (which became oldstable on 04/25/2015) used '486'
463 if self
.was_oldstable(datetime
.date(2015, 4, 26)):
467 elif self
.settings
['arch'] == 'armhf':
468 kernel_arch
= 'armmp'
470 kernel_arch
= self
.settings
['arch']
471 kernel_image
= 'linux-image-%s' % kernel_arch
472 include
.append(kernel_image
)
474 if self
.settings
['sudo'] and 'sudo' not in include
:
475 include
.append('sudo')
477 args
= ['debootstrap', '--arch=%s' % self
.settings
['arch']]
479 if self
.settings
['package']:
481 '--include=%s' % ','.join(include
))
482 if self
.settings
['foreign']:
483 args
.append('--foreign')
484 if self
.settings
['variant']:
485 args
.append('--variant')
486 args
.append(self
.settings
['variant'])
487 args
+= [self
.settings
['distribution'],
488 rootdir
, self
.settings
['mirror']]
489 logging
.debug(" ".join(args
))
491 if self
.settings
['foreign']:
492 # set a noninteractive debconf environment for secondstage
494 "DEBIAN_FRONTEND": "noninteractive",
495 "DEBCONF_NONINTERACTIVE_SEEN": "true",
498 # add the mapping to the complete environment.
499 env
.update(os
.environ
)
500 # First copy the binfmt handler over
501 self
.message('Setting up binfmt handler')
502 shutil
.copy(self
.settings
['foreign'], '%s/usr/bin/' % rootdir
)
503 # Next, run the package install scripts etc.
504 self
.message('Running debootstrap second stage')
505 self
.runcmd(['chroot', rootdir
,
506 '/debootstrap/debootstrap', '--second-stage'],
509 def set_hostname(self
, rootdir
):
510 hostname
= self
.settings
['hostname']
511 with
open(os
.path
.join(rootdir
, 'etc', 'hostname'), 'w') as f
:
512 f
.write('%s\n' % hostname
)
514 etc_hosts
= os
.path
.join(rootdir
, 'etc', 'hosts')
516 with
open(etc_hosts
, 'r') as f
:
518 with
open(etc_hosts
, 'w') as f
:
519 for line
in data
.splitlines():
520 if line
.startswith('127.0.0.1'):
521 line
+= ' %s' % hostname
522 f
.write('%s\n' % line
)
526 def create_fstab(self
, rootdir
, rootdev
, roottype
, bootdev
, boottype
): # pylint: disable=too-many-arguments
528 out
= self
.runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
529 '-s', 'UUID', device
])
530 return out
.splitlines()[0].strip()
533 rootdevstr
= 'UUID=%s' % fsuuid(rootdev
)
535 rootdevstr
= '/dev/sda1'
538 bootdevstr
= 'UUID=%s' % fsuuid(bootdev
)
542 fstab
= os
.path
.join(rootdir
, 'etc', 'fstab')
543 with
open(fstab
, 'w') as f
:
544 f
.write('proc /proc proc defaults 0 0\n')
545 f
.write('%s / %s errors=remount-ro 0 1\n' % (rootdevstr
, roottype
))
547 f
.write('%s /boot %s errors=remount-ro 0 2\n' % (bootdevstr
, boottype
))
548 if self
.settings
['swap'] > 0:
549 f
.write("/dev/sda3 swap swap defaults 0 0\n")
550 elif self
.settings
['swap'] > 0:
551 f
.write("/dev/sda2 swap swap defaults 0 0\n")
553 def install_debs(self
, rootdir
):
554 if not self
.settings
['custom-package']:
556 self
.message('Installing custom packages')
557 tmp
= os
.path
.join(rootdir
, 'tmp', 'install_debs')
559 for deb
in self
.settings
['custom-package']:
560 shutil
.copy(deb
, tmp
)
561 filenames
= [os
.path
.join('/tmp/install_debs', os
.path
.basename(deb
))
562 for deb
in self
.settings
['custom-package']]
564 self
.runcmd_unchecked(['chroot', rootdir
, 'dpkg', '-i'] + filenames
)
565 logging
.debug('stdout:\n%s', out
)
566 logging
.debug('stderr:\n%s', err
)
567 out
= self
.runcmd(['chroot', rootdir
,
568 'apt-get', '-f', '--no-remove', 'install'])
569 logging
.debug('stdout:\n%s', out
)
572 def cleanup_apt_cache(self
, rootdir
):
573 out
= self
.runcmd(['chroot', rootdir
, 'apt-get', 'clean'])
574 logging
.debug('stdout:\n%s', out
)
576 def set_root_password(self
, rootdir
):
577 if self
.settings
['root-password']:
578 self
.message('Setting root password')
579 self
.set_password(rootdir
, 'root', self
.settings
['root-password'])
580 elif self
.settings
['lock-root-password']:
581 self
.message('Locking root password')
582 self
.runcmd(['chroot', rootdir
, 'passwd', '-l', 'root'])
584 self
.message('Give root an empty password')
585 self
.delete_password(rootdir
, 'root')
587 def create_users(self
, rootdir
):
588 def create_user(user
):
589 self
.runcmd(['chroot', rootdir
, 'adduser', '--gecos', user
,
590 '--disabled-password', user
])
591 if self
.settings
['sudo']:
592 self
.runcmd(['chroot', rootdir
, 'adduser', user
, 'sudo'])
594 for userpass
in self
.settings
['user']:
596 user
, password
= userpass
.split('/', 1)
598 self
.set_password(rootdir
, user
, password
)
600 create_user(userpass
)
601 self
.delete_password(rootdir
, userpass
)
603 def set_password(self
, rootdir
, user
, password
):
604 encrypted
= crypt
.crypt(password
, '..')
605 self
.runcmd(['chroot', rootdir
, 'usermod', '-p', encrypted
, user
])
607 def delete_password(self
, rootdir
, user
):
608 self
.runcmd(['chroot', rootdir
, 'passwd', '-d', user
])
610 def remove_udev_persistent_rules(self
, rootdir
):
611 self
.message('Removing udev persistent cd and net rules')
612 for x
in ['70-persistent-cd.rules', '70-persistent-net.rules']:
613 pathname
= os
.path
.join(rootdir
, 'etc', 'udev', 'rules.d', x
)
614 if os
.path
.exists(pathname
):
615 logging
.debug('rm %s', pathname
)
618 logging
.debug('not removing non-existent %s', pathname
)
620 def setup_networking(self
, rootdir
):
621 self
.message('Setting up networking')
623 f
= open(os
.path
.join(rootdir
, 'etc', 'network', 'interfaces'), 'w')
625 f
.write('iface lo inet loopback\n')
627 if self
.settings
['enable-dhcp']:
629 f
.write('auto eth0\n')
630 f
.write('iface eth0 inet dhcp\n')
634 def append_serial_console(self
, rootdir
):
635 if self
.settings
['serial-console']:
636 serial_command
= self
.settings
['serial-console-command']
637 logging
.debug('adding getty to serial console')
638 inittab
= os
.path
.join(rootdir
, 'etc/inittab')
639 # to autologin, serial_command can contain '-a root'
640 with
open(inittab
, 'a') as f
:
641 f
.write('\nS0:23:respawn:%s\n' % serial_command
)
643 # pylint: disable=no-self-use
644 def _grub_serial_console(self
, rootdir
):
645 cmdline
= 'GRUB_CMDLINE_LINUX_DEFAULT="console=tty0 console=tty1 console=ttyS0,38400n8"'
646 terminal
= 'GRUB_TERMINAL="serial gfxterm"'
647 command
= 'GRUB_SERIAL_COMMAND="serial --speed=38400 --unit=0 --parity=no --stop=1"'
648 grub_cfg
= os
.path
.join(rootdir
, 'etc', 'default', 'grub')
649 logging
.debug("Allowing serial output in grub config %s", grub_cfg
)
650 with
open(grub_cfg
, 'a+') as cfg
:
651 cfg
.write("# %s serial support\n" % os
.path
.basename(__file__
))
652 cfg
.write("%s\n" % cmdline
)
653 cfg
.write("%s\n" % terminal
)
654 cfg
.write("%s\n" % command
)
656 def install_grub2(self
, rootdev
, rootdir
):
657 self
.message("Configuring grub2")
658 # rely on kpartx using consistent naming to map loop0p1 to loop0
659 install_dev
= os
.path
.join('/dev', os
.path
.basename(rootdev
)[:-2])
660 self
.runcmd(['mount', '/dev', '-t', 'devfs', '-obind',
661 '%s' % os
.path
.join(rootdir
, 'dev')])
662 self
.runcmd(['mount', '/proc', '-t', 'proc', '-obind',
663 '%s' % os
.path
.join(rootdir
, 'proc')])
664 self
.runcmd(['mount', '/sys', '-t', 'sysfs', '-obind',
665 '%s' % os
.path
.join(rootdir
, 'sys')])
666 if self
.settings
['serial-console']:
667 self
._grub
_serial
_console
(rootdir
)
670 self
.runcmd(['chroot', rootdir
, 'update-grub'])
671 self
.runcmd(['chroot', rootdir
, 'grub-install', install_dev
])
672 except cliapp
.AppException
:
673 self
.message("Failed. Is grub2-common installed? Using extlinux.")
674 self
.install_extlinux(rootdev
, rootdir
)
675 self
.runcmd(['umount', os
.path
.join(rootdir
, 'sys')])
676 self
.runcmd(['umount', os
.path
.join(rootdir
, 'proc')])
677 self
.runcmd(['umount', os
.path
.join(rootdir
, 'dev')])
679 def install_extlinux(self
, rootdev
, rootdir
):
680 if not os
.path
.exists("/usr/bin/extlinux"):
681 self
.message("extlinux not installed, skipping.")
683 self
.message('Installing extlinux')
686 dirname
= os
.path
.join(rootdir
, 'boot')
687 basenames
= os
.listdir(dirname
)
688 logging
.debug('find: %s', basenames
)
689 for basename
in basenames
:
690 if re
.search(pattern
, basename
):
691 return os
.path
.join('boot', basename
)
692 raise cliapp
.AppException('Cannot find match: %s' % pattern
)
695 kernel_image
= find('vmlinuz-.*')
696 initrd_image
= find('initrd.img-.*')
697 except cliapp
.AppException
as e
:
698 self
.message("Unable to find kernel. Not installing extlinux.")
699 logging
.debug("No kernel found. %s. Skipping install of extlinux.", e
)
702 out
= self
.runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
703 '-s', 'UUID', rootdev
])
704 uuid
= out
.splitlines()[0].strip()
706 conf
= os
.path
.join(rootdir
, 'extlinux.conf')
707 logging
.debug('configure extlinux %s', conf
)
708 kserial
= 'console=ttyS0,115200' if self
.settings
['serial-console'] else ''
709 extserial
= 'serial 0 115200' if self
.settings
['serial-console'] else ''
716 append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s
719 'kernel': kernel_image
, # pylint: disable=bad-continuation
720 'initrd': initrd_image
, # pylint: disable=bad-continuation
721 'uuid': uuid
, # pylint: disable=bad-continuation
722 'kserial': kserial
, # pylint: disable=bad-continuation
723 'extserial': extserial
, # pylint: disable=bad-continuation
724 } # pylint: disable=bad-continuation
725 logging
.debug("extlinux config:\n%s", msg
)
727 # python multiline string substitution is just ugly.
728 # use an external file or live with the mangling, no point in
729 # mangling the string to remove spaces just to keep it pretty in source.
733 self
.runcmd(['extlinux', '--install', rootdir
])
734 self
.runcmd(['sync'])
737 def optimize_image(self
, rootdir
):
739 Filing up the image with zeros will increase its compression rate
741 if not self
.settings
['sparse']:
742 zeros
= os
.path
.join(rootdir
, 'ZEROS')
743 self
.runcmd_unchecked(['dd', 'if=/dev/zero', 'of=' + zeros
, 'bs=1M'])
744 self
.runcmd(['rm', '-f', zeros
])
748 Run squashfs on the image.
750 if not os
.path
.exists('/usr/bin/mksquashfs'):
751 logging
.warning("Squash selected but mksquashfs not found!")
753 self
.message("Running mksquashfs")
754 suffixed
= "%s.squashfs" % self
.settings
['image']
755 self
.runcmd(['mksquashfs', self
.settings
['image'],
757 '-no-progress', '-comp', 'xz'], ignore_fail
=False)
758 os
.unlink(self
.settings
['image'])
759 self
.settings
['image'] = suffixed
761 def cleanup_system(self
):
762 # Clean up after any errors.
764 self
.message('Cleaning up')
766 # Umount in the reverse mount order
767 if self
.settings
['image']:
768 for i
in range(len(self
.mount_points
) - 1, -1, -1):
769 mount_point
= self
.mount_points
[i
]
771 self
.runcmd(['umount', mount_point
], ignore_fail
=False)
772 except cliapp
.AppException
:
773 logging
.debug("umount failed, sleeping and trying again")
775 self
.runcmd(['umount', mount_point
], ignore_fail
=False)
777 self
.runcmd(['kpartx', '-d', self
.settings
['image']], ignore_fail
=True)
779 for dirname
in self
.remove_dirs
:
780 shutil
.rmtree(dirname
)
782 def customize(self
, rootdir
):
783 script
= self
.settings
['customize']
786 if not os
.path
.exists(script
):
787 example
= os
.path
.join("/usr/share/vmdebootstrap/examples/", script
)
788 if not os
.path
.exists(example
):
789 self
.message("Unable to find %s" % script
)
792 self
.message('Running customize script %s' % script
)
793 logging
.info("rootdir=%s image=%s", rootdir
, self
.settings
['image'])
794 with
open('/dev/tty', 'w') as tty
:
796 cliapp
.runcmd([script
, rootdir
, self
.settings
['image']], stdout
=tty
, stderr
=tty
)
798 subprocess
.call([script
, rootdir
, self
.settings
['image']])
800 def create_tarball(self
, rootdir
):
801 # Create a tarball of the disk's contents
802 # shell out to runcmd since it more easily handles rootdir
803 self
.message('Creating tarball of disk contents')
804 self
.runcmd(['tar', '-cf', self
.settings
['tarball'], '-C', rootdir
, '.'])
807 # Change image owner after completed build
808 if self
.settings
['image']:
809 filename
= self
.settings
['image']
810 elif self
.settings
['tarball']:
811 filename
= self
.settings
['tarball']
814 self
.message("Changing owner to %s" % self
.settings
["owner"])
815 subprocess
.call(["chown", self
.settings
["owner"], filename
])
817 def list_installed_pkgs(self
, rootdir
):
818 # output the list of installed packages for sources identification
819 self
.message("Creating a list of installed binary package names")
820 out
= self
.runcmd(['chroot', rootdir
,
821 'dpkg-query', '-W', "-f='${Package}.deb\n'"])
822 with
open('dpkg.list', 'w') as dpkg
:
825 def configure_apt(self
, rootdir
):
826 # use the distribution and mirror to create an apt source
827 self
.message("Configuring apt to use distribution and mirror")
828 conf
= os
.path
.join(rootdir
, 'etc', 'apt', 'sources.list.d', 'base.list')
829 logging
.debug('configure apt %s', conf
)
830 mirror
= self
.settings
['mirror']
831 if self
.settings
['apt-mirror']:
832 mirror
= self
.settings
['apt-mirror']
833 self
.message("Setting apt mirror to %s" % mirror
)
834 os
.unlink(os
.path
.join(rootdir
, 'etc', 'apt', 'sources.list'))
836 line
= 'deb %s %s main\n' % (mirror
, self
.settings
['distribution'])
838 line
= '#deb-src %s %s main\n' % (mirror
, self
.settings
['distribution'])
841 # ensure the apt sources have valid lists
842 self
.runcmd(['chroot', rootdir
, 'apt-get', '-qq', 'update'])
844 if __name__
== '__main__':
845 VmDebootstrap(version
=__version__
).run()