summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNeil Williams <codehelp@debian.org>2015-11-05 15:50:15 +0000
committerNeil Williams <codehelp@debian.org>2015-11-05 15:50:15 +0000
commit0100446b4febb76cf96cfd9559d1d22d8dcd83aa (patch)
treed23233fcda466939e0172069bc07b7e22268dca7
parent5413ccc44b905facd55fbb0812b7c9e350deb44f (diff)
parent92b873ab69757662bec66618c06aa69d3b06814f (diff)
downloadvmdebootstrap-0100446b4febb76cf96cfd9559d1d22d8dcd83aa.tar.gz
Merge branch 'modules'
Split the single script into modules and add documentation.
-rw-r--r--.gitignore4
-rw-r--r--MANIFEST.in8
-rw-r--r--README34
-rw-r--r--TODO37
-rwxr-xr-xbin/qemu-wrapper.sh28
-rwxr-xr-xbin/vmdebootstrap556
-rw-r--r--common/customise.lib77
-rwxr-xr-xcommon/jessie-amd64-hook.sh30
-rwxr-xr-xcommon/jessie-amd64.sh30
-rwxr-xr-xcommon/jessie-arm64-hook.sh29
-rwxr-xr-xcommon/jessie-arm64.sh33
-rw-r--r--doc/Makefile153
-rw-r--r--doc/conf.py213
-rw-r--r--doc/devel.rst133
-rw-r--r--doc/index.rst9
-rw-r--r--doc/live.rst112
-rw-r--r--doc/overview.rst392
-rw-r--r--examples/README.txt50
-rwxr-xr-xexamples/auto-serial-console35
-rwxr-xr-xexamples/jessie-uefi-amd64.sh15
-rwxr-xr-xexamples/lava-submit.py92
-rwxr-xr-xexamples/lava.sh7
-rwxr-xr-xexamples/qemu-efi-bochs-drm.sh8
-rw-r--r--man/Makefile153
-rw-r--r--man/conf.py211
-rw-r--r--man/index.rst4
-rw-r--r--man/vmdebootstrap.rst343
-rw-r--r--setup.py52
-rwxr-xr-xvmdebootstrap1095
-rw-r--r--vmdebootstrap.8.in405
-rw-r--r--vmdebootstrap/__init__.py0
-rw-r--r--vmdebootstrap/base.py217
-rw-r--r--vmdebootstrap/codenames.py79
-rw-r--r--vmdebootstrap/constants.py52
-rw-r--r--vmdebootstrap/extlinux.py107
-rw-r--r--vmdebootstrap/filesystem.py273
-rw-r--r--vmdebootstrap/grub.py123
-rw-r--r--vmdebootstrap/uefi.py167
38 files changed, 3821 insertions, 1545 deletions
diff --git a/.gitignore b/.gitignore
index 3f54e8c..dcf1963 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,7 @@
*.tar.gz
*.pyc
*.egg-info
+doc/_build/
+man/_build/
+dist/*
+local/*
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..f5a8dad
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,8 @@
+include NEWS
+include vmextract.py
+include vmdebootstrap.8.in
+recursive-include examples *.sh *.txt *.py
+include common/*
+include doc/*
+include man/*
+include bin/qemu-wrapper.sh
diff --git a/README b/README
index 2b58ccf..b6fa46c 100644
--- a/README
+++ b/README
@@ -68,12 +68,40 @@ $ sudo PYTHONPATH=. ./bin/vmdebootstrap
This has changed slightly with version 1.0 with the need for
PYTHONPATH to reference the module approach for support handlers.
+vmdebootstrap modules
+---------------------
+
+The single vmdebootstrap script has been refactored to be the top
+level settings parser and validator and the point where the other
+modules (handlers) get to be called in a collaborative sequence.
+
+The new modules are an attempt to work with a DRY process as well
+as keeping the source code itself maintainable. Handler functions
+need to check settings at the start so that calls to the handlers
+can be retained in a simple flow. Where a function needs code from
+multiple handlers, that function needs to be in the vmdebootstrap
+script but these should, ideally, be single calls into dedicated
+calls from the relevant handlers which can return True|False or
+raise cliapp.AppException to affect subsequent flow. Handlers must
+NOT hook into other handlers, except Base or constants, only the
+vmdebootstrap script has the full set, so use function arguments to
+pass variables populated by different handlers. Wherever possible,
+large sections of new functionality need to be added as new handlers.
+
pylint
------
-vmdebootstrap uses pylint and contains comments to disable certain
-pylint checks in certain areas. pylint compatibility will make it
-easier to accept patches, just follow the existing pattern
+When using pylint, the following option is advised:
+
+ $ pylint --ignore-imports=y vmdebootstrap
+
+(Despite the name of the option, this only ignores imports when
+computing similarities and various handlers will end up needing
+similar imports, it makes no sense to complain about that.)
+
+Apart from that, vmdebootstrap uses pylint and contains comments to
+disable certain pylint checks in certain areas. pylint compatibility
+will make it easier to accept patches, just follow the existing pattern
of pylint usage. pylint is far from perfect but can be helpful.
Testing UEFI support
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..a1cd415
--- /dev/null
+++ b/TODO
@@ -0,0 +1,37 @@
+TODO
+====
+
+* document the problems of setting a default password
+
+* first-boot customisation support (via a package) which forces a
+ new password, possibly new hostname, generate new ssh key etc.
+
+* make all internal additions and operations optional
+
+* investigate some way to support complex partitioning
+
+* document that no-kernel can lead to images where the kernel
+ cannot be upgraded.
+
+* try to support upgrading the bootloader
+ possibly via the config output
+ problems of flash-kernel
+
+* automatically grow rootfs to media size on first boot
+ boot into ramdisk, umount media, move ext4 along media
+ expand vfat.
+
+* document of how to add new bootloader etc.
+ unless the old and new bootloader exist as packages, this
+ is going to be manual.
+
+* customisations as packages.
+
+* document how the image was built with the config called.
+ include the config output and a copy of the script with
+ dependencies.
+
+* consider limitations of only one hook script
+ likely to be a lack of time to implement multi-hook support.
+
+* support method to share shell customisation scripts
diff --git a/bin/qemu-wrapper.sh b/bin/qemu-wrapper.sh
new file mode 100755
index 0000000..901fd64
--- /dev/null
+++ b/bin/qemu-wrapper.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+set -e
+
+if [ -z "$1" ]; then
+ echo "Usage: <imagefile> <arch> [uefi_directory]"
+ echo "For x86_64, amd64 is also supported."
+ exit 1
+fi
+
+if [ -n "$2" ]; then
+ if [ "$2" = 'amd64' ]; then
+ ARCH='x86_64'
+ else
+ ARCH="$2"
+ fi
+else
+ echo "Specify the architecture of the image"
+ echo "Usage: <imagefile> <arch>"
+ echo "For x86_64, amd64 is also supported."
+ exit 1
+fi
+UEFI=""
+if [ -n "$3" ]; then
+ UEFI="-L $3"
+fi
+
+qemu-system-${ARCH} -m 1024 ${UEFI} -enable-kvm -drive format=raw,file=./$1
diff --git a/bin/vmdebootstrap b/bin/vmdebootstrap
new file mode 100755
index 0000000..35287ce
--- /dev/null
+++ b/bin/vmdebootstrap
@@ -0,0 +1,556 @@
+#! /usr/bin/python
+# Copyright 2011-2013 Lars Wirzenius
+# Copyright 2012 Codethink Limited
+# Copyright 2014-2015 Neil Williams <codehelp@debian.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+import time
+import shutil
+import cliapp
+import logging
+import datetime
+import tempfile
+import subprocess
+from vmdebootstrap.base import (
+ Base,
+ runcmd,
+ cleanup_apt_cache,
+)
+from vmdebootstrap.grub import GrubHandler
+from vmdebootstrap.extlinux import ExtLinux
+from vmdebootstrap.codenames import Codenames
+from vmdebootstrap.filesystem import Filesystem
+from vmdebootstrap.uefi import Uefi
+
+__version__ = '1.1'
+
+# pylint: disable=invalid-name,line-too-long,missing-docstring
+
+
+class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-methods
+
+ def __init__(self, progname=None, version=__version__, description=None, epilog=None):
+ super(VmDebootstrap, self).__init__(progname, version, description, epilog)
+ self.remove_dirs = []
+ self.mount_points = []
+ self.bootdir = None
+ self.handlers = {
+ Base.name: Base(),
+ Uefi.name: Uefi(),
+ Codenames.name: Codenames(),
+ GrubHandler.name: GrubHandler(),
+ ExtLinux.name: ExtLinux(),
+ Filesystem.name: Filesystem(),
+ }
+
+ def add_settings(self):
+ default_arch = subprocess.check_output(
+ ["dpkg", "--print-architecture"]).strip()
+
+ self.settings.boolean(
+ ['verbose'], 'report what is going on')
+ self.settings.string(
+ ['image'], 'put created disk image in FILE',
+ metavar='FILE')
+ self.settings.bytesize(
+ ['size'], 'create a disk image of size SIZE (%default)',
+ metavar='SIZE', default='1G')
+ self.settings.bytesize(
+ ['bootsize'], 'create boot partition of size '
+ 'SIZE (%default)',
+ metavar='BOOTSIZE', default='0%')
+ self.settings.string(
+ ['boottype'], 'specify file system type for /boot/',
+ default='ext2')
+ self.settings.bytesize(
+ ['bootoffset'], 'Space to leave at start of the '
+ 'image for bootloader',
+ default='0')
+ self.settings.boolean(
+ ['use-uefi'], 'Setup image for UEFI boot',
+ default=False)
+ self.settings.bytesize(
+ ['esp-size'], 'Size of EFI System Partition - '
+ 'requires use-uefi',
+ default='5mib')
+ self.settings.string(
+ ['part-type'], 'Partition type to use for this image',
+ default='msdos')
+ self.settings.string(
+ ['roottype'], 'specify file system type for /',
+ default='ext4')
+ self.settings.bytesize(
+ ['swap'], 'create swap space of size SIZE (min 256Mb)')
+ self.settings.string(
+ ['foreign'], 'set up foreign debootstrap environment '
+ 'using provided program (ie binfmt handler)')
+ self.settings.string_list(
+ ['debootstrapopts'],
+ 'pass additional options to debootstrap')
+ self.settings.boolean(
+ ['extlinux'], 'install extlinux?',
+ default=True)
+ self.settings.string(
+ ['tarball'], "tar up the disk's contents in FILE",
+ metavar='FILE')
+ self.settings.string(
+ ['apt-mirror'], 'configure apt to use MIRROR',
+ metavar='URL')
+ self.settings.string(
+ ['mirror'], 'use MIRROR as package source (%default)',
+ metavar='URL',
+ default='http://http.debian.net/debian/')
+ self.settings.string(
+ ['arch'], 'architecture to use (%default)',
+ metavar='ARCH',
+ default=default_arch)
+ self.settings.string(
+ ['distribution'], 'release to use (%default)',
+ metavar='NAME',
+ default='stable')
+ self.settings.string_list(
+ ['package'], 'install PACKAGE onto system')
+ self.settings.string_list(
+ ['custom-package'], 'install package in DEB file '
+ 'onto system (not from mirror)',
+ metavar='DEB')
+ self.settings.boolean(
+ ['no-kernel'], 'do not install a linux package')
+ self.settings.string(
+ ['kernel-package'], 'install PACKAGE instead of '
+ 'the default kernel package',
+ metavar='PACKAGE')
+ self.settings.boolean(
+ ['enable-dhcp'], 'enable DHCP on eth0')
+ self.settings.string(
+ ['root-password'], 'set root password',
+ metavar='PASSWORD')
+ self.settings.boolean(
+ ['lock-root-password'], 'lock root account so they '
+ 'cannot login?')
+ self.settings.string(
+ ['customize'], 'run SCRIPT after setting up system',
+ metavar='SCRIPT')
+ self.settings.string(
+ ['hostname'], 'set name to HOSTNAME (%default)',
+ metavar='HOSTNAME',
+ default='debian')
+ self.settings.string_list(
+ ['user'], 'create USER with PASSWORD',
+ metavar='USER/PASSWORD')
+ self.settings.boolean(
+ ['serial-console'], 'configure image to use a serial console')
+ self.settings.string(
+ ['serial-console-command'], 'command to manage the '
+ 'serial console, appended to /etc/inittab (%default)',
+ metavar='COMMAND',
+ default='/sbin/getty -L ttyS0 115200 vt100')
+ self.settings.boolean(
+ ['sudo'], 'install sudo, and if user is created, '
+ 'add them to sudo group')
+ self.settings.string(
+ ['owner'], 'the user who will own the image when '
+ 'the build is complete.')
+ self.settings.string(
+ ['squash'], 'use squashfs on the rootfs - '
+ 'cannot be used with --image', metavar='DIRECTORY')
+ self.settings.boolean(
+ ['configure-apt'], 'Create an apt source based on '
+ 'the distribution and mirror selected.')
+ self.settings.boolean(
+ ['mbr'], 'Run install-mbr (default if extlinux used)')
+ self.settings.boolean(
+ ['grub'], 'Install and configure grub2 - '
+ 'disables extlinux.')
+ self.settings.boolean(
+ ['sparse'], 'Do not fill the image with zeros to '
+ 'keep a sparse disk image',
+ default=False)
+ self.settings.boolean(
+ ['pkglist'], 'Create a list of package names '
+ 'included in the image.')
+ self.settings.boolean(
+ ['no-acpid'], 'do not install the acpid package',
+ default=False)
+
+ def process_args(self, args): # pylint: disable=too-many-branches,too-many-statements
+
+ for _, handler in self.handlers.items():
+ handler.define_settings(self.settings)
+
+ distro = self.handlers[Codenames.name]
+ if self.settings['squash'] and self.settings['image']:
+ raise cliapp.AppException(
+ '--image can no longer be used with --squash')
+ if not self.settings['image'] and not (
+ self.settings['tarball'] or self.settings['squash']):
+ raise cliapp.AppException(
+ 'You must give disk image filename or use either a '
+ 'tarball filename or use squash')
+ if self.settings['image'] and not self.settings['size']:
+ raise cliapp.AppException(
+ 'If disk image is specified, you must give image size.')
+ if not distro.debian_info.valid(self.settings['distribution']):
+ if not distro.ubuntu_info.valid(self.settings['distribution']):
+ raise cliapp.AppException(
+ '%s is not a valid Debian or Ubuntu suite or codename.'
+ % self.settings['distribution'])
+ uefi = self.handlers[Uefi.name]
+ oldstable = distro.was_oldstable(datetime.date(2015, 4, 26))
+ uefi.check_settings(oldstable=oldstable)
+
+ if os.geteuid() != 0:
+ sys.exit("You need to have root privileges to run this script.")
+ self.start_ops()
+
+ def _image_preparations(self):
+ uefi = self.handlers[Uefi.name]
+ base = self.handlers[Base.name]
+ filesystem = self.handlers[Filesystem.name]
+ extlinux = self.handlers[ExtLinux.name]
+ base.create_empty_image()
+ self.partition_image()
+ extlinux.install_mbr()
+ filesystem.setup_kpartx()
+ rootdev = filesystem.devices['rootdev']
+ roottype = filesystem.devices['roottype']
+ bootdev = filesystem.devices['bootdev']
+ if self.settings['swap'] > 0:
+ base.message("Creating swap space")
+ runcmd(['mkswap', filesystem.devices['swapdev']])
+ filesystem.mkfs(rootdev, fstype=roottype)
+ rootdir = self.mount(rootdev)
+ filesystem.devices['rootdir'] = rootdir
+ if self.settings['use-uefi']:
+ self.bootdir = uefi.prepare_esp(rootdir, bootdev)
+ logging.debug("mounting %s", self.bootdir)
+ self.mount(bootdev, self.bootdir)
+ logging.debug(runcmd(['mount']))
+ elif bootdev:
+ boottype = self.settings['boottype']
+ filesystem.mkfs(bootdev, fstype=boottype)
+ self.bootdir = '%s/%s' % (rootdir, 'boot/')
+ filesystem.devices['bootdir'] = self.bootdir
+ os.mkdir(self.bootdir)
+ self.mount(bootdev, self.bootdir)
+
+ def _image_operations(self, rootdir, rootdev):
+ if not self.settings['image']:
+ return
+ logging.debug("rootdir=%s rootdev=%s", rootdir, rootdev)
+ grub = self.handlers[GrubHandler.name]
+ extlinux = self.handlers[ExtLinux.name]
+ base = self.handlers[Base.name]
+ uefi = self.handlers[Uefi.name]
+ if self.settings['use-uefi']:
+ bootdir = self.bootdir
+ logging.debug(
+ "rootdir=%s rootdev=%s bootdir=%s",
+ rootdir, rootdev, bootdir)
+ logging.debug(runcmd(['mount']))
+ if not os.path.ismount(bootdir):
+ logging.warning("%s had to be remounted", bootdir)
+ self.mount(bootdir)
+ grub.install_grub_uefi(rootdir)
+ uefi.configure_efi(rootdir)
+ grub.install_extra_grub_uefi(rootdir)
+ uefi.configure_extra_efi(rootdir)
+ elif self.settings['grub']:
+ if not grub.install_grub2(rootdev, rootdir):
+ extlinux.install_extlinux(rootdev, rootdir)
+ elif self.settings['extlinux']:
+ extlinux.install_extlinux(rootdev, rootdir)
+ base.append_serial_console(rootdir)
+ self.optimize_image(rootdir)
+
+ def start_ops(self):
+ base = self.handlers[Base.name]
+ filesystem = self.handlers[Filesystem.name]
+ try:
+ if self.settings['image']:
+ self._image_preparations()
+ rootdir = filesystem.devices['rootdir']
+ rootdev = filesystem.devices['rootdev']
+ else:
+ rootdir = self.mkdtemp()
+ filesystem.devices['rootdir'] = rootdir
+ rootdev = filesystem.devices['rootdev']
+ logging.debug("rootdir=%s rootdev=%s", rootdir, rootdev)
+ self.debootstrap(rootdir)
+ filesystem.set_hostname()
+ filesystem.create_fstab()
+ self.install_debs(rootdir)
+ base.set_root_password(rootdir)
+ base.create_users(rootdir)
+ filesystem.remove_udev_persistent_rules()
+ self.setup_networking(rootdir)
+ filesystem.configure_apt()
+ base.customize(rootdir)
+ cleanup_apt_cache(rootdir)
+ filesystem.update_initramfs()
+ self._image_operations(rootdir, rootdev)
+ filesystem.list_installed_pkgs()
+
+ if self.settings['foreign']:
+ os.unlink(
+ '%s/usr/bin/%s' %
+ (rootdir, os.path.basename(self.settings['foreign'])))
+
+ if self.settings['tarball']:
+ base.create_tarball(rootdir)
+ elif self.settings['squash']:
+ filesystem.squash_rootfs()
+ filesystem.chown()
+
+ except BaseException as e:
+ base.message('EEEK! Something bad happened...')
+ rootdir = filesystem.devices['rootdir']
+ if rootdir:
+ db_log = os.path.join(rootdir, 'debootstrap', 'debootstrap.log')
+ if os.path.exists(db_log):
+ shutil.copy(db_log, os.getcwd())
+ if self.settings['owner']:
+ runcmd(["chown", self.settings["owner"], db_log])
+ base.message(e)
+ self.cleanup_system()
+ raise
+ else:
+ self.cleanup_system()
+
+ def mkdtemp(self):
+ dirname = tempfile.mkdtemp()
+ self.remove_dirs.append(dirname)
+ logging.debug('mkdir %s', dirname)
+ return dirname
+
+ def mount(self, device, path=None):
+ base = self.handlers[Base.name]
+ if not path:
+ mount_point = self.mkdtemp()
+ else:
+ mount_point = path
+ base.message('Mounting %s on %s' % (device, mount_point))
+ runcmd(['mount', device, mount_point])
+ self.mount_points.append(mount_point)
+ logging.debug('mounted %s on %s', device, mount_point)
+ return mount_point
+
+ def partition_image(self):
+ """
+ Uses fat16 (msdos) partitioning by default, use part-type to change.
+ If bootoffset is specified, the first actual partition
+ starts at that offset to allow customisation scripts to
+ put bootloader images into the space, e.g. u-boot.
+ """
+ base = self.handlers[Base.name]
+ base.message('Creating partitions')
+ uefi = self.handlers[Uefi.name]
+ runcmd(['parted', '-s', self.settings['image'],
+ 'mklabel', self.settings['part-type']])
+ partoffset = 0
+ extent = base.check_swap_size()
+
+ # uefi
+ uefi.partition_esp()
+
+ # /boot partitioning offset calculation
+ # returns partoffset
+ if self.settings['bootoffset'] and self.settings['bootoffset'] is not '0':
+ # turn v.small offsets into something at least possible to create.
+ if self.settings['bootoffset'] < 1048576:
+ partoffset = 1
+ logging.info(
+ "Setting bootoffset %smib to allow for %s bytes",
+ partoffset, self.settings['bootoffset'])
+ else:
+ partoffset = self.settings['bootoffset'] / (1024 * 1024)
+ base.message(
+ "Using bootoffset: %smib %s bytes" %
+ (partoffset, self.settings['bootoffset']))
+
+ # /boot creation - move into base but keep the check
+ # needs extent, partoffset, bootsize: no return
+ if self.settings['bootsize'] and self.settings['bootsize'] is not '0%':
+ if self.settings['grub'] and not partoffset:
+ partoffset = 1
+ bootsize = self.settings['bootsize'] / (1024 * 1024)
+ bootsize += partoffset
+ base.message("Using bootsize %smib: %s bytes" % (bootsize, self.settings['bootsize']))
+ logging.debug("Starting boot partition at %sMb", bootsize)
+ runcmd(['parted', '-s', self.settings['image'],
+ 'mkpart', 'primary', 'fat16', str(partoffset), str(bootsize)])
+ logging.debug("Starting root partition at %sMb", partoffset)
+ runcmd(['parted', '-s', self.settings['image'],
+ 'mkpart', 'primary', str(bootsize), extent])
+
+ # uefi - make rootfs partition after end of ESP
+ # needs extent
+ elif self.settings['use-uefi']:
+ uefi.make_root(extent)
+
+ # no boot partition
+ else:
+ runcmd(['parted', '-s', self.settings['image'],
+ 'mkpart', 'primary', '0%', extent])
+
+ # whatever we create, something needs the boot flag
+ runcmd(['parted', '-s', self.settings['image'],
+ 'set', '1', 'boot', 'on'])
+
+ # return to doing swap setup
+ base.make_swap(extent)
+
+ def _bootstrap_packages(self):
+ base = self.handlers[Base.name]
+ uefi = self.handlers[Uefi.name]
+ grub = self.handlers[GrubHandler.name]
+ distro = self.handlers[Codenames.name]
+
+ include = self.settings['package']
+ include.extend(base.base_packages())
+ include.extend(uefi.efi_packages())
+ include.extend(grub.grub_packages())
+ include.extend(distro.kernel_package())
+ return list(set(include))
+
+ def _debootstrap_second_stage(self, rootdir):
+ base = self.handlers[Base.name]
+ # set a noninteractive debconf environment for secondstage
+ env = {
+ "DEBIAN_FRONTEND": "noninteractive",
+ "DEBCONF_NONINTERACTIVE_SEEN": "true",
+ "LC_ALL": "C"
+ }
+ # add the mapping to the complete environment.
+ env.update(os.environ)
+ # First copy the binfmt handler over
+ base.message('Setting up binfmt handler')
+ shutil.copy(self.settings['foreign'], '%s/usr/bin/' % rootdir)
+ # Next, run the package install scripts etc.
+ base.message('Running debootstrap second stage')
+ runcmd(['chroot', rootdir,
+ '/debootstrap/debootstrap', '--second-stage'],
+ env=env)
+
+ def debootstrap(self, rootdir):
+ base = self.handlers[Base.name]
+ include = self._bootstrap_packages()
+ base.message(
+ 'Debootstrapping %s [%s]' % (
+ self.settings['distribution'], self.settings['arch']))
+
+ args = ['debootstrap', '--arch=%s' % self.settings['arch']]
+
+ if self.settings['package']:
+ args.append(
+ '--include=%s' % ','.join(include))
+ if self.settings['foreign']:
+ args.append('--foreign')
+ if self.settings['debootstrapopts']:
+ for opt in self.settings['debootstrapopts']:
+ for part in opt.split(' '):
+ args.append('--%s' % part)
+ logging.debug("debootstrap arguments: %s", args)
+ args += [self.settings['distribution'],
+ rootdir, self.settings['mirror']]
+ logging.debug(" ".join(args))
+ runcmd(args)
+ if self.settings['foreign']:
+ self._debootstrap_second_stage(rootdir)
+
+ def install_debs(self, rootdir):
+ base = self.handlers[Base.name]
+ if not self.settings['custom-package']:
+ return
+ base.message('Installing custom packages')
+ tmp = os.path.join(rootdir, 'tmp', 'install_debs')
+ os.mkdir(tmp)
+ for deb in self.settings['custom-package']:
+ shutil.copy(deb, tmp)
+ filenames = [os.path.join('/tmp/install_debs', os.path.basename(deb))
+ for deb in self.settings['custom-package']]
+ out, err, _ = \
+ self.runcmd_unchecked(['chroot', rootdir, 'dpkg', '-i'] + filenames)
+ logging.debug('stdout:\n%s', out)
+ logging.debug('stderr:\n%s', err)
+ out = runcmd(['chroot', rootdir,
+ 'apt-get', '-f', '--no-remove', 'install'])
+ logging.debug('stdout:\n%s', out)
+ shutil.rmtree(tmp)
+
+ def optimize_image(self, rootdir):
+ """
+ Filing up the image with zeros will increase its compression rate
+ """
+ if not self.settings['sparse']:
+ zeros = os.path.join(rootdir, 'ZEROS')
+ self.runcmd_unchecked(['dd', 'if=/dev/zero', 'of=' + zeros, 'bs=1M'])
+ runcmd(['rm', '-f', zeros])
+
+ def setup_networking(self, rootdir):
+ base = self.handlers[Base.name]
+ base.message('Setting up networking')
+ distro = self.handlers[Codenames.name]
+ ifc_file = os.path.join(rootdir, 'etc', 'network', 'interfaces')
+ ifc_d = os.path.join(rootdir, 'etc', 'network', 'interfaces.d')
+
+ # unconditionally write for wheezy (which became oldstable on 2015.04.25)
+ if distro.was_oldstable(datetime.date(2015, 4, 26)):
+ with open(ifc_file, 'w') as netfile:
+ netfile.write('source /etc/network/interfaces.d/*\n')
+ elif not os.path.exists(ifc_file):
+ with open(ifc_file, 'a') as netfile:
+ netfile.write('source-directory /etc/network/interfaces.d\n')
+
+ if not os.path.exists(ifc_d):
+ os.mkdir(ifc_d)
+ ethpath = os.path.join(ifc_d, 'setup')
+ with open(ethpath, 'w') as eth:
+ eth.write('auto lo\n')
+ eth.write('iface lo inet loopback\n')
+
+ if self.settings['enable-dhcp']:
+ eth.write('\n')
+ eth.write('auto eth0\n')
+ eth.write('iface eth0 inet dhcp\n')
+ # force predictable interface names
+ base.mask_udev_predictable_rules(rootdir)
+
+ def cleanup_system(self):
+ base = self.handlers[Base.name]
+ # Clean up after any errors.
+
+ base.message('Cleaning up')
+
+ # Umount in the reverse mount order
+ if self.settings['image']:
+ for i in range(len(self.mount_points) - 1, -1, -1):
+ mount_point = self.mount_points[i]
+ try:
+ runcmd(['umount', mount_point], ignore_fail=False)
+ except cliapp.AppException:
+ logging.debug("umount failed, sleeping and trying again")
+ time.sleep(5)
+ runcmd(['umount', mount_point], ignore_fail=False)
+
+ runcmd(['kpartx', '-d', self.settings['image']], ignore_fail=True)
+
+ for dirname in self.remove_dirs:
+ shutil.rmtree(dirname)
+
+if __name__ == '__main__':
+ VmDebootstrap(version=__version__).run()
diff --git a/common/customise.lib b/common/customise.lib
new file mode 100644
index 0000000..a62c64a
--- /dev/null
+++ b/common/customise.lib
@@ -0,0 +1,77 @@
+# Copyright 2015 Neil Williams <codehelp@debian.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+BASE_OPTS="
+ --owner ${WHO} --verbose \
+ --sudo \
+ --lock-root-password \
+ --enable-dhcp \
+ --configure-apt \
+ --log debian-cd-${SUITE}-${ARCH}.log --log-level debug \
+"
+
+TASK_PACKAGES="task-laptop task-english"
+
+# packages which are not (yet) part of a task
+EXTRA_PACKAGES="dkms locales whois telnet aptitude lsof host \
+ bash-completion firmware-linux-free dnsutils time rsync reportbug w3m \
+ ethtool ftp host lsof debian-faq debian-installer-launcher doc-debian \
+"
+cleanup() {
+ umount ${rootdir}/proc
+ umount ${rootdir}/sys
+}
+
+export DEBIAN_FRONTEND=noninteractive
+export LC_ALL=C
+export LANG=C
+export LANGUAGE=C
+
+mount_support() {
+ mount proc -t proc ${rootdir}/proc
+ mount sys -t sysfs ${rootdir}/sys
+}
+
+disable_daemons() {
+ # prevent packages starting daemons inside the chroot until after boot.
+ # https://wiki.debian.org/chroot
+ cat > ${rootdir}/usr/sbin/policy-rc.d <<EOF
+#!/bin/sh
+exit 101
+EOF
+ chmod a+x ${rootdir}/usr/sbin/policy-rc.d
+}
+
+# FIXME: pass the replacement mirror
+prepare_apt_source() {
+ # handle the apt source
+ mv ${rootdir}/etc/apt/sources.list.d/base.list ${rootdir}/etc/apt/
+ echo "deb http://mirror/debian/ ${SUITE} main contrib non-free" > ${rootdir}/etc/apt/sources.list
+ chroot ${rootdir} apt update
+}
+
+remove_daemon_block() {
+ rm ${rootdir}/usr/sbin/policy-rc.d
+}
+
+replace_apt_source() {
+ # Undo apt source change
+ rm ${rootdir}/etc/apt/sources.list
+ mv ${rootdir}/etc/apt/base.list ${rootdir}/etc/apt/sources.list.d/
+}
+
+blacklist_qemu_bochs() {
+ echo "blacklist bochs-drm" > ${rootdir}/etc/modprobe.d/qemu-blacklist.conf
+}
diff --git a/common/jessie-amd64-hook.sh b/common/jessie-amd64-hook.sh
new file mode 100755
index 0000000..c6a99fd
--- /dev/null
+++ b/common/jessie-amd64-hook.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+set -e
+
+rootdir=$1
+
+# common needs rootdir to already be defined.
+. /usr/share/vmdebootstrap/common/customise.lib
+
+trap cleanup 0
+
+mount_support
+disable_daemons
+# prepare_apt_source
+
+chroot ${rootdir} apt-get -q -y install ${TASK_PACKAGES} ${EXTRA_PACKAGES} \
+ task-xfce-desktop exim4 mutt info rpcbind pciutils \
+ task-ssh-server task-print-server plymouth procmail \
+ m4 open-vm-tools apt-listchanges at busybox nfs-common \
+ wamerican texinfo plymouth-themes plymouth-x11 uuid-runtime \
+ open-vm-tools-dkms open-vm-tools-desktop gettext-base mlocate \
+ irqbalance memtest86+ user-setup zerofree
+
+remove_daemon_block
+# replace_apt_source
+
+# particular to efi builds
+blacklist_qemu_bochs
+
+echo "Customisation complete"
diff --git a/common/jessie-amd64.sh b/common/jessie-amd64.sh
new file mode 100755
index 0000000..6f0971e
--- /dev/null
+++ b/common/jessie-amd64.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+set -e
+
+# define before sourcing common
+WHO=`whoami`
+USER='user/live'
+SUITE='jessie'
+SIZE='5G'
+ARCH='amd64'
+SHARE_PATH='/usr/share/vmdebootstrap/common'
+# needs a path for arch and task desktop
+IMAGE_PATH='.'
+
+. ${SHARE_PATH}/customise.lib
+
+sudo vmdebootstrap \
+ ${BASE_OPTS} --user ${USER} \
+ --size ${SIZE} \
+ --arch ${ARCH} \
+ --no-extlinux \
+ --grub --use-uefi \
+ --distribution ${SUITE} \
+ --customize "${SHARE_PATH}/${SUITE}-${ARCH}-hook.sh" \
+ --image ${IMAGE_PATH}/${SUITE}.img \
+ "$@"
+
+# report results and check we have something valid.
+ls -l ${IMAGE_PATH}/${SUITE}.img
+md5sum ${IMAGE_PATH}/${SUITE}.img
diff --git a/common/jessie-arm64-hook.sh b/common/jessie-arm64-hook.sh
new file mode 100755
index 0000000..c986759
--- /dev/null
+++ b/common/jessie-arm64-hook.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+set -e
+
+rootdir=$1
+
+# common needs rootdir to already be defined.
+. /usr/share/vmdebootstrap/common/customise.lib
+
+trap cleanup 0
+
+mount_support
+disable_daemons
+prepare_apt_source
+
+chroot ${rootdir} apt-get -q -y install ${TASK_PACKAGES} ${EXTRA_PACKAGES} \
+ task-xfce-desktop exim4 mutt info rpcbind pciutils \
+ task-ssh-server task-print-server plymouth procmail \
+ m4 apt-listchanges at busybox nfs-common \
+ wamerican texinfo plymouth-themes plymouth-x11 uuid-runtime \
+ gettext-base mlocate irqbalance \
+ irqbalance user-setup zerofree
+
+remove_daemon_block
+replace_apt_source
+# particular to efi builds
+blacklist_qemu_bochs
+
+echo "Customisation complete"
diff --git a/common/jessie-arm64.sh b/common/jessie-arm64.sh
new file mode 100755
index 0000000..4ac9f8d
--- /dev/null
+++ b/common/jessie-arm64.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+set -e
+
+# define before sourcing common
+WHO=`whoami`
+USER='user/live'
+SUITE='jessie'
+SIZE='5G'
+ARCH='arm64'
+BINFMT='/usr/bin/qemu-aarch64-static'
+SHARE_PATH='/usr/share/vmdebootstrap/common'
+# needs a path for arch and task desktop
+IMAGE_PATH='.'
+
+. ${SHARE_PATH}/customise.lib
+
+sudo vmdebootstrap \
+ ${BASE_OPTS} --user ${USER} \
+ --size ${SIZE} \
+ --arch ${ARCH} \
+ --foreign ${BINFMT} \
+ --no-extlinux \
+ --grub --use-uefi \
+ --package dosfstools \
+ --distribution ${SUITE} \
+ --customize "${SHARE_PATH}/${SUITE}-${ARCH}-hook.sh" \
+ --image ${IMAGE_PATH}/${SUITE}-${ARCH}.img \
+ "$@"
+
+# report results and check we have something valid.
+ls -l ${IMAGE_PATH}/${SUITE}-${ARCH}.img
+md5sum ${IMAGE_PATH}/${SUITE}-${ARCH}.img
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..551fe06
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,153 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/vmdebootstrap.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/vmdebootstrap.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/vmdebootstrap"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/vmdebootstrap"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/doc/conf.py b/doc/conf.py
new file mode 100644
index 0000000..5f3056c
--- /dev/null
+++ b/doc/conf.py
@@ -0,0 +1,213 @@
+# -*- coding: utf-8 -*-
+#
+# vmdebootstrap documentation build configuration file
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import subprocess
+import sys
+import os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.append(os.path.abspath('..'))
+
+# -- General configuration -----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.doctest',
+ 'sphinx.ext.intersphinx',
+ 'sphinx.ext.todo',
+ 'sphinx.ext.coverage',
+ 'sphinx.ext.viewcode']
+
+# Configuration for sphinx.ext.todo
+todo_include_todos = True
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+# source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'vmdebootstrap'
+copyright = u'2015 Neil Williams'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = subprocess.Popen(['python', 'setup.py', '-V'], cwd=r'..', stdout=subprocess.PIPE).stdout.read().rstrip()
+# The full version, including alpha/beta/rc tags.
+release = version
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+# language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+# today = ''
+# Else, today_fmt is used as the format for a strftime call.
+# today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+# unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = []
+
+exclude_patterns = ['pages/reference-architecture', 'tables.rst']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+# default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+# add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+# add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+# show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+# modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+# html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+# html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+# html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+# html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+# html_logo = ""
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+# html_favicon = "favicon.ico"
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+# html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+# html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+# html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+# html_additional_pages = {}
+
+# If false, no module index is generated.
+# html_use_modindex = True
+
+# If false, no index is generated.
+# html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+# html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+html_show_sphinx = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+# html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+# html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'vmdebootstrapdocs'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+# latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+# latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'vmdebootstrap.tex', u'vmdebootstrap',
+ u'Neil Williams', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+# latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+# latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+# latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+# latex_appendices = []
+
+# If false, no module index is generated.
+# latex_use_modindex = True
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'http://docs.python.org/': None}
diff --git a/doc/devel.rst b/doc/devel.rst
new file mode 100644
index 0000000..161133c
--- /dev/null
+++ b/doc/devel.rst
@@ -0,0 +1,133 @@
+Developing live scripts and customisation hooks
+===============================================
+
+:file:`vmdebootstrap` is available in git and in Debian. The live image
+processing requires several options which are only available in
+versions of vmdebootstrap newer than version 0.5-2 available in
+Debian Jessie. vmdebootstrap is able to run on Stretch, Jessie or
+Wheezy and able to build any suite supported by debootstrap (and
+and architecture supported by QEMU) on any of those versions of
+Debian. This leads to a large matrix of build options and hooks.
+
+Calls to vmdebootstrap are best scripted. See the README for notes
+on which options and settings are required to make a live image using
+vmdebootstrap.
+
+The 'common' library contains functions and parameters which need to
+be used in *all* images, including::
+
+ cleanup
+ export_env
+ mount_proc
+ disable_daemons
+ prepare_apt_source
+ remove_daemon_block
+ replace_apt_source
+
+.. _cleanup:
+
+cleanup
+-------
+
+Ensure that :file:`proc` is unmounted even if the customisation fails or else
+the image build itself will fail to unmount :file:`${rootdir}`.
+
+.. _export_env:
+
+export_env
+----------
+
+Debconf needs to be set in noninteractive mode to prevent the image
+build waiting for keyboard intervention.
+
+.. _mount_proc:
+
+mount_proc
+----------
+
+Many packages require ``/proc`` to be mounted inside the chroot during
+installation - cleanup must be specified as a trap if ``mount_proc`` is
+used::
+
+ trap cleanup 0
+
+.. _disable_daemons:
+
+disable_daemons
+---------------
+
+Packages which include a daemon **must not** start those daemons inside
+the chroot as this will make the ``${rootdir}`` appear busy and the unmount
+will fail. All scripts need to use :ref:`remove_daemon_block` after package
+installation is complete.
+
+.. _prepare_apt_source:
+
+prepare_apt_source
+------------------
+
+The final Debian mirror location is not useful during the build as there
+is a faster mirror available during the build. This function moves the
+specified mirror file aside and uses the nearby mirror. Always use with
+:ref:`replace_apt_source`.
+
+.. _remove_daemon_block:
+
+remove_daemon_block
+-------------------
+
+After using :ref:`disable_daemons`, a policy script remains which needs
+to be removed to allow daemons to start normally when the image itself
+is booted. Use ``remove_daemon_block`` as the next step once package
+installation is complete.
+
+.. _replace_apt_source:
+
+replace_apt_source
+------------------
+
+Requires :ref:`prepare_apt_source` to have been run first, then undoes the
+change to the apt sources and cleans up.
+
+.. index: task
+
+.. _task_packages:
+
+TASK_PACKAGES
+-------------
+
+Some task packages are useful to all images, these are specified here
+and should be included in the set of packages to be installed using
+all customisation scripts.
+
+.. index: extra
+
+.. _extra_packages:
+
+EXTRA_PACKAGES
+--------------
+
+Packages which are not part of an existing task but which are useful for
+all images and should be included in the set of packages to be installed
+using all customisation scripts.
+
+.. _new_architectures:
+
+New architectures
+-----------------
+
+The precursor to new architecture support is :file:`vmdebootstrap` support. A
+default :file:`vmdebootstrap` (with no customisation hook) will need to work
+and any changes to the settings (e.g. ``--no-kernel --package linux-myarch-flavour``)
+There is default support for some architectures in :file:`vmdebootstrap`
+(e.g. armhf architectures select the armmp kernel), such support depends
+on how many users would use the same kernel compared to the number of
+possible kernel flavours for that architecture.
+
+For a Debian LIVE image, **all** packages must exist in Debian.
+
+The package list also needs a review - some packages will simply not
+exist for the specified architecture. Some architecture-specific packages
+need to be added, so each architecture has a particular customisation
+hook script. Package names frequently change between releases, so the
+package selection needs to be suite specific as well.
diff --git a/doc/index.rst b/doc/index.rst
new file mode 100644
index 0000000..8b15fd9
--- /dev/null
+++ b/doc/index.rst
@@ -0,0 +1,9 @@
+VMDebootstrap
+#############
+
+.. toctree::
+ :maxdepth: 2
+
+ overview.rst
+ live.rst
+ devel.rst
diff --git a/doc/live.rst b/doc/live.rst
new file mode 100644
index 0000000..99d9346
--- /dev/null
+++ b/doc/live.rst
@@ -0,0 +1,112 @@
+vmdebootstrap for creation of live images
+=========================================
+
+Role of vmdebootstrap
+---------------------
+
+``vmdebootstrap`` is limited to the role of generating the rootfs for
+the live image - the architecture-specific part. ``vmdebootstrap`` then
+copies the kernel files out of the rootfs and runs ``mksquashfs``.
+
+The files in the directory specified by the ``--squash`` option are not
+themselves sufficient to create a live image. Remaining steps include
+configuration of grub and EFI, addition of other components (like a menu
+or Debian Installer) and packaging up into a isohybrid image.
+
+vmdebootstrap features
+======================
+
+#. vmdebootstrap has explicit support for foreign architecture
+ bootstraps using qemu static binformat handling as well as
+ support for Debian releases from wheezy onwards.
+
+ * This is **not** intended to provide support for all packages
+ in the Debian archive. Some packages do not install correctly
+ with binfmt handling and ``vmdebootstrap`` should be run natively
+ when the package list is to include these packages.
+
+#. vmdebootstrap can support adding specific packages but a
+ simpler approach is to use the existing task-* packages and
+ only add packages manually where explicitly needed for a live
+ image, using the ``live-support`` package.
+
+#. debian-cd runs vmdebootstrap inside a VM in a similar manner to
+ how debian-live currently operates, as both debian-live and
+ vmdebootstrap need to call debootstrap which involves making
+ device nodes and needs to run as root. This outer VM is specific
+ for the release of Debian being built. vmdebootstrap can build
+ older releases and it may be necessary to use a newer version of
+ vmdebootstrap than is present in jessie to build jessie and to
+ use that version to build wheezy.
+
+#. vmdebootstrap uses a single config file per image type and each
+ config file can have a single customisation script. The config
+ file specifies the architecture of the image and the binformat
+ handler for that architecture (if used), so the customisation hook
+ script can be architecture-specific.
+
+#. Customisation hook scripts are shell scripts which will be passed
+ a single parameter - the directory which represents the root
+ directory of the final image. These scripts can use standard shell
+ support to include other common functions or call out to utilities
+ known to be installed in the outer VM running vmdebootstrap.
+
+#. Customisation hooks clearly need to live in a VCS - examples will
+ be carried in the ``examples`` directory of ``vmdebootstrap`` and
+ in the ``/usr/share/vmdebootstrap/examples`` directory. Working
+ scripts based on these examples will likely be within the debian-cd
+ git repo.
+
+#. Although vmdebootstrap does have architecture support, the deciding
+ factor is the availability of a working default kernel for the images
+ built for that architecture and how to configure the bootloader(s) to
+ provide the relevant dtb where needed.
+
+#. Unlike standard vmdebootstrap example scripts, the scripts calling
+ vmdebootstrap itself do not need to use sudo as the call is made inside
+ the outer VM which already has root. Using sudo will work but will output
+ a message: sudo: unable to resolve host JESSIE-debian-live-builder
+
+#. The building of live images doesn't appear to need changes in the
+ vmdebootstrap package itself. The changes to isolinux to add the menu config,
+ splash screen and to provide access to the install menus can all be done
+ after the generation of the squashfs.
+
+#. Remember to use ``http://cdbuilder.debian.org/debian/`` for the bootstrap
+ operations (--mirror option) and ``http://httpredir.debian.org/debian`` for
+ the mirror to be used after the image has booted (--apt-mirror option).
+
+#. Ensure that a user is created (``--user 'user/live'``) and that ``sudo`` is
+ added to the set of packages to install and the --sudo option is passed
+ to vmdebootstrap to ensure that the user is added to the sudo group. The
+ root user password should also be locked (--lock-root-password).
+
+ * Consider using a blank password and enforcing a password to be set
+ upon login for those images which can support this.
+
+#. Installing task packages using debootstrap **omits** ``Recommended`` packages,
+ resulting in a much smaller image which is not expected for a live image.
+ Task selection needs to be done in the customisation hook using the chroot
+ command, at which point the default apt configuration will install the
+ Recommends as well as the Depends packages. Ensure that the image size is
+ big enough.
+
+#. When installing using apt in the customisation script, ensure that the
+ debconf non-interactive settings are exported to prevent the install
+ waiting for keyboard interaction. ``DEBIAN_FRONTEND=noninteractive``
+
+#. The customisation script needs to mount proc (and possibly other
+ locations like ``/sys/``, ``/dev/`` and ``/dev/pts/``) before
+ starting the apt install.
+
+#. Calls to apt should also not output the progress bar but the actual package
+ installation steps should be logged.
+
+#. Move the image apt sources aside and set the cdimage apt source instead.
+ Use ``http://cdbuilder.debian.org/debian/`` Then, at the end of the
+ customisation hook, remove that source and replace the original.
+
+#. ``mksquashfs`` can fail without indication of why and when it does, the image
+ file can be 4Kb or so of junk. ``vmdebootstrap`` will fail if the
+ squashfs output is less than 1MB. This can occur if the drive runs
+ out of space but squashfs does not report an error.
diff --git a/doc/overview.rst b/doc/overview.rst
new file mode 100644
index 0000000..6ed05fa
--- /dev/null
+++ b/doc/overview.rst
@@ -0,0 +1,392 @@
+VMDebootstrap
+#############
+
+.. index:: purpose
+
+.. _purpose:
+
+Purpose
+*******
+
+vmdebootstrap is a helper to install basic Debian system into virtual
+disk image. It wraps **debootstrap**. You need to run :file:`vmdebootstrap`
+as root. If the ``--verbose`` option is not used, no output will be
+sent to the command line. If the ``--log`` option is not used, no
+output will be sent to any log files either.
+
+To use the image, you probably want to create a virtual machine using
+your preferred virtualization technology, such as file:`kvm` or
+file:`qemu`. Configure the virtual machine to use the image you've
+created. Then start the virtual machine and log into it via its console
+to configure it. The image has an empty root password and will not have
+networking configured by default. Set the root password before you
+configure networking.
+
+.. _synopsis:
+
+.. index:: synopsis
+
+Synopsis
+********
+
+::
+
+ $ sudo vmdebootstrap --image=FILE --size=SIZE [--mirror=URL] [--distribution=NAME]
+
+Options
+*******
+
+ --output=FILE write output to FILE, instead of standard output
+ --verbose report what is going on
+ --image=FILE put created disk image in FILE
+ --size=SIZE create a disk image of size SIZE (1000000000)
+ --tarball=FILE tar up the disk's contents in FILE
+ --mirror=URL use MIRROR as package source (http://http.debian.net/debian/)
+ --arch=ARCH architecture to use (amd64) - if using an
+ architecture which the host system cannot execute,
+ ensure the --foreign option is also used.
+ --distribution=NAME release to use (defaults to stable). The release
+ needs to be a valid Debian or Ubuntu release name
+ or codename.
+ --debootstrapopts=OPTS
+ Supply options and arguments to ``debootstrap``,
+ separated by spaces.
+ e.g. --debootstrapopts="variant=buildd no-check-gpg components=main,contrib".
+ See **debootstrap (1)** for more information. This
+ option replaces the ``--variant`` support in
+ previous versions.
+ --package=PACKAGE install PACKAGE onto system
+ --custom-package=DEB install package in DEB file onto system (not
+ from mirror)
+ --no-kernel do not install a linux package
+ --kernel-package If --no-kernel is not used and the auto-selection
+ of the **linux-image-586** or **linux-image-armmp**
+ or **linux-image-$ARCH** package is not suitable,
+ the kernel package can be specified explicitly.
+ --enable-dhcp enable DHCP on eth0
+ --root-password=PASSWORD
+ set root password
+ --customize=SCRIPT run SCRIPT after setting up system. If the script
+ does not exist in the current working directory,
+ :file:`usr/share/vmdebootstrap/examples/` will be
+ checked as a fallback. The script needs to be
+ executable and is passed the root directory of the
+ debootstrap as the only argument. Use chroot if
+ you need to execute binaries within the
+ debootstrap.
+ --hostname=HOSTNAME set name to HOSTNAME (debian)
+ --user=USERSTRING create USER with PASSWORD. The USERSTRING needs to
+ be of the format: USER/PASSSWORD.
+ --owner=OWNER change the owner of the final image from root to
+ the specified user.
+ --serial-console configure image to use a serial console
+ --serial-console-command
+ set the command to manage the serial console which
+ will be appended to :file:`/etc/inittab`. Default
+ is ``/sbin/getty \-L ttyS0 115200 vt100``, resulting
+ in a line::
+
+ "S0:23:respawn:/sbin/getty \-L ttyS0 115200 vt100"
+
+ --sudo install sudo, and if user is created, add them to
+ sudo group
+ --bootsize=BOOTSIZE If specified, create a /boot partition of the given
+ size within the image. Debootstrapping will fail
+ if this is too small for the selected kernel
+ package and upgrading such a kernel package is
+ likely to need two or three times the space of the
+ installed kernel.
+ --boottype=FSTYPE Filesystem to use for the /boot partition. (default ext2)
+ --roottype=FSTYPE Filesystem to use for the / (root) partition. (default ext4)
+ --swap=SWAPSIZE If specified, create a swap partition of the given
+ size within the image. Debootstrapping will fail
+ if this results in a root partition which is too
+ small for the selected packages. The minimum swap
+ space is 256Mb as the default memory allocation
+ of QEMU is 128Mb. A default 1Gb image is not likely
+ to have enough space for a swap partition as well.
+ --foreign=PATH Path to the binfmt_handler to enable foreign support
+ in debootstrap. e.g. :file:`/usr/bin/qemu-arm-static`
+ Note: foreign debootstraps may take a signficant
+ amount of time to complete and that debootstrap will
+ retry five times if packages fail to install by default.
+ --no-extlinux Skip installation of extlinux. needs a customize script
+ or alternative bootloader to make the image bootable.
+ Useful for architectures where extlinux is not supportable.
+ Depending on how the image is to be booted, the --mbr
+ option may also be necessary with extlinux.
+ --squash=DIRECTORY Run mksquashfs against the rootfs using xz
+ compression - requires ``squashfs-tools`` to be installed.
+ The squashfs and other files needed to use the squashfs
+ to make a bootable system will be put into the specified directory.
+ The directory will contain a ``filesystem.squashfs``
+ as well as the top level contents of the ``boot/``
+ directory. (If using UEFI, the ``boot/efi`` directory
+ as well.) By default, ``mksquashfs`` is allowed to use
+ all processors which may result in high load. squashfs
+ can also have issues with large root filesystems. These
+ errors can result in truncated files. This is a known
+ bug in squashfs. ``vmdebootstrap`` will fail if the
+ squashed filesystem is less than 1MB.
+ --configure-apt Use the specified mirror and distribution to create a
+ suitable apt source inside the VM. Can be useful if
+ debootstrap fails to create it automatically.
+ --apt-mirror Use the specified mirror inside the image instead of the
+ mirror used to build the image. This is useful if you have
+ a local mirror to make building the image quicker but
+ the image needs to run even if that mirror is not available.
+ --grub Disable extlinux installation and configure grub2 instead.
+ grub2 will be added to the list of packages to install.
+ update-grub will be called once the debootstrap is
+ complete and grub-install will be called in the image.
+ --no-acpid Disable installation of acpid if not required, otherwise
+ acpid will be installed if --foreign is not used.
+ --pkglist Output a list of package names installed inside the image.
+ Useful if you need to track the relevant source packages
+ used inside the image for licence compliance.
+
+Configuration files and settings
+********************************
+
+ --dump-config write out the entire current configuration
+ --no-default-configs clear list of configuration files to read
+ --config=FILE add FILE to config files
+
+Logging
+*******
+
+ --log=FILE write log entries to FILE (default is to not write
+ log files at all); use "syslog" to log to system
+ log, or "none" to disable logging.
+ --log-level=LEVEL log at LEVEL, one of debug, info, warning, error,
+ critical, fatal (default: debug).
+ --log-max=SIZE rotate logs larger than SIZE, zero for never (default: 0)
+ --log-keep=N keep last N logs (10)
+ --log-mode=MODE set permissions of new log files to MODE (octal; default 0600)
+
+Peformance
+**********
+
+ --dump-memory-profile=METHOD
+ make memory profiling dumps using METHOD, which is one
+ of: none, simple, meliae, or heapy (default: simple)
+ --memory-dump-interval=SECONDS
+ make memory profiling dumps at least SECONDS apart
+
+.. index:: networking
+
+.. _networking:
+
+Networking
+**********
+
+The ``--enable-networking`` option uses the :file:`/etc/network/interfaces.d/`
+source directory, with the default settings for ``lo`` and ``eth0``
+being added to :file:`/etc/network/interfaces.d/setup`. Other networking
+configuration can be specified using a customisation script.
+Localhost settings would be::
+
+ auto lo
+ iface lo inet loopback
+
+If ``--enable-dhcp`` is specified, these settings are also included
+into :file:`/etc/network/interfaces.d/setup`::
+
+ auto eth0
+ iface eth0 inet dhcp
+
+.. index:: bootloaders
+
+.. _bootloaders:
+
+Bootloaders
+***********
+
+Unless the ``--no-extlinux`` or ``--grub`` options are specified, the
+image will use ``extlinux`` as a boot loader. ``bootsize`` is not
+recommended when using ``extlinux`` - use ``grub`` instead.
+
+.. _wheezy_grub:
+
+Versions of grub2 in wheezy
+===========================
+
+Grub2 in wheezy can fail to install in the VM, at which point
+:file:`vmdebootstrap` will fall back to ``extlinux``. It may still be
+possible to complete the installation of ``grub2`` after booting the
+VM as the problem may be related to the need to use loopback devices
+during the ``grub-install`` operation. Details of the error will appear
+in the vmdebootstrap log file, if enabled with the ``--log`` option.
+
+.. note:: **grub-legacy** is not supported.
+
+:file:`vmdebootstrap` also supports **EFI**. See :ref:`uefi`.
+
+Use ``--use-uefi`` to use ``grub-efi`` instead of ``grub-pc``. If the
+default 5Mb is not enough space, use the ``--esp-size`` option to
+specify a different size for the EFI partition. Registered firmware is
+not supported as it would need to be done after boot. If the system you
+are creating is for more than just a VM or live image, you will likely
+need a larger ESP, up to 500Mb.
+
+.. index: uefi
+
+.. _uefi:
+
+UEFI
+====
+
+UEFI support requires Grub and ``vmdebootstrap`` contains a configuration
+table of the UEFI components required for supported architectures.
+
+There are issues with running UEFI with QEMU on some architectures and
+a customisation script is available for amd64::
+
+ # vmdebootstrap --verbose --image jessie-uefi.img --grub --use-uefi \
+ --customize ./examples/qemu-efi-bochs-drm.sh
+
+``vmdebootstrap`` supports UEFI for images and for squashfs but the necessary
+behaviour is different. With an image, an ESP vfat partition is created.
+With squashfs, the EFI files will be copied into an ``efi/`` directory
+in the squashfs output directory instead.
+
+There is EFI firmware available to use with QEMU when testing images built
+using the UEFI support, but this software is in Debian non-free due to patent
+concerns. If you choose to install ``ovmf`` to test UEFI builds, a
+secondary change is also needed to symlink the provided ``OVMF.fd`` to
+the file required by QEMU: ``bios-256k.bin`` and then tell QEMU about
+the location of this file with the -L option::
+
+ $ qemu-system-x86_64 -L /usr/share/ovmf/ -machine accel=kvm \\
+ -m 4096 -smp 2 -drive format=raw,file=test.img
+
+To test the image, also consider using the ``qemu-wrapper.sh``::
+
+ $ /usr/share/vmdebootstrap/qemu-wrapper.sh jessie-uefi.img amd64 /usr/share/ovmf/
+
+.. index: uboot
+
+.. _uboot:
+
+UBoot
+=====
+
+UBoot needs manual configuration via the customisation hook scripts,
+typically support requires adding ``u-boot`` using ``--package`` and then
+copying or manipulating the relevant ``u-boot`` files in the customisation
+script. Examples are included for beaglebone-black.
+
+.. _installation_images:
+
+Installation images and virtual machines
+****************************************
+
+:file:`vmdebootstrap`` is aimed principally at creating virtual machines,
+not installers or prebuilt installation images. It is possible to create
+prebuilt installation images for some devices but this depends on the
+specific device. (A 'prebuilt installation image' is a single image file
+which can be written to physical media in a single operation and which
+allows the device to boot directly into a fully installed system - in
+a similar way to how a virtual machine would behave.)
+
+:file:`vmdebootstrap` assumes that all operations take place on a local
+image file, not a physical block device / removable media.
+
+:file:`vmdebootstrap` is intended to be used with tools like ``qemu`` on
+the command line to launch a new virtual machine. Not all devices have
+virtualisation support in hardware.
+
+This has implications for file:`u-boot` support in some cases. If the
+device can support reading the bootloader from a known partition, like
+the beaglebone-black, then :file:`vmdebootstrap` can provide space for
+the bootloader and the image will work as a prebuilt installation image.
+If the device expects that the bootloader exists at a specific offset
+and therefore requires that the bootloader is written as an image not
+as a binary which can be copied into an existing partition,
+:file:vmdebootstrap` is unable to include that bootloader image into
+the virtual machine image.
+
+The beagleboneblack.sh script in the examples/ directory provides a worked
+example to create a prebuilt installation image. However, the beagleboneblack
+itself does not support virtualisation in hardware, so is unable to launch
+a virtual machine. Other devices, like the Cubietruck or Wandboard need
+:file:`u-boot` at a predefined offset but can launch a virtual machine
+using ``qemu``, so the cubietruck and wandboard6q scripts in the
+examples/ directory relate to building images for virtual machines once
+the device is already installed and booted into a suitable kernel.
+
+It is possible to wrap :file:`vmdebootstrap` in such a way as to prepare
+a physical block device with a bootloader image and then deploy the
+bootstrap on top. However, this does require physical media to be
+inserted and removed each time the wrapper is executed. To do this, use
+the ``--tarball`` option instead of the ``--image`` option. Then setup
+the physical media and bootloader image manually, as required for the
+device, redefine the partitions to make space for the rootfs, create a
+filesystem on the physical media and unpack the :file:`vmdebootstrap`
+tarball onto that filesystem. Once you have working media, an image can be
+created using dd to read back from the media to an image file, allowing
+other media to be written with a single image file.
+
+Example
+*******
+
+To create an image for the stable release of Debian::
+
+ sudo vmdebootstrap --image test.img --size 1g \\
+ --log test.log --log-level debug --verbose \\
+ --mirror http://mirror.lan/debian/
+
+To run the test image, make sure it is writeable. Use the ``--owner``
+option to set mode 0644 for the specified user or use chmod manually::
+
+ sudo chmod a+w ./test.img
+
+Execute using qemu, e.g. on amd64 using qemu-system-x86_64::
+
+ qemu-system-x86_64 -drive format=raw,file=./test.img
+
+(This loads the image in a new window.) Note the use of ``-drive
+file=<img>,format=raw`` which is needed for newer versions of QEMU.
+
+There is a ``bin/qemu-wrapper.sh <image> <arch>`` script for simple
+calls where the ``--owner`` option is used, e.g.::
+
+ $ /usr/share/vmdebootstrap/qemu-wrapper.sh jessie.img amd64
+
+For further examples, including u-boot support for beaglebone-black,
+see ``/usr/share/vmdebootstrap/examples``
+
+Notes
+*****
+
+If you get problems with the bootstrap process, run a similar bootstrap
+call directly and chroot into the directory to investigate the failure.
+The actual debootstrap call is part of the vmdebootstrap logfile. The
+debootstrap logfile, if any, will be copied into your current working
+directory on error.
+
+:file:`debootstrap` will download all the apt archive files into the apt cache and does not
+remove them before starting the configuration of the packages. This can
+mean that debootstrap can fail due to a lack of space on the device if
+the VM size is small. vmdebootstrap cleans up the apt cache once debootstrap
+has finished but this doesn't help if the package unpack or configuration
+steps use up all of the space in the meantime. Avoid this problem by
+specifying a larger size for the image.
+
+.. note:: if you are also using a separate /boot partition in your options to
+ :file:`vmdebootstrap` it may well be the boot partition which needs
+ to be enlarged rather than the entire image.
+
+It is advisable to change the mirror in the example scripts to a mirror
+closer to your location, particularly if you need to do repeated builds.
+Use the --apt-mirror option to specify the apt mirror to be used inside
+the image, after boot.
+
+There are two types of examples for ARM devices available with
+:file:`vmdebootstrap`: prebuilt installation images (like the beaglebone-black) and virtual
+machine images (cubietruck and wandboard). ARM devices which do not
+support hypervisor mode and which also rely on the bootloader being at
+a specific offset instead of using a normal partition will
+**not** be supportable by vmdebootstrap. Similarly, devices which support
+hypervisor will only be supported using virtual machine images, unless
+the bootloader can be executed from a normal partition.
diff --git a/examples/README.txt b/examples/README.txt
index cde28cf..65e6bd9 100644
--- a/examples/README.txt
+++ b/examples/README.txt
@@ -30,3 +30,53 @@ CubieTruck
----------
Currently untested and lacking u-boot support.
+
+QEMU and EFI
+------------
+
+The bochs-drm kernel driver can be a problem when testing UEFI images,
+even headless ones, causing systemd to halt before a login prompt is
+offered.
+
+vmdebootstrap includes a simple customisation script which blacklists
+the bochs-drm module. Use, copy or extend this script for any image
+which uses UEFI and which should be testable using QEMU.
+
+To run UEFI with QEMU, the ovmf package needs to be installed from
+non-free (due to patent issues with VFAT) and the -L option used to
+QEMU to indicate the directory containing the EFI firmware to use.
+For amd64, the firmware installed by ovmf can need to be renamed
+(or symlinked) as /usr/share/ovmf/bios-256k.bin - then supply the
+-L option to QEMU:
+
+$ qemu-system-x86_64 -machine accel=kvm -m 4096 -smp 2 -drive format=raw,file=test.img -L /usr/share/ovmf/
+
+debootstrap and task packages
+-----------------------------
+
+debootstrap is designed to be a minimalist tool and vmdebootstrap
+wraps this support without substantial changes. Task packages are
+the simplest way to extend a minimal bootstrap to a more general
+purpose machine but there are limitations. debootstrap does not
+handle Recommended packages, so installing a task package using
+the --package support of vmdebootstrap (just as with the --include
+support of debootstrap itself) may result in a system with fewer
+packages installed than expected. Such systems can have the extra
+packages identified after boot using graphical tools like aptitude
+but to have all packages available during the creation of the image,
+a customisation hook is required. The hook simply needs to install
+the task package using apt instead of passing the task package to
+--package. This allows apt to do all the normal Recommends calculations
+and results in all of the extra packages being installed in one
+operation. However, the apt source used for this will be the apt
+source specified to vmdebootstrap for use after the system is booted,
+so you may also want to extend the hook to temporarily reinstate a
+local mirror (as used for the bootstrap phase) and put the other
+mirror back at the end of the hook.
+
+Examples of such hooks are available here:
+http://anonscm.debian.org/cgit/debian-cd/pettersson-live.git/tree/vmdebootstrap
+
+(These will need modification for other uses as the hooks expect
+a particular filesystem layout only useful for debian-cd.)
+
diff --git a/examples/auto-serial-console b/examples/auto-serial-console
deleted file mode 100755
index 9304e98..0000000
--- a/examples/auto-serial-console
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/sh -e
-
-[ -f /etc/default/autogetty ] && . /etc/default/autogetty
-
-[ 1 -gt $ENABLED ] && exit
-
-for arg in $(cat /proc/cmdline)
-do
- case $arg in
- console=*)
- tty=${arg#console=}
- tty=${tty#/dev/}
-
- case $tty in
- tty[a-zA-Z]* )
- PORT=${tty%%,*}
-
- # check for service which do something on this port
- if [ -f /etc/init/$PORT.conf ];then continue;fi
-
- tmp=${tty##$PORT,}
- SPEED=${tmp%%n*}
- BITS=${tmp##${SPEED}n}
-
- # 8bit serial is default
- [ -z $BITS ] && BITS=8
- [ 8 -eq $BITS ] && GETTY_ARGS="$GETTY_ARGS -8 "
-
- [ -z $SPEED ] && SPEED='115200,57600,38400,19200,9600'
-
- GETTY_ARGS="$AUTOGETTY_ARGS $GETTY_ARGS $SPEED $PORT"
- exec /sbin/getty $GETTY_ARGS
- esac
- esac
-done
diff --git a/examples/jessie-uefi-amd64.sh b/examples/jessie-uefi-amd64.sh
new file mode 100755
index 0000000..0578450
--- /dev/null
+++ b/examples/jessie-uefi-amd64.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+set -e
+
+WHO=`whoami`
+sudo vmdebootstrap --owner ${WHO} --verbose \
+ --sudo --lock-root-password \
+ --enable-dhcp --configure-apt \
+ --log amd64-uefi.log --log-level debug \
+ --size 5G --distribution jessie \
+ --grub --use-uefi \
+ --package task-xfce-desktop \
+ --customize ./examples/qemu-efi-bochs-drm.sh \
+ "$@"
+
diff --git a/examples/lava-submit.py b/examples/lava-submit.py
new file mode 100755
index 0000000..ae89758
--- /dev/null
+++ b/examples/lava-submit.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# lava-submit.py
+#
+# Copyright 2015 Neil Williams <codehelp@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+#
+#
+
+"""
+Example script - needs configuration (or use lava-tool).
+Expects to submit a pipeline job to a QEMU device, so
+ensure that the host supports these jobs.
+**This script is not to be expanded with argparse or CLI options for
+the username, token, hostname or image**.
+It is meant to be copied into something like jenkins to do the
+submission using hardcoded values, themselves hidden behind a UI.
+Other details like architecture, prompt and login information may
+need to come from a config file or command line.
+LAVA needs a serial console to tell whether the VM booted or not.
+Larger images will need the LAVA device to have more memory available.
+"""
+
+import os
+import yaml
+import xmlrpclib
+
+# Constants for each particular script configuration.
+USERNAME = ""
+TOKEN = ""
+HOSTNAME = ""
+IMAGE = ""
+ARCH = ""
+
+
+def job(image):
+ """ Bare bones YAML job definition """
+ job_def = {
+ 'actions': [{
+ 'deploy': {'images': {'rootfs': {
+ 'image_arg': "-drive format=raw,file={rootfs}",
+ "url": "file://%s" % image
+ }},
+ 'os': 'debian',
+ 'timeout': {'minutes': 5},
+ 'to': 'tmpfs'}
+ }, {
+ 'boot': {
+ 'media': 'tmpfs',
+ 'prompts': ["root@debian:"],
+ 'auto_login': {
+ "login_prompt": "login:",
+ "username": "root"
+ },
+ 'method': 'qemu'}
+ }],
+ 'device_type': 'qemu',
+ 'job_name': 'vmdebootstrap-test',
+ 'priority': 'medium',
+ "context": { "arch": ARCH },
+ 'timeouts': {'action': {'minutes': 1}, 'job': {'minutes': 5}},
+ 'visibility': 'public'}
+ return job_def
+
+
+def main():
+ """ submit using XMLRPC """
+ image = os.path.realpath(IMAGE)
+ url = "http://%s:%s@%s//RPC2" % (USERNAME, TOKEN, HOSTNAME)
+ server = xmlrpclib.ServerProxy(url)
+ job_id = server.scheduler.submit_job(yaml.dump(job(image)))
+ print job_id
+ return 0
+
+if __name__ == '__main__':
+ import sys
+ sys.exit(main())
diff --git a/examples/lava.sh b/examples/lava.sh
deleted file mode 100755
index 06da069..0000000
--- a/examples/lava.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/sh
-
-set -e
-
-rootdir=$1
-
-cp auto-serial-console $rootdir/bin/
diff --git a/examples/qemu-efi-bochs-drm.sh b/examples/qemu-efi-bochs-drm.sh
new file mode 100755
index 0000000..d7b7a58
--- /dev/null
+++ b/examples/qemu-efi-bochs-drm.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+set -e
+
+rootdir=$1
+
+echo "blacklist bochs-drm" > $rootdir/etc/modprobe.d/qemu-blacklist.conf
+
diff --git a/man/Makefile b/man/Makefile
new file mode 100644
index 0000000..441b050
--- /dev/null
+++ b/man/Makefile
@@ -0,0 +1,153 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/LavaDispatcher.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/LavaDispatcher.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/LavaDispatcher"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/LavaDispatcher"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/man/conf.py b/man/conf.py
new file mode 100644
index 0000000..6913777
--- /dev/null
+++ b/man/conf.py
@@ -0,0 +1,211 @@
+# -*- coding: utf-8 -*-
+#
+# vmdebootstrap documentation build configuration file
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys
+import os
+import subprocess
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.append(os.path.abspath('..'))
+
+# -- General configuration -----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage']
+
+# Configuration for sphinx.ext.todo
+todo_include_todos = True
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = []
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+# source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'vmdebootstrap'
+copyright = u'2015, Neil Williams'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = subprocess.Popen(['python', 'setup.py', '-V'], cwd=r'..', stdout=subprocess.PIPE).stdout.read()
+# The full version, including alpha/beta/rc tags.
+release = version
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+# language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+# today = ''
+# Else, today_fmt is used as the format for a strftime call.
+# today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+# unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = []
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+# default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+# add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+# add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+# show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+# modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+# html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+# html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+# html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+# html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+# html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+# html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+# html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+# html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+# html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+# html_additional_pages = {}
+
+# If false, no module index is generated.
+# html_use_modindex = True
+
+# If false, no index is generated.
+# html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+# html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+# html_show_sourcelink = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+# html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+# html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'vmdebootstrap'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+# latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+# latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'vmdebootstrap.tex', u'VMDebootstrap Documentation',
+ u'Neil Williams', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+# latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+# latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+# latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+# latex_appendices = []
+
+# If false, no module index is generated.
+# latex_use_modindex = True
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'http://docs.python.org/': None}
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('vmdebootstrap', 'vmdebootstrap', u'install basic Debian system into virtual disk image',
+ [u'Neil Williams'], 8),
+]
diff --git a/man/index.rst b/man/index.rst
new file mode 100644
index 0000000..4d95c9c
--- /dev/null
+++ b/man/index.rst
@@ -0,0 +1,4 @@
+.. toctree::
+ :maxdepth: 2
+
+ vmdebootstrap.rst
diff --git a/man/vmdebootstrap.rst b/man/vmdebootstrap.rst
new file mode 100644
index 0000000..90fad4c
--- /dev/null
+++ b/man/vmdebootstrap.rst
@@ -0,0 +1,343 @@
+VMDebootstrap
+#############
+
+Purpose
+*******
+
+vmdebootstrap is a helper to install basic Debian system into virtual
+disk image. It wraps **debootstrap**. You need to run :file:`vmdebootstrap`
+as root. If the ``--verbose`` option is not used, no output will be
+sent to the command line. If the ``--log`` option is not used, no
+output will be sent to any log files either.
+
+To use the image, you probably want to create a virtual machine using
+your preferred virtualization technology, such as file:`kvm` or
+file:`qemu`. Configure the virtual machine to use the image you've
+created. Then start the virtual machine and log into it via its console
+to configure it. The image has an empty root password and will not have
+networking configured by default. Set the root password before you
+configure networking.
+
+Synopsis
+********
+
+::
+
+ $ sudo vmdebootstrap --image=FILE --size=SIZE [--mirror=URL] [--distribution=NAME]
+
+Options
+*******
+
+ --output=FILE write output to FILE, instead of standard output
+ --verbose report what is going on
+ --image=FILE put created disk image in FILE
+ --size=SIZE create a disk image of size SIZE (1000000000)
+ --tarball=FILE tar up the disk's contents in FILE
+ --mirror=URL use MIRROR as package source (http://http.debian.net/debian/)
+ --arch=ARCH architecture to use (amd64) - if using an
+ architecture which the host system cannot execute,
+ ensure the --foreign option is also used.
+ --distribution=NAME release to use (defaults to stable). The release
+ needs to be a valid Debian or Ubuntu release name
+ or codename.
+ --debootstrapopts="command=option,command=option"
+ Supply options and arguments to ``debootstrap``,
+ separated by spaces.
+ e.g. --debootstrapopts="variant=buildd no-check-gpg components=main,contrib".
+ See **debootstrap (1)** for more information. This
+ option replaces the ``--variant`` support in
+ previous versions.
+ --package=PACKAGE install PACKAGE onto system
+ --custom-package=DEB install package in DEB file onto system (not
+ from mirror)
+ --no-kernel do not install a linux package
+ --kernel-package If --no-kernel is not used and the auto-selection
+ of the **linux-image-586** or **linux-image-armmp**
+ or **linux-image-$ARCH** package is not suitable,
+ the kernel package can be specified explicitly.
+ --enable-dhcp enable DHCP on eth0
+ --root-password=PASSWORD
+ set root password
+ --customize=SCRIPT run SCRIPT after setting up system. If the script
+ does not exist in the current working directory,
+ :file:`usr/share/vmdebootstrap/examples/` will be
+ checked as a fallback. The script needs to be
+ executable and is passed the root directory of the
+ debootstrap as the only argument. Use chroot if
+ you need to execute binaries within the
+ debootstrap.
+ --hostname=HOSTNAME set name to HOSTNAME (debian)
+ --user=USERSTRING create USER with PASSWORD. The USERSTRING needs to
+ be of the format: USER/PASSSWORD.
+ --owner=OWNER change the owner of the final image from root to
+ the specified user.
+ --serial-console configure image to use a serial console
+ --serial-console-command
+ set the command to manage the serial console which
+ will be appended to :file:`/etc/inittab`. Default
+ is ``/sbin/getty \-L ttyS0 115200 vt100``, resulting
+ in a line::
+
+ "S0:23:respawn:/sbin/getty \-L ttyS0 115200 vt100"
+
+ --sudo install sudo, and if user is created, add them to
+ sudo group
+ --bootsize=BOOTSIZE If specified, create a /boot partition of the given
+ size within the image. Debootstrapping will fail
+ if this is too small for the selected kernel
+ package and upgrading such a kernel package is
+ likely to need two or three times the space of the
+ installed kernel.
+ --boottype=FSTYPE Filesystem to use for the /boot partition. (default ext2)
+ --roottype=FSTYPE Filesystem to use for the / (root) partition. (default ext4)
+ --swap=SWAPSIZE If specified, create a swap partition of the given
+ size within the image. Debootstrapping will fail
+ if this results in a root partition which is too
+ small for the selected packages. The minimum swap
+ space is 256Mb as the default memory allocation
+ of QEMU is 128Mb. A default 1Gb image is not likely
+ to have enough space for a swap partition as well.
+ --foreign=PATH Path to the binfmt_handler to enable foreign support
+ in debootstrap. e.g. :file:`/usr/bin/qemu-arm-static`
+ Note: foreign debootstraps may take a signficant
+ amount of time to complete and that debootstrap will
+ retry five times if packages fail to install by default.
+ --no-extlinux Skip installation of extlinux. needs a customize script
+ or alternative bootloader to make the image bootable.
+ Useful for architectures where extlinux is not supportable.
+ Depending on how the image is to be booted, the --mbr
+ option may also be necessary with extlinux.
+ --squash=DIRECTORY Run mksquashfs against the rootfs using xz
+ compression - requires ``squashfs-tools`` to be installed.
+ The squashfs and other files needed to use the squashfs
+ to make a bootable system will be put into the specified directory.
+ The directory will contain a ``filesystem.squashfs``
+ as well as the top level contents of the ``boot/``
+ directory. (If using UEFI, the ``boot/efi`` directory
+ as well.) By default, ``mksquashfs`` is allowed to use
+ all processors which may result in high load. squashfs
+ can also have issues with large root filesystems. These
+ errors can result in truncated files. This is a known
+ bug in squashfs. ``vmdebootstrap`` will fail if the
+ squashed filesystem is less than 1MB.
+ --configure-apt Use the specified mirror and distribution to create a
+ suitable apt source inside the VM. Can be useful if
+ debootstrap fails to create it automatically.
+ --apt-mirror Use the specified mirror inside the image instead of the
+ mirror used to build the image. This is useful if you have
+ a local mirror to make building the image quicker but
+ the image needs to run even if that mirror is not available.
+ --grub Disable extlinux installation and configure grub2 instead.
+ grub2 will be added to the list of packages to install.
+ update-grub will be called once the debootstrap is
+ complete and grub-install will be called in the image.
+ --no-acpid Disable installation of acpid if not required, otherwise
+ acpid will be installed if --foreign is not used.
+ --pkglist Output a list of package names installed inside the image.
+ Useful if you need to track the relevant source packages
+ used inside the image for licence compliance.
+
+Configuration files and settings
+********************************
+
+ --dump-config write out the entire current configuration
+ --no-default-configs clear list of configuration files to read
+ --config=FILE add FILE to config files
+
+Logging
+*******
+
+ --log=FILE write log entries to FILE (default is to not write
+ log files at all); use "syslog" to log to system
+ log, or "none" to disable logging.
+ --log-level=LEVEL log at LEVEL, one of debug, info, warning, error,
+ critical, fatal (default: debug).
+ --log-max=SIZE rotate logs larger than SIZE, zero for never (default: 0)
+ --log-keep=N keep last N logs (10)
+ --log-mode=MODE set permissions of new log files to MODE (octal; default 0600)
+
+Peformance
+**********
+
+ --dump-memory-profile=METHOD
+ make memory profiling dumps using METHOD, which is one
+ of: none, simple, meliae, or heapy (default: simple)
+ --memory-dump-interval=SECONDS
+ make memory profiling dumps at least SECONDS apart
+
+Networking
+**********
+
+The ``--enable-networking`` option uses the :file:`/etc/network/interfaces.d/`
+source directory, with the default settings for ``lo`` and ``eth0``
+being added to :file:`/etc/network/interfaces.d/setup`. Other networking
+configuration can be specified using a customisation script.
+Localhost settings would be::
+
+ auto lo
+ iface lo inet loopback
+
+If ``--enable-dhcp`` is specified, these settings are also included
+into :file:`/etc/network/interfaces.d/setup`::
+
+ auto eth0
+ iface eth0 inet dhcp
+
+Bootloaders
+***********
+
+Unless the ``--no-extlinux`` or ``--grub`` options are specified, the
+image will use ``extlinux`` as a boot loader. ``bootsize`` is not
+recommended when using ``extlinux`` - use ``grub`` instead.
+
+Versions of grub2 in wheezy
+===========================
+
+Grub2 in wheezy can fail to install in the VM, at which point
+:file:`vmdebootstrap` will fall back to ``extlinux``. It may still be
+possible to complete the installation of ``grub2`` after booting the
+VM as the problem may be related to the need to use loopback devices
+during the ``grub-install`` operation. Details of the error will appear
+in the vmdebootstrap log file, if enabled with the ``--log`` option.
+
+.. note:: **grub-legacy** is not supported.
+
+:file:`vmdebootstrap` also supports **EFI**.
+
+Use ``--use-uefi`` to use ``grub-efi`` instead of ``grub-pc``. If the
+default 5Mb is not enough space, use the ``--esp-size`` option to
+specify a different size for the EFI partition. Registered firmware is
+not supported as it would need to be done after boot. If the system you
+are creating is for more than just a VM or live image, you will likely
+need a larger ESP, up to 500Mb.
+
+UBoot
+=====
+
+UBoot needs manual configuration via the customisation hook scripts,
+typically support requires adding ``u-boot`` using ``--package`` and then
+copying or manipulating the relevant ``u-boot`` files in the customisation
+script. Examples are included for beaglebone-black.
+
+Installation images and virtual machines
+****************************************
+
+:file:`vmdebootstrap`` is aimed principally at creating virtual machines,
+not installers or prebuilt installation images. It is possible to create
+prebuilt installation images for some devices but this depends on the
+specific device. (A 'prebuilt installation image' is a single image file
+which can be written to physical media in a single operation and which
+allows the device to boot directly into a fully installed system - in
+a similar way to how a virtual machine would behave.)
+
+:file:`vmdebootstrap` assumes that all operations take place on a local
+image file, not a physical block device / removable media.
+
+:file:`vmdebootstrap` is intended to be used with tools like ``qemu`` on
+the command line to launch a new virtual machine. Not all devices have
+virtualisation support in hardware.
+
+This has implications for file:`u-boot` support in some cases. If the
+device can support reading the bootloader from a known partition, like
+the beaglebone-black, then :file:`vmdebootstrap` can provide space for
+the bootloader and the image will work as a prebuilt installation image.
+If the device expects that the bootloader exists at a specific offset
+and therefore requires that the bootloader is written as an image not
+as a binary which can be copied into an existing partition,
+:file:vmdebootstrap` is unable to include that bootloader image into
+the virtual machine image.
+
+The beagleboneblack.sh script in the examples/ directory provides a worked
+example to create a prebuilt installation image. However, the beagleboneblack
+itself does not support virtualisation in hardware, so is unable to launch
+a virtual machine. Other devices, like the Cubietruck or Wandboard need
+:file:`u-boot` at a predefined offset but can launch a virtual machine
+using ``qemu``, so the cubietruck and wandboard6q scripts in the
+examples/ directory relate to building images for virtual machines once
+the device is already installed and booted into a suitable kernel.
+
+It is possible to wrap :file:`vmdebootstrap` in such a way as to prepare
+a physical block device with a bootloader image and then deploy the
+bootstrap on top. However, this does require physical media to be
+inserted and removed each time the wrapper is executed. To do this, use
+the ``--tarball`` option instead of the ``--image`` option. Then setup
+the physical media and bootloader image manually, as required for the
+device, redefine the partitions to make space for the rootfs, create a
+filesystem on the physical media and unpack the :file:`vmdebootstrap`
+tarball onto that filesystem. Once you have working media, an image can be
+created using dd to read back from the media to an image file, allowing
+other media to be written with a single image file.
+
+Example
+*******
+
+To create an image for the stable release of Debian::
+
+ sudo vmdebootstrap --image test.img --size 1g \\
+ --log test.log --log-level debug --verbose \\
+ --mirror http://mirror.lan/debian/
+
+To run the test image, make sure it is writeable. Use the ``--owner``
+option to set mode 0644 for the specified user or use chmod manually::
+
+ sudo chmod a+w ./test.img
+
+Execute using qemu, e.g. on amd64 using qemu-system-x86_64::
+
+ qemu-system-x86_64 -drive format=raw,file=./test.img
+
+(This loads the image in a new window.) Note the use of ``-drive
+file=<img>,format=raw`` which is needed for newer versions of QEMU.
+
+There is a ``bin/qemu-wrapper.sh <image> <arch>`` script for simple
+calls where the ``--owner`` option is used, e.g.::
+
+ $ /usr/share/vmdebootstrap/qemu-wrapper.sh jessie.img amd64
+
+There is EFI firmware available to use with QEMU when testing images built
+using the UEFI support, but this software is in Debian non-free due to patent
+concerns. If you choose to install ``ovmf`` to test UEFI builds, a
+secondary change is also needed to symlink the provided ``OVMF.fd`` to
+the file required by QEMU: ``bios-256k.bin`` and then tell QEMU about
+the location of this file with the -L option::
+
+ $ qemu-system-x86_64 -L /usr/share/ovmf/ -machine accel=kvm \\
+ -m 4096 -smp 2 -drive format=raw,file=test.img
+
+For further examples, including u-boot support for beaglebone-black,
+see ``/usr/share/vmdebootstrap/examples``
+
+Notes
+*****
+
+If you get problems with the bootstrap process, run a similar bootstrap
+call directly and chroot into the directory to investigate the failure.
+The actual debootstrap call is part of the vmdebootstrap logfile. The
+debootstrap logfile, if any, will be copied into your current working
+directory on error.
+
+:file:`debootstrap` will download all the apt archive files into the apt cache and does not
+remove them before starting the configuration of the packages. This can
+mean that debootstrap can fail due to a lack of space on the device if
+the VM size is small. vmdebootstrap cleans up the apt cache once debootstrap
+has finished but this doesn't help if the package unpack or configuration
+steps use up all of the space in the meantime. Avoid this problem by
+specifying a larger size for the image.
+
+.. caution:: if you are also using a separate /boot partition in your options to
+ :file:`vmdebootstrap` it may well be the boot partition which needs
+ to be enlarged rather than the entire image.
+
+It is advisable to change the mirror in the example scripts to a mirror
+closer to your location, particularly if you need to do repeated builds.
+Use the --apt-mirror option to specify the apt mirror to be used inside
+the image, after boot.
+
+There are two types of examples for ARM devices available with
+:file:`vmdebootstrap`: prebuilt installation images (like the beaglebone-black) and virtual
+machine images (cubietruck and wandboard). ARM devices which do not
+support hypervisor mode and which also rely on the bootloader being at
+a specific offset instead of using a normal partition will
+**not** be supportable by vmdebootstrap. Similarly, devices which support
+hypervisor will only be supported using virtual machine images, unless
+the bootloader can be executed from a normal partition.
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..5ad1c57
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# setup.py
+#
+# Copyright 2015 Neil Williams <codehelp@debian.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from setuptools import setup, find_packages
+
+
+setup(
+ name='vmdebootstrap',
+ version='1.1',
+ description='Bootstrap Debian into a (virtual machine) disk image',
+ author='Neil Williams',
+ author_email='codehelp@debian.org',
+ url='http://git.liw.fi/cgi-bin/cgit/cgit.cgi/vmdebootstrap/',
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Console',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: System Administrators',
+ 'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)',
+ 'Programming Language :: Python',
+ 'Topic :: System :: Installation/Setup',
+ ],
+ packages=[
+ 'vmdebootstrap',
+ ],
+ package_data={
+ 'vmdebootstrap': ['README', 'COPYING', 'NEWS'],
+ },
+ install_requires=[
+ 'cliapp >= 1.20150829',
+ 'distro-info',
+ ],
+ scripts=['bin/vmdebootstrap']
+)
diff --git a/vmdebootstrap b/vmdebootstrap
deleted file mode 100755
index 67ea2c8..0000000
--- a/vmdebootstrap
+++ /dev/null
@@ -1,1095 +0,0 @@
-#! /usr/bin/python
-# Copyright 2011-2013 Lars Wirzenius
-# Copyright 2012 Codethink Limited
-# Copyright 2014-2015 Neil Williams <codehelp@debian.org>
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import cliapp
-import crypt
-import logging
-import os
-import re
-import sys
-import shutil
-import datetime
-import subprocess
-import tempfile
-import time
-from distro_info import DebianDistroInfo, UbuntuDistroInfo
-
-
-__version__ = '0.11'
-
-# pylint: disable=invalid-name,line-too-long,missing-docstring,too-many-branches
-
-
-class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-methods
-
- def __init__(self, progname=None, version=__version__, description=None, epilog=None):
- super(VmDebootstrap, self).__init__(progname, version, description, epilog)
- self.remove_dirs = []
- self.mount_points = []
- self.debian_info = DebianDistroInfo()
- self.ubuntu_info = UbuntuDistroInfo()
- self.bootdir = None
- self.efi_arch_table = {
- 'amd64': {
- 'removable': '/EFI/boot/bootx64.efi', # destination location
- 'install': '/EFI/debian/grubx64.efi', # package location
- 'package': 'grub-efi-amd64', # bootstrap package
- 'bin_package': 'grub-efi-amd64-bin', # binary only
- 'extra': 'i386', # architecture to add binary package
- 'exclusive': False, # only EFI supported for this arch.
- 'target': 'x86_64-efi', # grub target name
- },
- 'i386': {
- 'removable': '/EFI/boot/bootia32.efi',
- 'install': '/EFI/debian/grubia32.efi',
- 'package': 'grub-efi-ia32',
- 'bin_package': 'grub-efi-ia32-bin',
- 'extra': None,
- 'exclusive': False,
- 'target': 'i386-efi',
- },
- 'arm64': {
- 'removable': '/EFI/boot/bootaa64.efi',
- 'install': '/EFI/debian/grubaa64.efi',
- 'package': 'grub-efi-arm64',
- 'bin_package': 'grub-efi-arm64-bin',
- 'extra': None,
- 'exclusive': True,
- 'target': 'arm64-efi',
- }
- }
-
- def add_settings(self):
- default_arch = subprocess.check_output(
- ["dpkg", "--print-architecture"]).strip()
-
- self.settings.boolean(
- ['verbose'], 'report what is going on')
- self.settings.string(
- ['image'], 'put created disk image in FILE',
- metavar='FILE')
- self.settings.bytesize(
- ['size'],
- 'create a disk image of size SIZE (%default)',
- metavar='SIZE',
- default='1G')
- self.settings.bytesize(
- ['bootsize'],
- 'create boot partition of size SIZE (%default)',
- metavar='BOOTSIZE',
- default='0%')
- self.settings.string(
- ['boottype'],
- 'specify file system type for /boot/',
- default='ext2')
- self.settings.bytesize(
- ['bootoffset'],
- 'Space to leave at start of the image for bootloader',
- default='0')
- self.settings.boolean(
- ['use-uefi'],
- 'Setup image for UEFI boot',
- default=False)
- self.settings.bytesize(
- ['esp-size'],
- 'Size of EFI System Partition - requires use-uefi',
- default='5mib')
- self.settings.string(
- ['part-type'],
- 'Partition type to use for this image',
- default='msdos')
- self.settings.string(
- ['roottype'],
- 'specify file system type for /',
- default='ext4')
- self.settings.bytesize(
- ['swap'],
- 'create swap space of size SIZE (min 256Mb)')
- self.settings.string(
- ['foreign'],
- 'set up foreign debootstrap environment using provided program (ie binfmt handler)')
- self.settings.string(
- ['variant'],
- 'select debootstrap variant if not using the default [deprecated]')
- self.settings.string_list(
- ['debootstrapopts'],
- 'pass additional options to debootstrap'),
- self.settings.boolean(
- ['extlinux'],
- 'install extlinux?',
- default=True)
- self.settings.string(
- ['tarball'],
- "tar up the disk's contents in FILE",
- metavar='FILE')
- self.settings.string(
- ['apt-mirror'],
- 'configure apt to use MIRROR',
- metavar='URL')
- self.settings.string(
- ['mirror'],
- 'use MIRROR as package source (%default)',
- metavar='URL',
- default='http://http.debian.net/debian/')
- self.settings.string(
- ['arch'],
- 'architecture to use (%default)',
- metavar='ARCH',
- default=default_arch)
- self.settings.string(
- ['distribution'],
- 'release to use (%default)',
- metavar='NAME',
- default='stable')
- self.settings.string_list(
- ['package'],
- 'install PACKAGE onto system')
- self.settings.string_list(
- ['custom-package'],
- 'install package in DEB file onto system (not from mirror)',
- metavar='DEB')
- self.settings.boolean(
- ['no-kernel'],
- 'do not install a linux package')
- self.settings.string(
- ['kernel-package'],
- 'install PACKAGE instead of the default kernel package',
- metavar='PACKAGE')
- self.settings.boolean(
- ['enable-dhcp'],
- 'enable DHCP on eth0')
- self.settings.string(
- ['root-password'],
- 'set root password',
- metavar='PASSWORD')
- self.settings.boolean(
- ['lock-root-password'],
- 'lock root account so they cannot login?')
- self.settings.string(
- ['customize'],
- 'run SCRIPT after setting up system',
- metavar='SCRIPT')
- self.settings.string(
- ['hostname'],
- 'set name to HOSTNAME (%default)',
- metavar='HOSTNAME',
- default='debian')
- self.settings.string_list(
- ['user'],
- 'create USER with PASSWORD',
- metavar='USER/PASSWORD')
- self.settings.boolean(
- ['serial-console'],
- 'configure image to use a serial console')
- self.settings.string(
- ['serial-console-command'],
- 'command to manage the serial console, appended to /etc/inittab (%default)',
- metavar='COMMAND',
- default='/sbin/getty -L ttyS0 115200 vt100')
- self.settings.boolean(
- ['sudo'],
- 'install sudo, and if user is created, add them to sudo group')
- self.settings.string(
- ['owner'],
- 'the user who will own the image when the build is complete.')
- self.settings.boolean(
- ['squash'],
- 'use squashfs on the final image.')
- self.settings.boolean(
- ['configure-apt'],
- 'Create an apt source based on the distribution and mirror selected.')
- self.settings.boolean(
- ['mbr'],
- 'Run install-mbr (default if extlinux used)')
- self.settings.boolean(
- ['grub'],
- 'Install and configure grub2 - disables extlinux.')
- self.settings.boolean(
- ['sparse'],
- 'Do not fill the image with zeros to keep a sparse disk image',
- default=False)
- self.settings.boolean(
- ['pkglist'],
- 'Create a list of package names included in the image.')
- self.settings.boolean(
- ['no-acpid'],
- 'do not install the acpid package',
- default=False)
-
- def process_args(self, args): # pylint: disable=too-many-branches,too-many-statements
- if not self.settings['image'] and not self.settings['tarball']:
- raise cliapp.AppException(
- 'You must give disk image filename, or tarball filename')
- if self.settings['image'] and not self.settings['size']:
- raise cliapp.AppException(
- 'If disk image is specified, you must give image size.')
- if not self.debian_info.valid(self.settings['distribution']):
- if not self.ubuntu_info.valid(self.settings['distribution']):
- raise cliapp.AppException(
- '%s is not a valid Debian or Ubuntu suite or codename.'
- % self.settings['distribution'])
- if not self.settings['use-uefi'] and self.settings['esp-size'] != 5242880:
- raise cliapp.AppException(
- 'You must specify use-uefi for esp-size to have effect')
- if self.settings['arch'] in self.efi_arch_table and\
- self.efi_arch_table[self.settings['arch']]['exclusive'] and\
- not self.settings['use-uefi']:
- raise cliapp.AppException(
- 'Only UEFI is supported on %s' % self.settings['arch'])
- elif self.settings['use-uefi'] and self.settings['arch'] not in self.efi_arch_table:
- raise cliapp.AppException(
- '%s is not a supported UEFI architecture' % self.settings['arch'])
- if self.settings['use-uefi'] and (
- self.settings['bootsize'] or
- self.settings['bootoffset']):
- raise cliapp.AppException(
- 'A separate boot partition is not supported with UEFI')
-
- if self.settings['use-uefi'] and not self.settings['grub']:
- raise cliapp.AppException(
- 'UEFI without Grub is not supported.')
-
- # wheezy (which became oldstable on 04/25/2015) only had amd64 uefi
- if self.was_oldstable(datetime.date(2015, 4, 26)):
- if self.settings['use-uefi'] and self.settings['arch'] != 'amd64':
- raise cliapp.AppException(
- 'Only amd64 supports UEFI in Wheezy')
-
- if os.geteuid() != 0:
- sys.exit("You need to have root privileges to run this script.")
- rootdir = None
- try:
- rootdev = None
- roottype = self.settings['roottype']
- bootdev = None
- boottype = None
- if self.settings['image']:
- self.create_empty_image()
- self.partition_image()
- if self.settings['mbr'] or self.settings['extlinux']:
- self.install_mbr()
- (rootdev, bootdev, swapdev) = self.setup_kpartx()
- if self.settings['swap'] > 0:
- self.message("Creating swap space")
- self.runcmd(['mkswap', swapdev])
- self.mkfs(rootdev, fstype=roottype)
- rootdir = self.mount(rootdev)
- if self.settings['use-uefi']:
- self.bootdir = '%s/%s/%s' % (rootdir, 'boot', 'efi')
- logging.debug("bootdir:%s", self.bootdir)
- self.mkfs(bootdev, fstype='vfat')
- os.makedirs(self.bootdir)
- self.mount(bootdev, self.bootdir)
- elif bootdev:
- if self.settings['boottype']:
- boottype = self.settings['boottype']
- else:
- boottype = 'ext2'
- self.mkfs(bootdev, fstype=boottype)
- self.bootdir = '%s/%s' % (rootdir, 'boot/')
- os.mkdir(self.bootdir)
- self.mount(bootdev, self.bootdir)
- else:
- rootdir = self.mkdtemp()
- self.debootstrap(rootdir)
- self.set_hostname(rootdir)
- self.create_fstab(rootdir, rootdev, roottype, bootdev, boottype)
- self.install_debs(rootdir)
- self.set_root_password(rootdir)
- self.create_users(rootdir)
- self.remove_udev_persistent_rules(rootdir)
- self.setup_networking(rootdir)
- if self.settings['configure-apt'] or self.settings['apt-mirror']:
- self.configure_apt(rootdir)
- self.customize(rootdir)
- self.cleanup_apt_cache(rootdir)
- self.update_initramfs(rootdir)
-
- if self.settings['image']:
- if self.settings['use-uefi']:
- self.install_grub_uefi(rootdir)
- elif self.settings['grub']:
- self.install_grub2(rootdev, rootdir)
- elif self.settings['extlinux']:
- self.install_extlinux(rootdev, rootdir)
- self.append_serial_console(rootdir)
- self.optimize_image(rootdir)
- if self.settings['squash']:
- self.squash()
- if self.settings['pkglist']:
- self.list_installed_pkgs(rootdir)
-
- if self.settings['foreign']:
- os.unlink('%s/usr/bin/%s' %
- (rootdir, os.path.basename(self.settings['foreign'])))
-
- if self.settings['tarball']:
- self.create_tarball(rootdir)
-
- if self.settings['owner']:
- self.chown()
- except BaseException as e:
- self.message('EEEK! Something bad happened...')
- if rootdir:
- db_log = os.path.join(rootdir, 'debootstrap', 'debootstrap.log')
- if os.path.exists(db_log):
- shutil.copy(db_log, os.getcwd())
- self.message(e)
- self.cleanup_system()
- raise
- else:
- self.cleanup_system()
-
- def message(self, msg):
- logging.info(msg)
- if self.settings['verbose']:
- print msg
-
- def runcmd(self, argv, stdin='', ignore_fail=False, env=None, **kwargs):
- logging.debug('runcmd: %s %s %s', argv, env, kwargs)
- p = subprocess.Popen(argv, stdin=subprocess.PIPE,
- stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- env=env, **kwargs)
- out, err = p.communicate(stdin)
- if p.returncode != 0:
- msg = 'command failed: %s\n%s\n%s' % (argv, out, err)
- logging.error(msg)
- if not ignore_fail:
- raise cliapp.AppException(msg)
- return out
-
- def mkdtemp(self):
- dirname = tempfile.mkdtemp()
- self.remove_dirs.append(dirname)
- logging.debug('mkdir %s', dirname)
- return dirname
-
- def mount(self, device, path=None):
- if not path:
- mount_point = self.mkdtemp()
- else:
- mount_point = path
- self.message('Mounting %s on %s' % (device, mount_point))
- self.runcmd(['mount', device, mount_point])
- self.mount_points.append(mount_point)
- logging.debug('mounted %s on %s', device, mount_point)
- return mount_point
-
- def create_empty_image(self):
- self.message('Creating disk image')
- self.runcmd(['qemu-img', 'create', '-f', 'raw',
- self.settings['image'],
- str(self.settings['size'])])
-
- def partition_image(self):
- """
- Uses fat16 (msdos) partitioning by default, use part-type to change.
- If bootoffset is specified, the first actual partition
- starts at that offset to allow customisation scripts to
- put bootloader images into the space, e.g. u-boot.
- """
- self.message('Creating partitions')
- self.runcmd(['parted', '-s', self.settings['image'],
- 'mklabel', self.settings['part-type']])
- partoffset = 0
- extent = '100%'
- swap = 256 * 1024 * 1024
- if self.settings['swap'] > 0:
- if self.settings['swap'] > swap:
- swap = self.settings['swap']
- else:
- # minimum 256Mb as default qemu ram is 128Mb
- logging.debug("Setting minimum 256Mb swap space")
- extent = "%s%%" % int(100 * (self.settings['size'] - swap) / self.settings['size'])
-
- if self.settings['use-uefi']:
- espsize = self.settings['esp-size'] / (1024 * 1024)
- self.message("Using ESP size: %smib %s bytes" % (espsize, self.settings['esp-size']))
- self.runcmd(['parted', '-s', self.settings['image'],
- 'mkpart', 'primary', 'fat32',
- '1', str(espsize)])
- self.runcmd(['parted', '-s', self.settings['image'],
- 'set', '1', 'boot', 'on'])
- self.runcmd(['parted', '-s', self.settings['image'],
- 'set', '1', 'esp', 'on'])
-
- if self.settings['bootoffset'] and self.settings['bootoffset'] is not '0':
- # turn v.small offsets into something at least possible to create.
- if self.settings['bootoffset'] < 1048576:
- partoffset = 1
- logging.info(
- "Setting bootoffset %smib to allow for %s bytes",
- partoffset, self.settings['bootoffset'])
- else:
- partoffset = self.settings['bootoffset'] / (1024 * 1024)
- self.message("Using bootoffset: %smib %s bytes" % (partoffset, self.settings['bootoffset']))
- if self.settings['bootsize'] and self.settings['bootsize'] is not '0%':
- if self.settings['grub'] and not partoffset:
- partoffset = 1
- bootsize = self.settings['bootsize'] / (1024 * 1024)
- bootsize += partoffset
- self.message("Using bootsize %smib: %s bytes" % (bootsize, self.settings['bootsize']))
- logging.debug("Starting boot partition at %sMb", bootsize)
- self.runcmd(['parted', '-s', self.settings['image'],
- 'mkpart', 'primary', 'fat16', str(partoffset), str(bootsize)])
- logging.debug("Starting root partition at %sMb", partoffset)
- self.runcmd(['parted', '-s', self.settings['image'],
- 'mkpart', 'primary', str(bootsize), extent])
- elif self.settings['use-uefi']:
- bootsize = self.settings['esp-size'] / (1024 * 1024) + 1
- self.runcmd(['parted', '-s', self.settings['image'],
- 'mkpart', 'primary', str(bootsize), extent])
- else:
- self.runcmd(['parted', '-s', self.settings['image'],
- 'mkpart', 'primary', '0%', extent])
- self.runcmd(['parted', '-s', self.settings['image'],
- 'set', '1', 'boot', 'on'])
- if self.settings['swap'] > 0:
- logging.debug("Creating swap partition")
- self.runcmd(['parted', '-s', self.settings['image'],
- 'mkpart', 'primary', 'linux-swap', extent, '100%'])
-
- def update_initramfs(self, rootdir):
- cmd = os.path.join('usr', 'sbin', 'update-initramfs')
- if os.path.exists(os.path.join(rootdir, cmd)):
- self.message("Updating the initramfs")
- self.runcmd(['chroot', rootdir, cmd, '-u'])
-
- def install_mbr(self):
- if os.path.exists("/sbin/install-mbr"):
- self.message('Installing MBR')
- self.runcmd(['install-mbr', self.settings['image']])
- else:
- msg = "mbr enabled but /sbin/install-mbr not found" \
- " - please install the mbr package."
- raise cliapp.AppException(msg)
-
- def setup_kpartx(self):
- bootindex = None
- swapindex = None
- out = self.runcmd(['kpartx', '-avs', self.settings['image']])
- if self.settings['bootsize'] and self.settings['swap'] > 0:
- bootindex = 0
- rootindex = 1
- swapindex = 2
- parts = 3
- elif self.settings['use-uefi']:
- bootindex = 0
- rootindex = 1
- parts = 2
- elif self.settings['use-uefi'] and self.settings['swap'] > 0:
- bootindex = 0
- rootindex = 1
- swapindex = 2
- parts = 3
- elif self.settings['bootsize']:
- bootindex = 0
- rootindex = 1
- parts = 2
- elif self.settings['swap'] > 0:
- rootindex = 0
- swapindex = 1
- parts = 2
- else:
- rootindex = 0
- parts = 1
- boot = None
- swap = None
- devices = [line.split()[2]
- for line in out.splitlines()
- if line.startswith('add map ')]
- if len(devices) != parts:
- msg = 'Surprising number of partitions - check output of losetup -a'
- logging.debug("%s", self.runcmd(['losetup', '-a']))
- logging.debug("%s: devices=%s parts=%s", msg, devices, parts)
- raise cliapp.AppException(msg)
- root = '/dev/mapper/%s' % devices[rootindex]
- if self.settings['bootsize'] or self.settings['use-uefi']:
- boot = '/dev/mapper/%s' % devices[bootindex]
- if self.settings['swap'] > 0:
- swap = '/dev/mapper/%s' % devices[swapindex]
- return root, boot, swap
-
- def _efi_packages(self):
- packages = []
- pkg = self.efi_arch_table[self.settings['arch']]['package']
- self.message("Adding %s" % pkg)
- packages.append(pkg)
- extra = self.efi_arch_table[self.settings['arch']]['extra']
- if extra and isinstance(extra, str):
- bin_pkg = self.efi_arch_table[str(extra)]['bin_package']
- self.message("Adding support for %s using %s" % (extra, bin_pkg))
- packages.append(bin_pkg)
- return packages
-
- def _copy_efi_binary(self, efi_removable, efi_install):
- logging.debug("using bootdir=%s", self.bootdir)
- logging.debug("moving %s to %s", efi_removable, efi_install)
- if efi_removable.startswith('/'):
- efi_removable = efi_removable[1:]
- if efi_install.startswith('/'):
- efi_install = efi_install[1:]
- efi_output = os.path.join(self.bootdir, efi_removable)
- efi_input = os.path.join(self.bootdir, efi_install)
- if not os.path.exists(efi_input):
- logging.warning("%s does not exist (%s)", efi_input, efi_install)
- raise cliapp.AppException("Missing %s" % efi_install)
- if not os.path.exists(os.path.dirname(efi_output)):
- os.makedirs(os.path.dirname(efi_output))
- logging.debug(
- 'Moving UEFI support: %s -> %s', efi_input, efi_output)
- if os.path.exists(efi_output):
- os.unlink(efi_output)
- os.rename(efi_input, efi_output)
-
- def configure_efi(self):
- """
- Copy the bootloader file from the package into the location
- so needs to be after grub and kernel already installed.
- """
- self.message('Configuring EFI')
- efi_removable = str(self.efi_arch_table[self.settings['arch']]['removable'])
- efi_install = str(self.efi_arch_table[self.settings['arch']]['install'])
- self.message('Installing UEFI support binary')
- self._copy_efi_binary(efi_removable, efi_install)
-
- def configure_extra_efi(self):
- extra = str(self.efi_arch_table[self.settings['arch']]['extra'])
- if extra:
- efi_removable = str(self.efi_arch_table[extra]['removable'])
- efi_install = str(self.efi_arch_table[extra]['install'])
- self.message('Copying UEFI support binary for %s' % extra)
- self._copy_efi_binary(efi_removable, efi_install)
-
- def mkfs(self, device, fstype):
- self.message('Creating filesystem %s' % fstype)
- self.runcmd(['mkfs', '-t', fstype, device])
-
- def suite_to_codename(self, distro):
- suite = self.debian_info.codename(distro, datetime.date.today())
- if not suite:
- return distro
- return suite
-
- def was_oldstable(self, limit):
- suite = self.suite_to_codename(self.settings['distribution'])
- # this check is only for debian
- if not self.debian_info.valid(suite):
- return False
- return suite == self.debian_info.old(limit)
-
- def was_stable(self, limit):
- suite = self.suite_to_codename(self.settings['distribution'])
- # this check is only for debian
- if not self.debian_info.valid(suite):
- return False
- return suite == self.debian_info.stable(limit)
-
- def debootstrap(self, rootdir): # pylint: disable=too-many-statements
- msg = "(%s)" % self.settings['variant'] if self.settings['variant'] else ''
- self.message(
- 'Debootstrapping %s [%s] %s' % (
- self.settings['distribution'], self.settings['arch'], msg))
-
- include = self.settings['package']
-
- if not self.settings['foreign'] and not self.settings['no-acpid']:
- include.append('acpid')
-
- if self.settings['grub']:
- if self.settings['use-uefi']:
- include.extend(self._efi_packages())
- else:
- include.append('grub-pc')
-
- if not self.settings['no-kernel']:
- if self.settings['kernel-package']:
- kernel_image = self.settings['kernel-package']
- else:
- if self.settings['arch'] == 'i386':
- # wheezy (which became oldstable on 04/25/2015) used '486'
- if self.was_oldstable(datetime.date(2015, 4, 26)):
- kernel_arch = '486'
- else:
- kernel_arch = '586'
- elif self.settings['arch'] == 'armhf':
- kernel_arch = 'armmp'
- else:
- kernel_arch = self.settings['arch']
- kernel_image = 'linux-image-%s' % kernel_arch
- include.append(kernel_image)
-
- if self.settings['sudo'] and 'sudo' not in include:
- include.append('sudo')
-
- args = ['debootstrap', '--arch=%s' % self.settings['arch']]
-
- if self.settings['package']:
- args.append(
- '--include=%s' % ','.join(include))
- if self.settings['foreign']:
- args.append('--foreign')
- if self.settings['debootstrapopts']:
- for opt in self.settings['debootstrapopts']:
- for part in opt.split(' '):
- args.append('--%s' % part)
- elif self.settings['variant']:
- args.append('--variant')
- args.append(self.settings['variant'])
- args += [self.settings['distribution'],
- rootdir, self.settings['mirror']]
- logging.debug(" ".join(args))
- self.runcmd(args)
- if self.settings['foreign']:
- # set a noninteractive debconf environment for secondstage
- env = {
- "DEBIAN_FRONTEND": "noninteractive",
- "DEBCONF_NONINTERACTIVE_SEEN": "true",
- "LC_ALL": "C"
- }
- # add the mapping to the complete environment.
- env.update(os.environ)
- # First copy the binfmt handler over
- self.message('Setting up binfmt handler')
- shutil.copy(self.settings['foreign'], '%s/usr/bin/' % rootdir)
- # Next, run the package install scripts etc.
- self.message('Running debootstrap second stage')
- self.runcmd(['chroot', rootdir,
- '/debootstrap/debootstrap', '--second-stage'],
- env=env)
-
- def set_hostname(self, rootdir):
- hostname = self.settings['hostname']
- with open(os.path.join(rootdir, 'etc', 'hostname'), 'w') as f:
- f.write('%s\n' % hostname)
-
- etc_hosts = os.path.join(rootdir, 'etc', 'hosts')
- try:
- with open(etc_hosts, 'r') as f:
- data = f.read()
- with open(etc_hosts, 'w') as f:
- for line in data.splitlines():
- if line.startswith('127.0.0.1'):
- line += ' %s' % hostname
- f.write('%s\n' % line)
- except IOError:
- pass
-
- def create_fstab(self, rootdir, rootdev, roottype, bootdev, boottype): # pylint: disable=too-many-arguments
- def fsuuid(device):
- out = self.runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
- '-s', 'UUID', device])
- return out.splitlines()[0].strip()
-
- if rootdev:
- rootdevstr = 'UUID=%s' % fsuuid(rootdev)
- else:
- rootdevstr = '/dev/sda1'
-
- if bootdev and not self.settings['use-uefi']:
- bootdevstr = 'UUID=%s' % fsuuid(bootdev)
- else:
- bootdevstr = None
-
- fstab = os.path.join(rootdir, 'etc', 'fstab')
- with open(fstab, 'w') as f:
- f.write('proc /proc proc defaults 0 0\n')
- f.write('%s / %s errors=remount-ro 0 1\n' % (rootdevstr, roottype))
- if bootdevstr:
- f.write('%s /boot %s errors=remount-ro 0 2\n' % (bootdevstr, boottype))
- if self.settings['swap'] > 0:
- f.write("/dev/sda3 swap swap defaults 0 0\n")
- elif self.settings['swap'] > 0:
- f.write("/dev/sda2 swap swap defaults 0 0\n")
-
- def install_debs(self, rootdir):
- if not self.settings['custom-package']:
- return
- self.message('Installing custom packages')
- tmp = os.path.join(rootdir, 'tmp', 'install_debs')
- os.mkdir(tmp)
- for deb in self.settings['custom-package']:
- shutil.copy(deb, tmp)
- filenames = [os.path.join('/tmp/install_debs', os.path.basename(deb))
- for deb in self.settings['custom-package']]
- out, err, _ = \
- self.runcmd_unchecked(['chroot', rootdir, 'dpkg', '-i'] + filenames)
- logging.debug('stdout:\n%s', out)
- logging.debug('stderr:\n%s', err)
- out = self.runcmd(['chroot', rootdir,
- 'apt-get', '-f', '--no-remove', 'install'])
- logging.debug('stdout:\n%s', out)
- shutil.rmtree(tmp)
-
- def cleanup_apt_cache(self, rootdir):
- out = self.runcmd(['chroot', rootdir, 'apt-get', 'clean'])
- logging.debug('stdout:\n%s', out)
-
- def set_root_password(self, rootdir):
- if self.settings['root-password']:
- self.message('Setting root password')
- self.set_password(rootdir, 'root', self.settings['root-password'])
- elif self.settings['lock-root-password']:
- self.message('Locking root password')
- self.runcmd(['chroot', rootdir, 'passwd', '-l', 'root'])
- else:
- self.message('Give root an empty password')
- self.delete_password(rootdir, 'root')
-
- def create_users(self, rootdir):
- def create_user(vmuser):
- self.runcmd(['chroot', rootdir, 'adduser', '--gecos', vmuser,
- '--disabled-password', vmuser])
- if self.settings['sudo']:
- self.runcmd(['chroot', rootdir, 'adduser', vmuser, 'sudo'])
-
- for userpass in self.settings['user']:
- if '/' in userpass:
- user, password = userpass.split('/', 1)
- create_user(user)
- self.set_password(rootdir, user, password)
- else:
- create_user(userpass)
- self.delete_password(rootdir, userpass)
-
- def set_password(self, rootdir, user, password):
- encrypted = crypt.crypt(password, '..')
- self.runcmd(['chroot', rootdir, 'usermod', '-p', encrypted, user])
-
- def delete_password(self, rootdir, user):
- self.runcmd(['chroot', rootdir, 'passwd', '-d', user])
-
- def remove_udev_persistent_rules(self, rootdir):
- self.message('Removing udev persistent cd and net rules')
- for x in ['70-persistent-cd.rules', '70-persistent-net.rules']:
- pathname = os.path.join(rootdir, 'etc', 'udev', 'rules.d', x)
- if os.path.exists(pathname):
- logging.debug('rm %s', pathname)
- os.remove(pathname)
- else:
- logging.debug('not removing non-existent %s', pathname)
-
- def mask_udev_predictable_rules(self, rootdir):
- """
- This can be reset later but to get networking working immediately
- on boot, the interface we're going to use must be known without
- reference to the eventual machine.
- http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/
- """
- self.message('Disabling systemd predictable interface names')
- udev_path = os.path.join(
- 'etc', 'udev', 'rules.d', '80-net-setup-link.rules')
- self.runcmd(['chroot', rootdir, 'ln', '-s', '/dev/null', udev_path])
-
- def setup_networking(self, rootdir):
- self.message('Setting up networking')
- ifc_file = os.path.join(rootdir, 'etc', 'network', 'interfaces')
- ifc_d = os.path.join(rootdir, 'etc', 'network', 'interfaces.d')
-
- # unconditionally write for wheezy (which became oldstable 2015.04.25)
- if self.was_oldstable(datetime.date(2015, 4, 26)):
- with open(ifc_file, 'w') as netfile:
- netfile.write('source /etc/network/interfaces.d/*\n')
- elif not os.path.exists(ifc_file):
- with open(ifc_file, 'a') as netfile:
- netfile.write('source-directory /etc/network/interfaces.d\n')
-
- if not os.path.exists(ifc_d):
- os.mkdir(ifc_d)
- ethpath = os.path.join(ifc_d, 'setup')
- with open(ethpath, 'w') as eth:
- eth.write('auto lo\n')
- eth.write('iface lo inet loopback\n')
-
- if self.settings['enable-dhcp']:
- eth.write('\n')
- eth.write('auto eth0\n')
- eth.write('iface eth0 inet dhcp\n')
- # force predictable interface names
- self.mask_udev_predictable_rules(rootdir)
-
- def append_serial_console(self, rootdir):
- if self.settings['serial-console']:
- serial_command = self.settings['serial-console-command']
- logging.debug('adding getty to serial console')
- inittab = os.path.join(rootdir, 'etc/inittab')
- # to autologin, serial_command can contain '-a root'
- with open(inittab, 'a') as f:
- f.write('\nS0:23:respawn:%s\n' % serial_command)
-
- # pylint: disable=no-self-use
- def _grub_serial_console(self, rootdir):
- cmdline = 'GRUB_CMDLINE_LINUX_DEFAULT="console=tty0 console=tty1 console=ttyS0,38400n8"'
- terminal = 'GRUB_TERMINAL="serial gfxterm"'
- command = 'GRUB_SERIAL_COMMAND="serial --speed=38400 --unit=0 --parity=no --stop=1"'
- grub_cfg = os.path.join(rootdir, 'etc', 'default', 'grub')
- logging.debug("Allowing serial output in grub config %s", grub_cfg)
- with open(grub_cfg, 'a+') as cfg:
- cfg.write("# %s serial support\n" % os.path.basename(__file__))
- cfg.write("%s\n" % cmdline)
- cfg.write("%s\n" % terminal)
- cfg.write("%s\n" % command)
-
- def _mount_wrapper(self, rootdir):
- self.runcmd(['mount', '/dev', '-t', 'devfs', '-obind',
- '%s' % os.path.join(rootdir, 'dev')])
- self.runcmd(['mount', '/proc', '-t', 'proc', '-obind',
- '%s' % os.path.join(rootdir, 'proc')])
- self.runcmd(['mount', '/sys', '-t', 'sysfs', '-obind',
- '%s' % os.path.join(rootdir, 'sys')])
-
- def _umount_wrapper(self, rootdir):
- self.runcmd(['umount', os.path.join(rootdir, 'sys')])
- self.runcmd(['umount', os.path.join(rootdir, 'proc')])
- self.runcmd(['umount', os.path.join(rootdir, 'dev')])
-
- def install_grub_uefi(self, rootdir):
- self.message("Configuring grub-uefi")
- target = self.efi_arch_table[self.settings['arch']]['target']
- grub_opts = "--target=%s" % target
- logging.debug("Running grub-install with options: %s", grub_opts)
- self._mount_wrapper(rootdir)
- try:
- self.runcmd(['chroot', rootdir, 'update-grub'])
- self.runcmd(['chroot', rootdir, 'grub-install', grub_opts])
- except cliapp.AppException as exc:
- logging.warning(exc)
- self.message(
- "Failed to configure grub-uefi for %s" %
- self.settings['arch'])
- self._umount_wrapper(rootdir)
- self.configure_efi()
- extra = str(self.efi_arch_table[self.settings['arch']]['extra'])
- if extra:
- target = self.efi_arch_table[extra]['target']
- grub_opts = "--target=%s" % target
- try:
- self.runcmd(['chroot', rootdir, 'update-grub'])
- self.runcmd(['chroot', rootdir, 'grub-install', grub_opts])
- except cliapp.AppException as exc:
- logging.warning(exc)
- self.message(
- "Failed to configure grub-uefi for %s" % extra)
- self.configure_extra_efi()
- self._umount_wrapper(rootdir)
-
- def install_grub2(self, rootdev, rootdir):
- self.message("Configuring grub2")
- # rely on kpartx using consistent naming to map loop0p1 to loop0
- grub_opts = os.path.join('/dev', os.path.basename(rootdev)[:-2])
- if self.settings['serial-console']:
- self._grub_serial_console(rootdir)
- logging.debug("Running grub-install with options: %s", grub_opts)
- self._mount_wrapper(rootdir)
- try:
- self.runcmd(['chroot', rootdir, 'update-grub'])
- self.runcmd(['chroot', rootdir, 'grub-install', grub_opts])
- except cliapp.AppException as exc:
- logging.warning(exc)
- self.message("Failed. Is grub2-common installed? Using extlinux.")
- self.install_extlinux(rootdev, rootdir)
- self._umount_wrapper(rootdir)
-
- def install_extlinux(self, rootdev, rootdir):
- if not os.path.exists("/usr/bin/extlinux"):
- self.message("extlinux not installed, skipping.")
- return
- self.message('Installing extlinux')
-
- def find(pattern):
- dirname = os.path.join(rootdir, 'boot')
- basenames = os.listdir(dirname)
- logging.debug('find: %s', basenames)
- for basename in basenames:
- if re.search(pattern, basename):
- return os.path.join('boot', basename)
- raise cliapp.AppException('Cannot find match: %s' % pattern)
-
- try:
- kernel_image = find('vmlinuz-.*')
- initrd_image = find('initrd.img-.*')
- except cliapp.AppException as e:
- self.message("Unable to find kernel. Not installing extlinux.")
- logging.debug("No kernel found. %s. Skipping install of extlinux.", e)
- return
-
- out = self.runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
- '-s', 'UUID', rootdev])
- uuid = out.splitlines()[0].strip()
-
- conf = os.path.join(rootdir, 'extlinux.conf')
- logging.debug('configure extlinux %s', conf)
- kserial = 'console=ttyS0,115200' if self.settings['serial-console'] else ''
- extserial = 'serial 0 115200' if self.settings['serial-console'] else ''
- msg = '''
-default linux
-timeout 1
-
-label linux
-kernel %(kernel)s
-append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s
-%(extserial)s
-''' % {
- 'kernel': kernel_image, # pylint: disable=bad-continuation
- 'initrd': initrd_image, # pylint: disable=bad-continuation
- 'uuid': uuid, # pylint: disable=bad-continuation
- 'kserial': kserial, # pylint: disable=bad-continuation
- 'extserial': extserial, # pylint: disable=bad-continuation
- } # pylint: disable=bad-continuation
- logging.debug("extlinux config:\n%s", msg)
-
- # python multiline string substitution is just ugly.
- # use an external file or live with the mangling, no point in
- # mangling the string to remove spaces just to keep it pretty in source.
- f = open(conf, 'w')
- f.write(msg)
-
- self.runcmd(['extlinux', '--install', rootdir])
- self.runcmd(['sync'])
- time.sleep(2)
-
- def optimize_image(self, rootdir):
- """
- Filing up the image with zeros will increase its compression rate
- """
- if not self.settings['sparse']:
- zeros = os.path.join(rootdir, 'ZEROS')
- self.runcmd_unchecked(['dd', 'if=/dev/zero', 'of=' + zeros, 'bs=1M'])
- self.runcmd(['rm', '-f', zeros])
-
- def squash(self):
- """
- Run squashfs on the image.
- """
- if not os.path.exists('/usr/bin/mksquashfs'):
- logging.warning("Squash selected but mksquashfs not found!")
- return
- logging.debug(
- "%s usage: %s", self.settings['image'],
- self.runcmd(['du', self.settings['image']]))
- self.message("Running mksquashfs")
- suffixed = "%s.squashfs" % self.settings['image']
- if os.path.exists(suffixed):
- os.unlink(suffixed)
- msg = self.runcmd(
- ['mksquashfs', self.settings['image'],
- suffixed,
- '-no-progress', '-comp', 'xz'], ignore_fail=False)
- logging.debug(msg)
- check_size = os.path.getsize(suffixed)
- if check_size < (1024 * 1024):
- logging.warning(
- "%s appears to be too small! %s bytes",
- suffixed, check_size)
- else:
- logging.debug("squashed size: %s", check_size)
- os.unlink(self.settings['image'])
- self.settings['image'] = suffixed
- logging.debug(
- "%s usage: %s", self.settings['image'],
- self.runcmd(['du', self.settings['image']]))
-
- def cleanup_system(self):
- # Clean up after any errors.
-
- self.message('Cleaning up')
-
- # Umount in the reverse mount order
- if self.settings['image']:
- for i in range(len(self.mount_points) - 1, -1, -1):
- mount_point = self.mount_points[i]
- try:
- self.runcmd(['umount', mount_point], ignore_fail=False)
- except cliapp.AppException:
- logging.debug("umount failed, sleeping and trying again")
- time.sleep(5)
- self.runcmd(['umount', mount_point], ignore_fail=False)
-
- self.runcmd(['kpartx', '-d', self.settings['image']], ignore_fail=True)
-
- for dirname in self.remove_dirs:
- shutil.rmtree(dirname)
-
- def customize(self, rootdir):
- script = self.settings['customize']
- if not script:
- return
- if not os.path.exists(script):
- example = os.path.join("/usr/share/vmdebootstrap/examples/", script)
- if not os.path.exists(example):
- self.message("Unable to find %s" % script)
- return
- script = example
- self.message('Running customize script %s' % script)
- logging.info("rootdir=%s image=%s", rootdir, self.settings['image'])
- logging.debug(
- "%s usage: %s", self.settings['image'],
- self.runcmd(['du', self.settings['image']]))
- try:
- with open('/dev/tty', 'w') as tty:
- cliapp.runcmd([script, rootdir, self.settings['image']], stdout=tty, stderr=tty)
- except IOError:
- logging.debug('tty unavailable, trying in headless mode.')
- subprocess.call([script, rootdir, self.settings['image']])
-
- def create_tarball(self, rootdir):
- # Create a tarball of the disk's contents
- # shell out to runcmd since it more easily handles rootdir
- self.message('Creating tarball of disk contents')
- self.runcmd(['tar', '-cf', self.settings['tarball'], '-C', rootdir, '.'])
-
- def chown(self):
- # Change image owner after completed build
- if self.settings['image']:
- filename = self.settings['image']
- elif self.settings['tarball']:
- filename = self.settings['tarball']
- else:
- return
- self.message("Changing owner to %s" % self.settings["owner"])
- subprocess.call(["chown", self.settings["owner"], filename])
-
- def list_installed_pkgs(self, rootdir):
- # output the list of installed packages for sources identification
- self.message("Creating a list of installed binary package names")
- out = self.runcmd(['chroot', rootdir,
- 'dpkg-query', '-W', "-f='${Package}.deb\n'"])
- with open('dpkg.list', 'w') as dpkg:
- dpkg.write(out)
-
- def configure_apt(self, rootdir):
- # use the distribution and mirror to create an apt source
- self.message("Configuring apt to use distribution and mirror")
- conf = os.path.join(rootdir, 'etc', 'apt', 'sources.list.d', 'base.list')
- logging.debug('configure apt %s', conf)
- mirror = self.settings['mirror']
- if self.settings['apt-mirror']:
- mirror = self.settings['apt-mirror']
- self.message("Setting apt mirror to %s" % mirror)
- os.unlink(os.path.join(rootdir, 'etc', 'apt', 'sources.list'))
- f = open(conf, 'w')
- line = 'deb %s %s main\n' % (mirror, self.settings['distribution'])
- f.write(line)
- line = '#deb-src %s %s main\n' % (mirror, self.settings['distribution'])
- f.write(line)
- f.close()
- # ensure the apt sources have valid lists
- self.runcmd(['chroot', rootdir, 'apt-get', '-qq', 'update'])
-
-if __name__ == '__main__':
- VmDebootstrap(version=__version__).run()
diff --git a/vmdebootstrap.8.in b/vmdebootstrap.8.in
deleted file mode 100644
index 56afc3c..0000000
--- a/vmdebootstrap.8.in
+++ /dev/null
@@ -1,405 +0,0 @@
-.\" Copyright 2011 Lars Wirzenius <liw@liw.fi>
-.\"
-.\" This program is free software: you can redistribute it and/or modify
-.\" it under the terms of the GNU General Public License as published by
-.\" the Free Software Foundation, either version 3 of the License, or
-.\" (at your option) any later version.
-.\"
-.\" This program is distributed in the hope that it will be useful,
-.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
-.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-.\" GNU General Public License for more details.
-.\"
-.\" You should have received a copy of the GNU General Public License
-.\" along with this program. If not, see <http://www.gnu.org/licenses/>.
-.\"
-.TH VMDEBOOTSTRAP 8
-.SH NAME
-vmdebootstrap \- install basic Debian system into virtual disk image
-.SH SYNOPSIS
-.B vmdebootstrap
-\-\-image=FILE \-\-size=SIZE [\-\-mirror=URL] [\-\-distribution=NAME]
-.PP
-.B vmdebootstrap
-[\-\-output=FILE] [\-\-verbose |\-\-no-verbose] \-\-image=FILE \-\-size=SIZE
-[\-\-tarball=FILE] [\-\-mirror=URL] [\-\-arch=ARCH] [\-\-distribution=NAME]
-[\-\-package=PACKAGE] [\-\-custom-package=DEB] [\-\-no-kernel] [\-\-kernel-package]
-[\-\-enable-dhcp | \-\-no-enable-dhcp] [\-\-root-password=PASSWORD]
-[\-\-customize=SCRIPT] [\-\-hostname=HOSTNAME] [\-\-user=USER/PASSWORD]
-[\-\-serial-console | \-\-no-serial-console] [\-\-sudo |\-\-no-sudo] [\-\-owner=OWNER]
-[\-\-bootsize=BOOTSIZE] [\-\-boottype=FSTYPE] [\-\-roottype=FSTYPE] [\-\-foreign=PATH]
-[\-\-variant=VARIANT] [\-\-no-extlinux] [\-\-squash] [\-\-configure-apt]
-[\-\-grub] [\-\-apt-mirror] [\-\-pkglist] [\-\-use\-efi] [\-\-efi\-size]
-[\-\-debootstrapopts]
-.SH DESCRIPTION
-.B vmdebootstrap
-installs a basic Debian system into a virtual disk image,
-for use with virtual machines,
-such as KVM, Qemu, or VirtualBox.
-It is like
-.BR debootstrap (8),
-which does the same thing, but puts the system into a directory,
-for use with
-.BR chroot (8).
-(In fact,
-.B vmdebootstrap
-is a wrapper around
-.BR debootstrap ).
-.PP
-You need to run
-.B vmdebootstrap
-as root. If the \-\-verbose option is not used, no output will be
-sent to the command line. If the \-\-log option is not used, no
-output will be sent to any log files either.
-.PP
-To use the image,
-you probably want to create a virtual machine using your preferred
-virtualization technology, such as
-.BR kvm (1),
-or
-.BR qemu (1).
-Configure the virtual machine to use the image you've created.
-Then start the virtual machine, (see
-.B EXAMPLES
-)
-and log into it via its console to configure it.
-The image has an empty root password and will not have networking
-configured by default. Set the root password before you configure
-networking.
-.SH NETWORKING
-The \-\-enable\-networking option uses the /etc/network/interfaces.d/
-source directory, with the default settings for
-.B lo
-and
-.B eth0
-being added to /etc/network/interfaces.d/setup. Other networking
-configuration can be specified using a customisation script.
-Localhost settings would be:
-
- auto lo
- iface lo inet loopback
-
-If \-\-enable\-dhcp is specified, these settings are also included
-into /etc/network/interfaces.d/setup:
-
- auto eth0
- iface eth0 inet dhcp
-
-For systems running newer versions of systemd, the interface name needs
-to be set in advance of the first boot instead of being dependent on the
-boot itself. See the http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/
-.B vmdebootstrap
-disables this behaviour by symlinking /dev/null to /etc/udev/rules.d/80-net-setup-link.rules
-
-.SH BOOTLOADERS
-Unless the \-\-no\-extlinux or \-\-grub options are specified, the
-image will use
-.BR extlinux (1)
-as a boot loader.
-.B bootsize
-is not recommended when using
-.B extlinux
-\- use grub instead.
-Versions of grub2 in wheezy
-can fail to install in the VM, at which point vmdebootstrap will fall back to
-extlinux. It may still be possible to complete the installation of grub2 after
-booting the VM as the problem may be related to the need to use loopback
-devices during the grub-install operation. Details of the error will appear in the
-vmdebootstrap log file, if enabled with the \-\-log option. Note that
-.B grub-legacy
-is not supported.
-.B vmdebootstrap
-also supports
-.B EFI.
-Use \-\-use\-uefi to use grub\-efi instead of grub\-pc. If the default 5Mb
-is not enough space, use the \-\-esp\-size option to specify a different
-size for the EFI partition. Registered firmware is not supported as it
-would need to be done after boot. If the system you are creating is for
-more than just a VM or live image, you will likely need a larger ESP,
-up to 500Mb.
-.B UBoot
-needs manual configuration via the customisation hook scripts,
-typically support requires adding u\-boot using \-\-package and then
-copying or manipulating the relevant u\-boot files in the customisation
-script. Examples are included for beaglebone-black.
-.SH INSTALLATION IMAGES AND VIRTUAL MACHINES
-.B vmdebootstrap
-is aimed principally at creating virtual machines, not installers or prebuilt
-installation images. It is possible to create prebuilt installation images
-for some devices but this depends on the specific device. (A 'prebuilt
-installation image' is a single image file which can be written to physical
-media in a single operation and which allows the device to boot directly
-into a fully installed system \- in a similar way to how a virtual machine
-would behave.)
-.PP
-.B vmdebootstrap
-assumes that all operations take place on a local image file, not a
-physical block device / removable media.
-.PP
-.B vmdebootstrap
-is intended to be used with tools like qemu on the command line to launch
-a new virtual machine. Not all devices have virtualisation support in hardware.
-.PP
-This has implications for
-.B u-boot
-support in some cases. If the device can support reading the bootloader
-from a known partition, like the beaglebone-black, then
-.B vmdebootstrap
-can provide space for the bootloader and the image will work as a prebuilt
-installation image. If the device expects that the bootloader exists at a
-specific offset and therefore requires that the bootloader is written as
-an image not as a binary which can be copied into an existing partition,
-.B vmdebootstrap
-is unable to include that bootloader image into the virtual machine image.
-.PP
-The beagleboneblack.sh script in the examples/ directory provides a worked
-example to create a prebuilt installation image. However, the beagleboneblack
-itself does not support virtualisation in hardware, so is unable to launch
-a virtual machine. Other devices, like the Cubietruck or Wandboard need
-.B u-boot
-at a predefined offset but can launch a virtual machine using qemu, so
-the cubietruck and wandboard6q scripts in the examples/ directory relate
-to building images for virtual machines once the device is already
-installed and booted into a suitable kernel.
-.PP
-It is possible to wrap
-.B vmdebootstrap
-in such a way as to prepare a
-.B physical block device
-with a bootloader image and then deploy the bootstrap on top. However,
-this does require physical media to be inserted and removed each time
-the wrapper is executed. To do this, use the \-\-tarball option instead
-of the \-\-image option. Then setup the physical media and bootloader
-image manually, as required for the device, redefine the partitions to
-make space for the rootfs, create a filesystem on the physical media and
-unpack the
-.B vmdebootstrap
-tarball onto that filesystem. Once you have working media, an image can be
-created using dd to read back from the media to an image file, allowing
-other media to be written with a single image file.
-.SH OPTIONS
-.IP \-\-output=FILE
-write output to FILE, instead of standard output
-.IP \-\-verbose
-report what is going on
-.IP \-\-image=FILE
-put created disk image in FILE
-.IP \-\-size=SIZE
-create a disk image of size SIZE (1000000000)
-.IP \-\-tarball=FILE
-tar up the disk's contents in FILE
-.IP \-\-mirror=URL
-use MIRROR as package source (http://http.debian.net/debian/)
-.IP \-\-arch=ARCH
-architecture to use (amd64) - if using an architecture which the
-host system cannot execute, ensure the \-\-foreign option is also
-used.
-.IP \-\-distribution=NAME
-release to use (defaults to stable). The release needs to be a valid
-Debian or Ubuntu release name or codename.
-.IP \-\-package=PACKAGE
-install PACKAGE onto system
-.IP \-\-custom-package=DEB
-install package in DEB file onto system (not from mirror)
-.IP \-\-no-kernel
-do not install a linux package
-.IP \-\-kernel-package
-If \-\-no-kernel is not used and the auto-selection of the
-.B linux-image-586
-or
-.B linux-image-armmp
-or
-.B linux-image-$ARCH
-package is not suitable, the kernel package can be specified
-explicitly.
-.IP \-\-enable-dhcp
-enable DHCP on eth0
-.IP \-\-root-password=PASSWORD
-set root password
-.IP \-\-customize=SCRIPT
-run SCRIPT after setting up system. If the script does not exist in the current
-working directory, /usr/share/vmdebootstrap/examples/ will be checked as a
-fallback. The script needs to be executable and is passed the root directory of
-the debootstrap as the only argument. Use chroot if you need to execute binaries
-within the debootstrap.
-.IP \-\-hostname=HOSTNAME
-set name to HOSTNAME (debian)
-.IP \-\-user=USER/PASSWORD
-create USER with PASSWORD
-.IP \-\-owner=OWNER
-change the owner of the final image from root to the specified user.
-.IP \-\-serial\-console
-configure image to use a serial console
-.IP \-\-serial-console-command
-set the command to manage the serial console which will be appended to
-/etc/inittab. Default is "/sbin/getty \-L ttyS0 115200 vt100", resulting in a line
-.BR "S0:23:respawn:/sbin/getty \-L ttyS0 115200 vt100"
-.IP \-\-sudo
-install sudo, and if user is created, add them to sudo group
-.IP \-\-bootsize=BOOTSIZE
-If specified, create a /boot partition of the given size within the image.
-Debootstrapping will fail if this is too small for the selected kernel package.
-.IP \-\-boottype=FSTYPE
-Filesystem to use for the /boot partition. (default ext2)
-.IP \-\-roottype=FSTYPE
-Filesystem to use for the / (root) partition. (default ext4)
-.IP \-\-swap=SWAPSIZE
-If specified, create a swap partition of the given size within the image.
-Debootstrapping will fail if this results in a root partition which is
-too small for the selected packages. The minimum swap space is 256Mb as
-the default memory allocation of QEMU is 128Mb. A default 1Gb image is
-not likely to have enough space for a swap partition as well.
-.IP \-\-foreign=PATH
-Path to the binfmt_handler to enable foreign support in debootstrap.
-e.g. /usr/bin/qemu-arm-static \- note foreign debootstraps may take a signficant
-amount of time to complete and that debootstrap will retry five times if
-packages fail to install by default.
-.IP \-\-no\-extlinux
-Skip installation of extlinux. needs a customize script to make the image
-bootable. Useful for architectures where extlinux is not supportable.
-Depending on how the image is to be booted, the \-\-mbr option may also be
-necessary with extlinux.
-.IP \-\-squash
-Run mksquashfs against the final image using xz compression \- requires
-squashfs-tools to be installed. The final file will have the .squashfs suffix.
-By default, mksquashfs is allowed to use all processors which may result
-in high load. Run mksquashfs separately if you need to control the number
-of processors used per run. squashfs can also have issues with large image
-files (where large is a factor of the amount of data inside the image rather
-than the size of the image itself). These errors can result in invalid
-images (e.g. image does not boot) or corrupted images (truncated file).
-This is a known bug in squashfs. Avoid using the \-\-squash option and
-consider squashing the loopback mounted directory tree of the image.
-.B
-vmdebootstrap
-will check if the squashed filesystem is less than 1MB and leave the
-unsquashed image in place with a warning about a possible squashfs
-failure.
-.IP \-\-configure\-apt
-Use the specified mirror and distribution to create a suitable apt source inside
-the VM. Can be useful if debootstrap fails to create it automatically.
-.IP \-\-apt\-mirror
-Use the specified mirror inside the image instead of the mirror used to
-build the image. This is useful if you have a local mirror to make building
-the image quicker but the image needs to run even if that mirror is not
-available.
-.IP \-\-grub
-Disable extlinux installation and configure grub2 instead. grub2 will be added to
-the list of packages to install. update-grub will be called once the debootstrap is
-complete and grub-install will be called in the image.
-.IP \-\-debootstrapopts
-Pass additional options to debootstrap as a quoted list of options
-and values, separated by spaces.
-e.g. --debootstrapopts="variant=buildd no-check-gpg components=main,contrib".
-See debootstrap \-\-help and debootstrap (1) for valid options.
-.IP \-\-no\-acpid
-Disable installation of acpid if not required, otherwise acpid will be
-installed if \-\-foreign is not used.
-.IP \-\-pkglist
-Output a list of package names installed inside the image. Useful if you
-need to track the relevant source packages used inside the image for
-licence compliance.
-.SH Configuration files and settings:
-.IP \-\-dump-config
-write out the entire current configuration
-.IP \-\-no-default-configs
-clear list of configuration files to read
-.IP \-\-config=FILE
-add FILE to config files
-.SH Logging:
-.IP \-\-log=FILE
-write log entries to FILE (default is to not write log files at all);
-use "syslog" to log to system log, or "none" to disable logging
-.IP \-\-log-level=LEVEL
-log at LEVEL, one of debug, info, warning, error, critical, fatal (default: debug)
-.IP \-\-log-max=SIZE
-rotate logs larger than SIZE, zero for never (default: 0)
-.IP \-\-log-keep=N
-keep last N logs (10)
-.IP \-\-log-mode=MODE
-set permissions of new log files to MODE (octal; default 0600)
-.SH Peformance:
-.IP \-\-dump-memory-profile=METHOD
-make memory profiling dumps using METHOD, which is one of:
-none, simple, meliae, or heapy (default: simple)
-.IP \-\-memory-dump-interval=SECONDS
-make memory profiling dumps at least SECONDS apart
-.SH EXAMPLE
-To create an image for the stable release of Debian:
-.IP
-sudo vmdebootstrap \-\-image test.img \-\-size 1g \\
- \-\-log test.log \-\-log-level debug \-\-verbose \\
- \-\-mirror http://mirror.lan/debian/
-.PP
-To run the test image, make sure it is writeable. Use the \-\-owner option to set
-mode 0644 for the specified user or use chmod manually:
-.IP
-sudo chmod a+w ./test.img
-.PP
-Execute using qemu, e.g. on amd64 using qemu-system-x86_64:
-.IP
-qemu-system-x86_64 -drive format=raw,file=./test.img
-.PP
-(This loads the image in a new window.) Note the use of -drive
-file=<img>,format=raw which is needed for newer versions of QEMU.
-.PP
-There is EFI firmware available to use with QEMU when testing images built
-using the UEFI support, but this software is in Debian non-free due to patent
-concerns. If you choose to install
-.B
-ovmf
-to test UEFI builds, a secondary change is also needed to symlink the provided
-OVMF.fd to the file required by QEMU: bios-256k.bin and then tell QEMU about
-the location of this file with the -L option:
-.IP
-$ qemu-system-x86_64 \-L /usr/share/ovmf/ -machine accel=kvm \\
- \-m 4096 \-smp 2 \-drive format=raw,file=test.img
-.PP
-For further examples, including u-boot support for beaglebone-black,
-see /usr/share/vmdebootstrap/examples
-.SH NOTES
-If you get problems with the bootstrap process, run a similar bootstrap
-call directly and chroot into the directory to investigate the failure.
-The actual debootstrap call is part of the vmdebootstrap logfile. The
-debootstrap logfile, if any, will be copied into your current working
-directory on error.
-.PP
-.B debootstrap
-will download all the apt archive files into the apt cache and does not
-remove them before starting the configuration of the packages. This can
-mean that debootstrap can fail due to a lack of space on the device if
-the VM size is small. vmdebootstrap cleans up the apt cache once debootstrap
-has finished but this doesn't help if the package unpack or configuration
-steps use up all of the space in the meantime. Avoid this problem by
-specifying a larger size for the image.
-.PP
-Note that if you are also using a separate /boot partition in your options to
-.B vmdebootstrap
-it may well be the boot partition which needs to be enlarged rather than
-the entire image.
-.PP
-It is advisable to change the mirror in the example scripts to a mirror
-closer to your location, particularly if you need to do repeated builds.
-Use the \-\-apt\-mirror option to specify the apt mirror to be used inside
-the image, after boot.
-.PP
-There are two types of examples for ARM devices available with
-.B vmdebootstrap:
-prebuilt installation images (like the beaglebone-black) and virtual
-machine images (cubietruck and wandboard). ARM devices which do not
-support hypervisor mode and which also rely on the bootloader being at
-a specific offset instead of using a normal partition will
-.B not
-be supportable by vmdebootstrap. Similarly, devices which support
-hypervisor will only be supported using virtual machine images, unless
-the bootloader can be executed from a normal partition.
-.PP
-.SH "SEE ALSO"
-.BR debootstrap (8)
-,
-.BR qemu-system-x86_64 (1)
-,
-.BR grub-install (8)
-.
-.SH BUGS
-Please provide the config section of the logfile when reporting bugs, as well as the complete command line.
diff --git a/vmdebootstrap/__init__.py b/vmdebootstrap/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/vmdebootstrap/__init__.py
diff --git a/vmdebootstrap/base.py b/vmdebootstrap/base.py
new file mode 100644
index 0000000..0a302a4
--- /dev/null
+++ b/vmdebootstrap/base.py
@@ -0,0 +1,217 @@
+"""
+ Base for common utility functions
+"""
+# -*- coding: utf-8 -*-
+#
+# base.py
+#
+# Copyright 2015 Neil Williams <codehelp@debian.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import crypt
+import shutil
+import cliapp
+import logging
+import subprocess
+
+# pylint: disable=missing-docstring
+
+
+def runcmd(argv, stdin='', ignore_fail=False, env=None, **kwargs):
+ logging.debug('runcmd: %s %s %s', argv, env, kwargs)
+ proc = subprocess.Popen(
+ argv, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ env=env, **kwargs)
+ out, err = proc.communicate(stdin)
+ if proc.returncode != 0:
+ msg = 'command failed: %s\n%s\n%s' % (argv, out, err)
+ logging.error(msg)
+ if not ignore_fail:
+ raise cliapp.AppException(msg)
+ return out
+
+
+# FIXME: use contextmanager
+def mount_wrapper(rootdir):
+ runcmd(['mount', '/dev', '-t', 'devfs', '-obind',
+ '%s' % os.path.join(rootdir, 'dev')])
+ runcmd(['mount', '/proc', '-t', 'proc', '-obind',
+ '%s' % os.path.join(rootdir, 'proc')])
+ runcmd(['mount', '/sys', '-t', 'sysfs', '-obind',
+ '%s' % os.path.join(rootdir, 'sys')])
+
+
+def umount_wrapper(rootdir):
+ runcmd(['umount', os.path.join(rootdir, 'sys')])
+ runcmd(['umount', os.path.join(rootdir, 'proc')])
+ runcmd(['umount', os.path.join(rootdir, 'dev')])
+
+
+def cleanup_apt_cache(rootdir):
+ out = runcmd(['chroot', rootdir, 'apt-get', 'clean'])
+ logging.debug('stdout:\n%s', out)
+
+
+def set_password(rootdir, user, password):
+ encrypted = crypt.crypt(password, '..')
+ runcmd(['chroot', rootdir, 'usermod', '-p', encrypted, user])
+
+
+def delete_password(rootdir, user):
+ runcmd(['chroot', rootdir, 'passwd', '-d', user])
+
+
+def copy_files(src, dest):
+ for filename in os.listdir(src):
+ if os.path.isdir(filename) or os.path.islink(filename):
+ continue
+ shutil.copyfile(
+ os.path.join(src, filename),
+ os.path.join(dest, filename))
+
+
+class Base(object):
+
+ name = 'base'
+
+ def __init__(self):
+ super(Base, self).__init__()
+ self.settings = None
+
+ def define_settings(self, settings):
+ self.settings = settings
+
+ def message(self, msg):
+ logging.info(msg)
+ if self.settings['verbose']:
+ print msg
+
+ def create_empty_image(self):
+ self.message('Creating disk image')
+ runcmd(['qemu-img', 'create', '-f', 'raw',
+ self.settings['image'],
+ str(self.settings['size'])])
+
+ def create_tarball(self, rootdir):
+ # Create a tarball of the disk's contents
+ # shell out to runcmd since it more easily handles rootdir
+ self.message('Creating tarball of disk contents')
+ runcmd(['tar', '-cf', self.settings['tarball'], '-C', rootdir, '.'])
+
+ def mkfs(self, device, fstype):
+ self.message('Creating filesystem %s' % fstype)
+ runcmd(['mkfs', '-t', fstype, device])
+
+ def set_root_password(self, rootdir):
+ if self.settings['root-password']:
+ self.message('Setting root password')
+ set_password(rootdir, 'root', self.settings['root-password'])
+ elif self.settings['lock-root-password']:
+ self.message('Locking root password')
+ runcmd(['chroot', rootdir, 'passwd', '-l', 'root'])
+ else:
+ self.message('Give root an empty password')
+ delete_password(rootdir, 'root')
+
+ def create_users(self, rootdir):
+ def create_user(vmuser):
+ runcmd(['chroot', rootdir, 'adduser', '--gecos', vmuser,
+ '--disabled-password', vmuser])
+ if self.settings['sudo']:
+ runcmd(['chroot', rootdir, 'adduser', vmuser, 'sudo'])
+
+ for userpass in self.settings['user']:
+ if '/' in userpass:
+ user, password = userpass.split('/', 1)
+ create_user(user)
+ set_password(rootdir, user, password)
+ else:
+ create_user(userpass)
+ delete_password(rootdir, userpass)
+
+ def customize(self, rootdir):
+ script = self.settings['customize']
+ if not script:
+ return
+ if not os.path.exists(script):
+ example = os.path.join("/usr/share/vmdebootstrap/examples/", script)
+ if not os.path.exists(example):
+ self.message("Unable to find %s" % script)
+ return
+ script = example
+ self.message('Running customize script %s' % script)
+ logging.info("rootdir=%s image=%s", rootdir, self.settings['image'])
+ logging.debug(
+ "%s usage: %s", self.settings['image'],
+ runcmd(['du', self.settings['image']]))
+ try:
+ with open('/dev/tty', 'w') as tty:
+ cliapp.runcmd([script, rootdir, self.settings['image']], stdout=tty, stderr=tty)
+ except IOError:
+ logging.debug('tty unavailable, trying in headless mode.')
+ subprocess.call([script, rootdir, self.settings['image']])
+
+ def append_serial_console(self, rootdir):
+ if self.settings['serial-console']:
+ serial_command = self.settings['serial-console-command']
+ logging.debug('adding getty to serial console')
+ inittab = os.path.join(rootdir, 'etc/inittab')
+ # to autologin, serial_command can contain '-a root'
+ with open(inittab, 'a') as ftab:
+ ftab.write('\nS0:23:respawn:%s\n' % serial_command)
+
+ def check_swap_size(self):
+ # swap - modifies extent
+ extent = '100%'
+ swap = 256 * 1024 * 1024
+ if self.settings['swap'] > 0:
+ if self.settings['swap'] > swap:
+ swap = self.settings['swap']
+ else:
+ # minimum 256Mb as default qemu ram is 128Mb
+ logging.debug("Setting minimum 256Mb swap space")
+ extent = "%s%%" % int(100 * (self.settings['size'] - swap) / self.settings['size'])
+ return extent
+
+ def make_swap(self, extent):
+ if self.settings['swap'] > 0:
+ logging.debug("Creating swap partition")
+ runcmd([
+ 'parted', '-s', self.settings['image'],
+ 'mkpart', 'primary', 'linux-swap', extent, '100%'])
+
+ def base_packages(self):
+ packages = []
+ if not self.settings['foreign'] and not self.settings['no-acpid']:
+ packages.append('acpid')
+ if self.settings['sudo']:
+ packages.append('sudo')
+ if not self.settings['no-kernel']:
+ if self.settings['kernel-package']:
+ packages.append(self.settings['kernel-package'])
+ return packages
+
+ def mask_udev_predictable_rules(self, rootdir):
+ """
+ This can be reset later but to get networking using immediately
+ on boot, the interface we're going to use must be known
+ http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/
+ """
+ self.message('Disabling systemd predictable interface names')
+ udev_path = os.path.join(
+ 'etc', 'udev', 'rules.d', '80-net-setup-link.rules')
+ runcmd(['chroot', rootdir, 'ln', '-s', '/dev/null', udev_path])
diff --git a/vmdebootstrap/codenames.py b/vmdebootstrap/codenames.py
new file mode 100644
index 0000000..d0f71fe
--- /dev/null
+++ b/vmdebootstrap/codenames.py
@@ -0,0 +1,79 @@
+"""
+ Wrapper for distro information
+"""
+# -*- coding: utf-8 -*-
+#
+# codenames.py
+#
+# Copyright 2015 Neil Williams <codehelp@debian.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import datetime
+from vmdebootstrap.base import Base
+from distro_info import DebianDistroInfo, UbuntuDistroInfo
+
+# pylint: disable=missing-docstring
+
+
+class Codenames(Base):
+
+ name = 'codenames'
+
+ def __init__(self):
+ super(Codenames, self).__init__()
+ self.debian_info = DebianDistroInfo()
+ self.ubuntu_info = UbuntuDistroInfo()
+ self.settings = None
+
+ def define_settings(self, settings):
+ self.settings = settings
+
+ def suite_to_codename(self, distro):
+ suite = self.debian_info.codename(distro, datetime.date.today())
+ if not suite:
+ return distro
+ return suite
+
+ def was_oldstable(self, limit):
+ suite = self.suite_to_codename(self.settings['distribution'])
+ # this check is only for debian
+ if not self.debian_info.valid(suite):
+ return False
+ return suite == self.debian_info.old(limit)
+
+ def was_stable(self, limit):
+ suite = self.suite_to_codename(self.settings['distribution'])
+ # this check is only for debian
+ if not self.debian_info.valid(suite):
+ return False
+ return suite == self.debian_info.stable(limit)
+
+ def kernel_package(self):
+ packages = []
+ if not self.settings['no-kernel']:
+ if self.settings['kernel-package']:
+ return packages
+ if self.settings['arch'] == 'i386':
+ # wheezy (which became oldstable on 04/25/2015) used '486'
+ if self.was_oldstable(datetime.date(2015, 4, 26)):
+ kernel_arch = '486'
+ else:
+ kernel_arch = '586'
+ elif self.settings['arch'] == 'armhf':
+ kernel_arch = 'armmp'
+ else:
+ kernel_arch = self.settings['arch']
+ packages.append('linux-image-%s' % kernel_arch)
+ return packages
diff --git a/vmdebootstrap/constants.py b/vmdebootstrap/constants.py
new file mode 100644
index 0000000..9f39415
--- /dev/null
+++ b/vmdebootstrap/constants.py
@@ -0,0 +1,52 @@
+"""
+ Constants which can be used by any handler
+"""
+# -*- coding: utf-8 -*-
+#
+# constants.py
+#
+# Copyright 2015 Neil Williams <codehelp@debian.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+arch_table = { # pylint: disable=invalid-name
+ 'amd64': {
+ 'removable': '/EFI/boot/bootx64.efi', # destination location
+ 'install': '/EFI/debian/grubx64.efi', # package location
+ 'package': 'grub-efi-amd64', # bootstrap package
+ 'bin_package': 'grub-efi-amd64-bin', # binary only
+ 'extra': 'i386', # architecture to add binary package
+ 'exclusive': False, # only EFI supported for this arch.
+ 'target': 'x86_64-efi', # grub target name
+ },
+ 'i386': {
+ 'removable': '/EFI/boot/bootia32.efi',
+ 'install': '/EFI/debian/grubia32.efi',
+ 'package': 'grub-efi-ia32',
+ 'bin_package': 'grub-efi-ia32-bin',
+ 'extra': None,
+ 'exclusive': False,
+ 'target': 'i386-efi',
+ },
+ 'arm64': {
+ 'removable': '/EFI/boot/bootaa64.efi',
+ 'install': '/EFI/debian/grubaa64.efi',
+ 'package': 'grub-efi-arm64',
+ 'bin_package': 'grub-efi-arm64-bin',
+ 'extra': None,
+ 'exclusive': True,
+ 'target': 'arm64-efi',
+ }
+}
diff --git a/vmdebootstrap/extlinux.py b/vmdebootstrap/extlinux.py
new file mode 100644
index 0000000..5698f6c
--- /dev/null
+++ b/vmdebootstrap/extlinux.py
@@ -0,0 +1,107 @@
+"""
+ Wrapper for Extlinux support
+"""
+# -*- coding: utf-8 -*-
+#
+# extlinux.py
+#
+# Copyright 2015 Neil Williams <codehelp@debian.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import re
+import os
+import time
+import cliapp
+import logging
+from vmdebootstrap.base import Base, runcmd
+
+# pylint: disable=missing-docstring
+
+
+class ExtLinux(Base):
+
+ name = 'extlinux'
+
+ def __init__(self):
+ super(ExtLinux, self).__init__()
+
+ def install_extlinux(self, rootdev, rootdir):
+ if not os.path.exists("/usr/bin/extlinux"):
+ self.message("extlinux not installed, skipping.")
+ return
+ self.message('Installing extlinux')
+
+ def find(pattern):
+ dirname = os.path.join(rootdir, 'boot')
+ basenames = os.listdir(dirname)
+ logging.debug('find: %s', basenames)
+ for basename in basenames:
+ if re.search(pattern, basename):
+ return os.path.join('boot', basename)
+ raise cliapp.AppException('Cannot find match: %s' % pattern)
+
+ try:
+ kernel_image = find('vmlinuz-.*')
+ initrd_image = find('initrd.img-.*')
+ except cliapp.AppException as exc:
+ self.message("Unable to find kernel. Not installing extlinux.")
+ logging.debug("No kernel found. %s. Skipping install of extlinux.", exc)
+ return
+
+ out = runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
+ '-s', 'UUID', rootdev])
+ uuid = out.splitlines()[0].strip()
+
+ conf = os.path.join(rootdir, 'extlinux.conf')
+ logging.debug('configure extlinux %s', conf)
+ kserial = 'console=ttyS0,115200' if self.settings['serial-console'] else ''
+ extserial = 'serial 0 115200' if self.settings['serial-console'] else ''
+ msg = '''
+default linux
+timeout 1
+
+label linux
+kernel %(kernel)s
+append initrd=%(initrd)s root=UUID=%(uuid)s ro %(kserial)s
+%(extserial)s
+''' % {
+ 'kernel': kernel_image, # pylint: disable=bad-continuation
+ 'initrd': initrd_image, # pylint: disable=bad-continuation
+ 'uuid': uuid, # pylint: disable=bad-continuation
+ 'kserial': kserial, # pylint: disable=bad-continuation
+ 'extserial': extserial, # pylint: disable=bad-continuation
+ } # pylint: disable=bad-continuation
+ logging.debug("extlinux config:\n%s", msg)
+
+ # python multiline string substitution is just ugly.
+ # use an external file or live with the mangling, no point in
+ # mangling the string to remove spaces just to keep it pretty in source.
+ ext_f = open(conf, 'w')
+ ext_f.write(msg)
+
+ runcmd(['extlinux', '--install', rootdir])
+ runcmd(['sync'])
+ time.sleep(2)
+
+ def install_mbr(self):
+ if not self.settings['mbr'] and not self.settings['extlinux']:
+ return
+ if os.path.exists("/sbin/install-mbr"):
+ self.message('Installing MBR')
+ runcmd(['install-mbr', self.settings['image']])
+ else:
+ msg = "mbr enabled but /sbin/install-mbr not found" \
+ " - please install the mbr package."
+ raise cliapp.AppException(msg)
diff --git a/vmdebootstrap/filesystem.py b/vmdebootstrap/filesystem.py
new file mode 100644
index 0000000..df3e7b5
--- /dev/null
+++ b/vmdebootstrap/filesystem.py
@@ -0,0 +1,273 @@
+"""
+ Wrapper for filesystem utilities
+"""
+# -*- coding: utf-8 -*-
+#
+# filesystem.py
+#
+# Copyright 2015 Neil Williams <codehelp@debian.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import os
+import cliapp
+import logging
+from vmdebootstrap.base import (
+ Base,
+ runcmd,
+ copy_files
+)
+
+# pylint: disable=missing-docstring
+
+
+class Filesystem(Base):
+
+ name = 'filesystem'
+
+ def __init__(self):
+ super(Filesystem, self).__init__()
+ self.settings = None
+ self.devices = {
+ 'rootdir': None,
+ 'rootdev': None,
+ 'bootdev': None,
+ 'boottype': None,
+ 'roottype': None,
+ 'swapdev': None,
+ }
+
+ def define_settings(self, settings):
+ self.settings = settings
+ self.devices['roottype'] = self.settings['roottype']
+
+ def chown(self):
+ if not self.settings['owner']:
+ return
+ # Change image owner after completed build
+ if self.settings['image']:
+ filename = self.settings['image']
+ elif self.settings['tarball']:
+ filename = self.settings['tarball']
+ elif self.settings['squash']:
+ filename = self.settings['squash']
+ else:
+ return
+ self.message("Changing owner to %s" % self.settings["owner"])
+ runcmd(["chown", "-R", self.settings["owner"], filename])
+
+ def update_initramfs(self):
+ rootdir = self.devices['rootdir']
+ if not rootdir:
+ raise cliapp.AppException("rootdir not set")
+ cmd = os.path.join('usr', 'sbin', 'update-initramfs')
+ if os.path.exists(os.path.join(str(rootdir), cmd)):
+ self.message("Updating the initramfs")
+ runcmd(['chroot', rootdir, cmd, '-u'])
+
+ def setup_kpartx(self):
+ bootindex = None
+ swapindex = None
+ out = runcmd(['kpartx', '-avs', self.settings['image']])
+ if self.settings['bootsize'] and self.settings['swap'] > 0:
+ bootindex = 0
+ rootindex = 1
+ swapindex = 2
+ parts = 3
+ elif self.settings['use-uefi']:
+ bootindex = 0
+ rootindex = 1
+ parts = 2
+ elif self.settings['use-uefi'] and self.settings['swap'] > 0:
+ bootindex = 0
+ rootindex = 1
+ swapindex = 2
+ parts = 3
+ elif self.settings['bootsize']:
+ bootindex = 0
+ rootindex = 1
+ parts = 2
+ elif self.settings['swap'] > 0:
+ rootindex = 0
+ swapindex = 1
+ parts = 2
+ else:
+ rootindex = 0
+ parts = 1
+ boot = None
+ swap = None
+ devices = [line.split()[2]
+ for line in out.splitlines()
+ if line.startswith('add map ')]
+ if len(devices) != parts:
+ msg = 'Surprising number of partitions - check output of losetup -a'
+ logging.debug("%s", runcmd(['losetup', '-a']))
+ logging.debug("%s: devices=%s parts=%s", msg, devices, parts)
+ raise cliapp.AppException(msg)
+ root = '/dev/mapper/%s' % devices[rootindex]
+ if self.settings['bootsize'] or self.settings['use-uefi']:
+ boot = '/dev/mapper/%s' % devices[bootindex]
+ if self.settings['swap'] > 0:
+ swap = '/dev/mapper/%s' % devices[swapindex]
+ self.devices['rootdev'] = root
+ self.devices['bootdev'] = boot
+ self.devices['swap'] = swap
+
+ def mkfs(self, device, fstype):
+ self.message('Creating filesystem %s' % fstype)
+ runcmd(['mkfs', '-t', fstype, device])
+
+ def create_fstab(self):
+ rootdir = self.devices['rootdir']
+ rootdev = self.devices['rootdev']
+ bootdev = self.devices['bootdev']
+ boottype = self.devices['boottype']
+ roottype = self.devices['roottype']
+
+ def fsuuid(device):
+ out = runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
+ '-s', 'UUID', device])
+ return out.splitlines()[0].strip()
+
+ if rootdev:
+ rootdevstr = 'UUID=%s' % fsuuid(rootdev)
+ else:
+ rootdevstr = '/dev/sda1'
+
+ if bootdev and not self.settings['use-uefi']:
+ bootdevstr = 'UUID=%s' % fsuuid(bootdev)
+ else:
+ bootdevstr = None
+
+ if not rootdir:
+ raise cliapp.AppException("rootdir not set")
+
+ fstab = os.path.join(str(rootdir), 'etc', 'fstab')
+ with open(fstab, 'w') as fstab:
+ fstab.write('proc /proc proc defaults 0 0\n')
+ fstab.write('%s / %s errors=remount-ro 0 1\n' % (rootdevstr, roottype))
+ if bootdevstr:
+ fstab.write('%s /boot %s errors=remount-ro 0 2\n' % (bootdevstr, boottype))
+ if self.settings['swap'] > 0:
+ fstab.write("/dev/sda3 swap swap defaults 0 0\n")
+ elif self.settings['swap'] > 0:
+ fstab.write("/dev/sda2 swap swap defaults 0 0\n")
+
+ def squash_rootfs(self):
+ """
+ Run squashfs on the rootfs within the image.
+ Copy the initrd and the kernel out, squashfs the rest.
+ Also UEFI files, if enabled, ESP partition as a vfat image. TBD.
+ """
+ if not self.settings['squash']:
+ return
+ if not os.path.exists('/usr/bin/mksquashfs'):
+ logging.warning("Squash selected but mksquashfs not found!")
+ return
+ if not os.path.exists(self.settings['squash']):
+ os.mkdir(self.settings['squash'])
+ suffixed = os.path.join(self.settings['squash'], "filesystem.squashfs")
+ if os.path.exists(suffixed):
+ os.unlink(suffixed)
+ self.message("Running mksquashfs on rootfs.")
+ msg = runcmd(
+ ['mksquashfs', self.devices['rootdir'], suffixed,
+ '-no-progress', '-comp', 'xz'], ignore_fail=False)
+ logging.debug(msg)
+ check_size = os.path.getsize(suffixed)
+ logging.debug("Created squashfs: %s", suffixed)
+ if check_size < (1024 * 1024):
+ logging.warning(
+ "%s appears to be too small! %s bytes",
+ suffixed, check_size)
+ else:
+ logging.debug("squashed size: %s", check_size)
+ bootdir = os.path.join(self.devices['rootdir'], 'boot')
+ # copying the boot/* files
+ self.message("Copying boot files out of squashfs")
+ copy_files(bootdir, self.settings['squash'])
+
+ def configure_apt(self):
+ rootdir = self.devices['rootdir']
+ if not self.settings['configure-apt'] or not self.settings['apt-mirror']:
+ return
+ if not rootdir:
+ raise cliapp.AppException("rootdir not set")
+ # use the distribution and mirror to create an apt source
+ self.message("Configuring apt to use distribution and mirror")
+ conf = os.path.join(str(rootdir), 'etc', 'apt', 'sources.list.d', 'base.list')
+ logging.debug('configure apt %s', conf)
+ mirror = self.settings['mirror']
+ if self.settings['apt-mirror']:
+ mirror = self.settings['apt-mirror']
+ self.message("Setting apt mirror to %s" % mirror)
+ os.unlink(os.path.join(str(rootdir), 'etc', 'apt', 'sources.list'))
+ source = open(conf, 'w')
+ line = 'deb %s %s main\n' % (mirror, self.settings['distribution'])
+ source.write(line)
+ line = '#deb-src %s %s main\n' % (mirror, self.settings['distribution'])
+ source.write(line)
+ source.close()
+ # ensure the apt sources have valid lists
+ runcmd(['chroot', rootdir, 'apt-get', '-qq', 'update'])
+
+ def list_installed_pkgs(self):
+ if not self.settings['pkglist']:
+ return
+ rootdir = self.devices['rootdir']
+ # output the list of installed packages for sources identification
+ self.message("Creating a list of installed binary package names")
+ out = runcmd(['chroot', rootdir,
+ 'dpkg-query', '-W', "-f='${Package}.deb\n'"])
+ with open('dpkg.list', 'w') as dpkg:
+ dpkg.write(out)
+
+ def remove_udev_persistent_rules(self):
+ rootdir = self.devices['rootdir']
+ if not rootdir:
+ raise cliapp.AppException("rootdir not set")
+ self.message('Removing udev persistent cd and net rules')
+ for xrule in ['70-persistent-cd.rules', '70-persistent-net.rules']:
+ pathname = os.path.join(str(rootdir), 'etc', 'udev', 'rules.d', xrule)
+ if os.path.exists(pathname):
+ logging.debug('rm %s', pathname)
+ os.remove(pathname)
+ else:
+ logging.debug('not removing non-existent %s', pathname)
+
+ def set_hostname(self):
+ rootdir = self.devices['rootdir']
+ hostname = self.settings['hostname']
+ if not rootdir:
+ raise cliapp.AppException("rootdir not set")
+ with open(os.path.join(str(rootdir), 'etc', 'hostname'), 'w') as fhost:
+ fhost.write('%s\n' % hostname)
+
+ etc_hosts = os.path.join(str(rootdir), 'etc', 'hosts')
+ try:
+ with open(etc_hosts, 'r') as fhost:
+ data = fhost.read()
+ with open(etc_hosts, 'w') as fhosts:
+ for line in data.splitlines():
+ if line.startswith('127.0.0.1'):
+ line += ' %s' % hostname
+ fhosts.write('%s\n' % line)
+ except IOError:
+ pass
+
+ def make_rootfs_part(self, extent):
+ bootsize = self.settings['esp-size'] / (1024 * 1024) + 1
+ runcmd(['parted', '-s', self.settings['image'],
+ 'mkpart', 'primary', str(bootsize), extent])
diff --git a/vmdebootstrap/grub.py b/vmdebootstrap/grub.py
new file mode 100644
index 0000000..95b17eb
--- /dev/null
+++ b/vmdebootstrap/grub.py
@@ -0,0 +1,123 @@
+"""
+ Wrapper for Grub operations
+"""
+# -*- coding: utf-8 -*-
+#
+# grub.py
+#
+# Copyright 2015 Neil Williams <codehelp@debian.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# pylint: disable=missing-docstring,duplicate-code
+
+
+import os
+import cliapp
+import logging
+from vmdebootstrap.base import (
+ Base,
+ runcmd,
+ mount_wrapper,
+ umount_wrapper
+)
+from vmdebootstrap.uefi import arch_table
+
+
+def grub_serial_console(rootdir):
+ cmdline = 'GRUB_CMDLINE_LINUX_DEFAULT="console=tty0 console=tty1 console=ttyS0,38400n8"'
+ terminal = 'GRUB_TERMINAL="serial gfxterm"'
+ command = 'GRUB_SERIAL_COMMAND="serial --speed=38400 --unit=0 --parity=no --stop=1"'
+ grub_cfg = os.path.join(rootdir, 'etc', 'default', 'grub')
+ logging.debug("Allowing serial output in grub config %s", grub_cfg)
+ with open(grub_cfg, 'a+') as cfg:
+ cfg.write("# %s serial support\n" % os.path.basename(__file__))
+ cfg.write("%s\n" % cmdline)
+ cfg.write("%s\n" % terminal)
+ cfg.write("%s\n" % command)
+
+
+class GrubHandler(Base):
+
+ name = 'grub'
+
+ def __init__(self):
+ super(GrubHandler, self).__init__()
+
+ def install_grub2(self, rootdev, rootdir):
+ self.message("Configuring grub2")
+ # rely on kpartx using consistent naming to map loop0p1 to loop0
+ grub_opts = os.path.join('/dev', os.path.basename(rootdev)[:-2])
+ if self.settings['serial-console']:
+ grub_serial_console(rootdir)
+ logging.debug("Running grub-install with options: %s", grub_opts)
+ mount_wrapper(rootdir)
+ try:
+ runcmd(['chroot', rootdir, 'update-grub'])
+ runcmd(['chroot', rootdir, 'grub-install', grub_opts])
+ except cliapp.AppException as exc:
+ logging.warning(exc)
+ self.message("Failed. Is grub2-common installed? Using extlinux.")
+ umount_wrapper(rootdir)
+ return False
+ umount_wrapper(rootdir)
+ return True
+
+ def install_grub_uefi(self, rootdir):
+ ret = True
+ self.message("Configuring grub-uefi")
+ target = arch_table[self.settings['arch']]['target']
+ grub_opts = "--target=%s" % target
+ logging.debug("Running grub-install with options: %s", grub_opts)
+ mount_wrapper(rootdir)
+ try:
+ runcmd(['chroot', rootdir, 'update-grub'])
+ runcmd(['chroot', rootdir, 'grub-install', grub_opts])
+ except cliapp.AppException as exc:
+ logging.warning(exc)
+ ret = False
+ self.message(
+ "Failed to configure grub-uefi for %s" %
+ self.settings['arch'])
+ finally:
+ umount_wrapper(rootdir)
+ if not ret:
+ raise cliapp.AppException("Failed to install grub uefi")
+
+ def install_extra_grub_uefi(self, rootdir):
+ ret = True
+ extra = arch_table[self.settings['arch']]['extra']
+ if extra:
+ logging.debug("Installing extra grub support for %s", extra)
+ mount_wrapper(rootdir)
+ target = arch_table[extra]['target']
+ grub_opts = "--target=%s" % target
+ self.message("Adding grub target %s" % grub_opts)
+ try:
+ runcmd(['chroot', rootdir, 'update-grub'])
+ runcmd(['chroot', rootdir, 'grub-install', grub_opts])
+ except cliapp.AppException as exc:
+ logging.warning(exc)
+ ret = False
+ self.message(
+ "Failed to configure grub-uefi for %s" % extra)
+ finally:
+ umount_wrapper(rootdir)
+ if not ret:
+ raise cliapp.AppException("Failed to install extra grub uefi")
+
+ def grub_packages(self):
+ if self.settings['grub'] and not self.settings['use-uefi']:
+ return ['grub-pc']
+ return []
diff --git a/vmdebootstrap/uefi.py b/vmdebootstrap/uefi.py
new file mode 100644
index 0000000..bf04ae8
--- /dev/null
+++ b/vmdebootstrap/uefi.py
@@ -0,0 +1,167 @@
+"""
+ Wrapper for UEFI operations
+"""
+# -*- coding: utf-8 -*-
+#
+# uefi.py
+#
+# Copyright 2015 Neil Williams <codehelp@debian.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# pylint: disable=missing-docstring,duplicate-code
+
+
+import os
+import cliapp
+import logging
+from vmdebootstrap.base import (
+ Base,
+ runcmd,
+ mount_wrapper,
+ umount_wrapper,
+)
+from vmdebootstrap.constants import arch_table
+
+
+class Uefi(Base):
+
+ name = 'uefi'
+
+ def __init__(self):
+ super(Uefi, self).__init__()
+ self.bootdir = None
+
+ def check_settings(self, oldstable=False):
+ if not self.settings['use-uefi'] and self.settings['esp-size'] != 5242880:
+ raise cliapp.AppException(
+ 'You must specify use-uefi for esp-size to have effect')
+ if self.settings['arch'] in arch_table and\
+ arch_table[self.settings['arch']]['exclusive'] and\
+ not self.settings['use-uefi']:
+ raise cliapp.AppException(
+ 'Only UEFI is supported on %s' % self.settings['arch'])
+ elif self.settings['use-uefi'] and self.settings['arch'] not in arch_table:
+ raise cliapp.AppException(
+ '%s is not a supported UEFI architecture' % self.settings['arch'])
+ if self.settings['use-uefi'] and (
+ self.settings['bootsize'] or
+ self.settings['bootoffset']):
+ raise cliapp.AppException(
+ 'A separate boot partition is not supported with UEFI')
+
+ if self.settings['use-uefi'] and not self.settings['grub']:
+ raise cliapp.AppException(
+ 'UEFI without Grub is not supported.')
+
+ # wheezy (which became oldstable on 04/25/2015) only had amd64 uefi
+ if oldstable:
+ if self.settings['arch'] != 'amd64' and self.settings['use-uefi']:
+ raise cliapp.AppException(
+ 'Only amd64 supports UEFI in Wheezy')
+
+ def efi_packages(self):
+ packages = []
+ if not self.settings['use-uefi'] or\
+ self.settings['arch'] not in arch_table:
+ return packages
+ pkg = arch_table[self.settings['arch']]['package']
+ self.message("Adding %s to debootstrap" % pkg)
+ packages.append(pkg)
+ extra = arch_table[self.settings['arch']]['extra']
+ if extra and isinstance(extra, str):
+ bin_pkg = arch_table[str(extra)]['bin_package']
+ self.message("Adding support for %s using %s" % (extra, bin_pkg))
+ packages.append(bin_pkg)
+ return packages
+
+ def copy_efi_binary(self, efi_removable, efi_install):
+ if self.settings['arch'] not in arch_table:
+ return
+ logging.debug("using bootdir=%s", self.bootdir)
+ if efi_removable.startswith('/'):
+ efi_removable = efi_removable[1:]
+ if efi_install.startswith('/'):
+ efi_install = efi_install[1:]
+ efi_output = os.path.join(self.bootdir, efi_removable)
+ efi_input = os.path.join(self.bootdir, efi_install)
+ logging.debug("moving %s to %s", efi_output, efi_input)
+ if not os.path.exists(efi_input):
+ logging.warning("%s does not exist (%s)", efi_input, efi_install)
+ raise cliapp.AppException("Missing %s" % efi_input)
+ if not os.path.exists(os.path.dirname(efi_output)):
+ os.makedirs(os.path.dirname(efi_output))
+ logging.debug(
+ 'Moving UEFI support: %s -> %s', efi_input, efi_output)
+ if os.path.exists(efi_output):
+ os.unlink(efi_output)
+ os.rename(efi_input, efi_output)
+
+ def configure_efi(self, rootdir):
+ """
+ Copy the bootloader file from the package into the location
+ so needs to be after grub and kernel already installed.
+ """
+ if self.settings['arch'] not in arch_table:
+ return
+ self.message('Configuring EFI')
+ mount_wrapper(rootdir)
+ efi_removable = str(arch_table[self.settings['arch']]['removable'])
+ efi_install = str(arch_table[self.settings['arch']]['install'])
+ self.message('Installing UEFI support binary')
+ logging.debug("moving %s to %s", efi_removable, efi_install)
+ try:
+ self.copy_efi_binary(efi_removable, efi_install)
+ finally:
+ umount_wrapper(rootdir)
+
+ def configure_extra_efi(self, rootdir):
+ if self.settings['arch'] not in arch_table:
+ return
+ extra = arch_table[self.settings['arch']]['extra']
+ if extra:
+ mount_wrapper(rootdir)
+ efi_removable = str(arch_table[extra]['removable'])
+ efi_install = str(arch_table[extra]['install'])
+ self.message('Copying UEFI support binary for %s' % extra)
+ try:
+ self.copy_efi_binary(efi_removable, efi_install)
+ finally:
+ umount_wrapper(rootdir)
+
+ def partition_esp(self):
+ if not self.settings['use-uefi']:
+ return
+ espsize = self.settings['esp-size'] / (1024 * 1024)
+ self.message("Using ESP size: %smib %s bytes" % (espsize, self.settings['esp-size']))
+ runcmd(['parted', '-s', self.settings['image'],
+ 'mkpart', 'primary', 'fat32',
+ '1', str(espsize)])
+ runcmd(['parted', '-s', self.settings['image'],
+ 'set', '1', 'boot', 'on'])
+ runcmd(['parted', '-s', self.settings['image'],
+ 'set', '1', 'esp', 'on'])
+
+ def prepare_esp(self, rootdir, bootdev):
+ self.bootdir = '%s/%s/%s' % (rootdir, 'boot', 'efi')
+ logging.debug("bootdir:%s", self.bootdir)
+ self.mkfs(bootdev, fstype='vfat')
+ os.makedirs(self.bootdir)
+ return self.bootdir
+
+ def make_root(self, extent):
+ bootsize = self.settings['esp-size'] / (1024 * 1024) + 1
+ runcmd(['parted', '-s', self.settings['image'],
+ 'mkpart', 'primary', str(bootsize), extent])