From 4f11597d495853c4ef8d10d9cadd13c231b2bec3 Mon Sep 17 00:00:00 2001
From: VG <vg@devys.org>
Date: Fri, 28 Oct 2016 23:20:19 +0200
Subject: m

---
 build-grub2-instructions.txt |   7 +
 extract-kernel.py            |  30 ++++
 make-vm-debootstrap          | 392 +++++++++++++++++++++++++++++++++++++++++++
 make-vm-debootstrap-efi.py   | 365 ++++++++++++++++++++++++++++++++++++++++
 make-vm-debootstrap.py.orig  | 331 ++++++++++++++++++++++++++++++++++++
 make_vm_debootstrap.py       | 299 +++++++++++++++++++++++++++++++++
 make_vm_debootstrap_armhf.py |  11 ++
 start-arm-vm.sh              |  15 ++
 8 files changed, 1450 insertions(+)
 create mode 100644 build-grub2-instructions.txt
 create mode 100755 extract-kernel.py
 create mode 100755 make-vm-debootstrap
 create mode 100755 make-vm-debootstrap-efi.py
 create mode 100755 make-vm-debootstrap.py.orig
 create mode 100755 make_vm_debootstrap.py
 create mode 100755 make_vm_debootstrap_armhf.py
 create mode 100755 start-arm-vm.sh

diff --git a/build-grub2-instructions.txt b/build-grub2-instructions.txt
new file mode 100644
index 0000000..fe2cf5a
--- /dev/null
+++ b/build-grub2-instructions.txt
@@ -0,0 +1,7 @@
+# amd64-efi
+./configure --with-platform=efi --target=x86_64
+make
+
+# armhf
+./configure --target=arm-linux-gnueabihf
+make
diff --git a/extract-kernel.py b/extract-kernel.py
new file mode 100755
index 0000000..71dde88
--- /dev/null
+++ b/extract-kernel.py
@@ -0,0 +1,30 @@
+#!/usr/bin/python3
+
+
+import os
+import shutil
+import subprocess
+import sys
+
+def get_vmlinuz_path():
+    return os.path.join('mnt', os.readlink('mnt/vmlinuz').lstrip('/'))
+
+def get_initrd_path():
+    return os.path.join('mnt', os.readlink('mnt/initrd.img').lstrip('/'))
+
+if not os.path.exists('mnt'):
+    os.mkdir('mnt')
+
+subprocess.run([
+    'guestmount',
+    '-a', 'unstable-armhf.qcow2',
+    '-m', '/dev/sda1',
+    '--ro',
+    'mnt'])
+print(get_vmlinuz_path())
+print(get_initrd_path())
+
+shutil.copyfile(get_vmlinuz_path(), 'vmlinuz', follow_symlinks=True)
+shutil.copyfile(get_initrd_path(), 'initrd.img', follow_symlinks=True)
+
+subprocess.run(['fusermount', '-u', 'mnt'])
diff --git a/make-vm-debootstrap b/make-vm-debootstrap
new file mode 100755
index 0000000..667eead
--- /dev/null
+++ b/make-vm-debootstrap
@@ -0,0 +1,392 @@
+#!/bin/bash
+
+set -eu
+
+#if [ "$UID" != "0" ]; then
+#    exec sudo -- "$0" "$@"
+#fi
+
+traperror()
+{
+    echo "args: $*" >&2
+    echo "trace: ${FUNCNAME[*]} ${BASH_LINENO[*]}" >&2
+    exit 1
+}
+
+fail() { echo "${FUNCNAME[1]} ${BASH_LINENO[0]}: $*" >&2; exit 1; }
+#trap 'fail unhandled exception' ERR
+trap 'traperror $LINENO ${FUNCNAME:-main} $BASH_LINENO; trap - ERR' ERR
+
+exec 3>&1
+exec >debootstrap.log
+echo "I: log=debootstrap.log">&2
+
+RELEASE=unstable
+#MIRROR=http://fr.archive.ubuntu.com/ubuntu/
+MIRROR=http://apt:9999/debian/
+ARCH=amd64
+PKGS="$(sed 's/^#.*$//'<<EOF
+apt
+aptitude
+bash
+bash-completion
+bind9-host
+bmon
+busybox
+bzip2
+curl
+ed
+grub2
+htop
+iftop
+ifupdown
+iotop
+iperf
+iproute2
+iptables
+iputils-ping
+isc-dhcp-client
+less
+lftp
+linux-image-virtual
+locales
+locales libui-dialog-perl dialog
+locate
+ncdu
+ncurses-base
+ncurses-term
+net-tools
+netbase
+netcat
+nload
+openssh-client
+openssh-server
+psmisc
+python3
+ranger
+rsync
+runit
+screen
+sed
+sl
+socat
+socklog
+strace
+tar
+tcpdump
+telnet
+tmux
+tree
+tzdata
+vim
+vim-nox
+vim-runtime
+w3m
+wget
+zsh
+EOF
+)"
+
+
+
+    fail I want to fail
+#false
+
+test0() {
+    traperror test
+    #fail test of the week
+    echo 'test0' >&2
+    false
+    echo 'test0-end' >&2
+}
+
+test0
+
+false
+
+#echo "Warning: will erase $BLOCKDEV with mkfs.ext4 in 10seconds...">&2
+#for i in $(seq 10 -1 0); do sleep 1; printf "%d... " $i>&2; done
+#echo>&2
+#mkfs -q -F -t ext4 -L rootfs "$BLOCKDEV" || exit 1
+#mkdir -p "$DESTDIR"
+#echo "mounting block device...">&2
+#mount -v -t ext4 "$BLOCKDEV" "$DESTDIR" || \
+#    mount -v -t ext4 -o loop "$BLOCKDEV" "$DESTDIR" || exit 1
+#echo "In case of error, please remember to umount $DESTDIR">&2
+
+# compacting qcow2 images files
+# You have to zerofill the data on the disk first.
+#   sfill -fllvz
+# Then clone the image file
+#   qemu-img convert -p -O qcow2 ./source.img ./packed.
+
+#modprobe nbd max_part=16
+#qemu-nbd -c /dev/nbd0 image.qcow2
+#partprobe /dev/nbd0
+#mount /dev/nbd0p1 /mnt/image
+
+echo "I: debootstraping...">&2
+qemu-debootstrap --arch="$ARCH" \
+    --include="$PKGS" --components=main,universe \
+    "$RELEASE" "$DESTDIR" "$MIRROR"
+
+if [ ! -e "$KERNEL" ];then
+    echo "I: wget kernel...">&2
+    wget -q "$MIRROR/dists/$RELEASE/main/installer-$ARCH/current/images/netboot/ubuntu-installer/$ARCH/linux" -O "$KERNEL"
+fi
+
+echo "I: adjust configuration...">&2
+rm -f "$DESTDIR/etc/localtime"
+cat "$DESTDIR/usr/share/zoneinfo/Europe/Paris" > "$DESTDIR/etc/localtime"
+
+# network
+cat >"$DESTDIR/etc/network/interfaces"<<EOF
+auto lo
+iface lo inet loopback
+
+auto eth0
+iface eth0 inet static
+address 10.1.1.252
+netmask 255.255.255.0
+gateway 10.1.1.254
+EOF
+echo "127.0.0.1 localhost localhost.localdomain">"$DESTDIR/etc/hosts"
+echo "localhost">"$DESTDIR/etc/hostname"
+echo "servername 10.1.1.254">"$DESTDIR/etc/resolv.conf"
+
+for f in "$DESTDIR"/etc/init/tty[1-6].conf; do
+    echo "manual">${f%.conf}.override
+done
+
+chmod +w "$DESTDIR/etc/sudoers"
+sed 's,#* *%sudo.*,%sudo ALL=(ALL) NOPASSWD: ALL,' -i "$DESTDIR/etc/sudoers"
+chmod -w "$DESTDIR/etc/sudoers"
+
+#UUID="$(blkid "$BLOCKDEV" | sed 's/^.*UUID="\([a-zA-Z0-9-]\+\)".*$/\1/')"
+cat >"$DESTDIR/etc/fstab"<<EOF
+# <device> <mount point> <type> <options> <dump>  <pass>
+/dev/vda   /     ext4 errors=remount-ro,noatime                      0 1
+tmpfs /tmp     tmpfs mode=1777,noatime,size=128m,nodev,nosuid,noexec 0 0
+tmpfs /var/log tmpfs mode=1777,noatime,size=4m,nodev,nosuid,noexec   0 0
+tmpfs /var/tmp tmpfs mode=1777,noatime,size=128m,nodev,nosuid,noexec 0 0
+
+#UUID=$UUID /    ext4 errors=remount-ro,noatime                      0 1
+#UUID=use blkid for uuid discovery /home ext4 noatime                0 2
+#UUID=6d9ac889-3c25-4e05-90f3-d183f8b14aca none  swap sw,auto        0 0
+EOF
+
+mkdir -p "$DESTDIR/etc/sv/ttyS0/"
+mkdir -p "$DESTDIR/etc/service/"
+cat >"$DESTDIR/etc/sv/ttyS0/run"<<"EOF"
+#!/bin/bash
+dev=/dev/ttyS0
+/bin/stty -F $dev sane clocal -crtscts -hupcl
+exec <$dev >$dev 2>$dev
+echo
+echo "Press enter to continue..."; read
+cd /root
+/usr/bin/env -i HOME=/root PATH=/usr/sbin:/usr/bin:/sbin:/bin TERM=screen-256color /usr/bin/setsid /bin/bash
+exec wait
+#exec /bin/bash
+#exec /usr/bin/env -i /bin/sh
+#exec /usr/bin/setsid /bin/sh
+EOF
+chmod +x "$DESTDIR/etc/sv/ttyS0/run"
+ln -sfv "/etc/sv/ttyS0/" "$DESTDIR/etc/service/"
+
+mkdir -p "$DESTDIR/etc/ssh"
+cat >"$DESTDIR/etc/ssh/sshd_config"<<EOF
+Port 22
+Protocol 2
+HostKey /etc/ssh/ssh_host_rsa_key
+HostKey /etc/ssh/ssh_host_dsa_key
+HostKey /etc/ssh/ssh_host_ecdsa_key
+UsePrivilegeSeparation yes
+KeyRegenerationInterval 3600
+ServerKeyBits 768
+SyslogFacility AUTH
+LogLevel INFO
+LoginGraceTime 120
+PermitRootLogin no
+StrictModes yes
+RSAAuthentication yes
+PubkeyAuthentication yes
+IgnoreRhosts yes
+RhostsRSAAuthentication no
+HostbasedAuthentication no
+PermitEmptyPasswords no
+ChallengeResponseAuthentication no
+PasswordAuthentication no
+X11Forwarding yes
+X11DisplayOffset 10
+PrintMotd no
+PrintLastLog yes
+TCPKeepAlive yes
+AcceptEnv LANG LC_*
+Subsystem sftp /usr/lib/openssh/sftp-server
+UsePAM yes
+AllowUsers calendros
+EOF
+
+cat > "$DESTDIR/etc/apt/sources.list" <<EOF
+deb $MIRROR $RELEASE main universe
+deb http://security.ubuntu.com/ubuntu/ $RELEASE-security main universe
+EOF
+
+# /etc/default files:
+cat > "$DESTDIR/etc/default/console-setup"<<EOF
+VERBOSE_OUTPUT="no"
+ACTIVE_CONSOLES="/dev/ttyS0"
+CHARMAP="UTF-8"
+CODESET="Uni2"
+FONTFACE="Fixed"
+FONTSIZE="16"
+if [ -f /etc/default/keyboard ]; then
+    . /etc/default/keyboard
+fi
+EOF
+cat >"$DESTDIR/etc/default/keyboard"<<EOF
+XKBMODEL="pc105"
+XKBLAYOUT="fr"
+XKBVARIANT="bepo"
+XKBOPTIONS=""
+EOF
+cat >"$DESTDIR/etc/default/locale"<<EOF
+LANG="en_US.UTF-8"
+LC_TIME="en_DK.UTF-8"
+LC_PAPER="en_GB.UTF-8"
+LC_MEASUREMENT="en_GB.UTF-8"
+EOF
+
+cat >>"$DESTDIR/etc/bash.bashrc"<<EOF
+# enable bash completion in interactive shells
+if ! shopt -oq posix; then
+  if [ -f /usr/share/bash-completion/bash_completion ]; then
+    . /usr/share/bash-completion/bash_completion
+  elif [ -f /etc/bash_completion ]; then
+    . /etc/bash_completion
+  fi
+fi
+alias ls="ls --color=aut"
+alias l="ls -CF"
+alias ll="l -lh"
+alias la="l -a"
+alias e="vim"
+alias rm='rm -i'
+alias cp='cp -i'
+alias mv='mv -i'
+export PAGER=less
+export EDITOR=vim
+export VISUAL=vim
+EOF
+
+mkdir -p "$DESTDIR/etc/vim"
+cat >"$DESTDIR/etc/vim/vimrc"<<EOF
+set nocompatible
+set modelines=0
+set backspace=2
+syntax on
+set autoindent
+set ts=4
+set shiftwidth=4
+set ruler
+set laststatus=2
+set statusline=%<%f%h%m%r%=%l,%c\ %P
+set wildmode=list:full
+set nobackup nowritebackup
+set hlsearch
+set whichwrap=<,>,[,]
+set scrolloff=1
+EOF
+
+rm -r "$DESTDIR/etc/skel"
+mkdir -p "$DESTDIR/etc/skel"
+
+cat > "$DESTDIR/init-stage2.sh" << EOF
+#!/bin/sh
+export PATH="/usr/sbin:/usr/bin:/sbin:/bin"
+mount -no remount,rw /
+mount -t proc proc /proc
+mount -t sysfs sysfs /sys
+
+/debootstrap/debootstrap --second-stage
+
+# second stage may unmount this...
+[ -e /proc/mounts ] || mount -t proc proc /proc
+[ -e /sys/kernel ] || mount -t sysfs sysfs /sys
+busybox mdev -s
+
+echo '------ mounts:'
+cat /proc/mounts
+echo '------'
+
+mv /etc/default/extlinux /etc/default/extlinux.dist
+cat >/etc/default/extlinux<<EOF2
+EXTLINUX_UPDATE="true"
+EXTLINUX_ALTERNATIVES="default"
+EXTLINUX_DEFAULT="l0"
+EXTLINUX_ENTRIES="all"
+EXTLINUX_MEMDISK="true"
+EXTLINUX_MEMDISK_DIRECTORY="/boot"
+EXTLINUX_MENU_LABEL="Ubuntu GNU/Linux, kernel"
+EXTLINUX_OS_PROBER="false"
+EXTLINUX_PARAMETERS="ro quiet console=ttyS0,115200n8 panic=120 oops=panic"
+EXTLINUX_ROOT="root=/dev/vda"
+EXTLINUX_THEME="none"
+EXTLINUX_TIMEOUT="1" # wait 1 tenth of a second (0 is wait forever...)
+EOF2
+[ -e /dev/root ] || ln -sv /dev/vda /dev/root
+extlinux -i /boot/extlinux/
+
+passwd -d -l root
+useradd -M -b /home -d /home/calendros -U -G sudo -s /bin/bash calendros
+mkdir -p /home/calendros/.ssh/
+touch /home/calendros/.ssh/authorized_keys
+chmod 700 /home/calendros/.ssh
+chmod 600 /home/calendros/.ssh/authorized_keys
+chown calendros:calendros -R /home/calendros
+echo '. /etc/bash.bashrc' >~root/.bashrc
+echo '. ~/.bashrc' >~root/.bash_profile
+echo '. /etc/bash.bashrc' >~calendros/.bashrc
+echo '. ~/.bashrc' >~calendros/.bash_profile
+
+locale-gen en_US.UTF-8 en_GB.UTF-8 en_DK.UTF-8 fr_FR.UTF-8
+
+# dhclient may erase /etc/resolv.conf, and is not really useful here
+#ip link set eth0 up
+#dhclient eth0
+#apt-get update
+
+echo "Dropping a shell, press Ctrl-D or type exit to finish."
+/bin/bash
+
+rm -v /etc/udev/rules.d/70-persistent-*.rules
+
+echo "Bootstrap ended, poweroff in 10seconds..."
+rm /init-stage2.sh
+#sleep 10 || exec /bin/bash # for debug
+#poweroff & # does not work
+sync
+fuser -k /
+sleep 1
+mount -o remount,ro /
+exit 0 # will panic, but does not matter now (don't know how to shutdown o/w)
+EOF
+chmod +x "$DESTDIR/init-stage2.sh"
+
+cat >&2 <<EOF
+I: please umount destination blockdev...
+I: like: sudo umount -v "$DESTDIR"
+I: then run vm, deboostrap phase 2 (adjust blockdev)...
+I: export DEV=blockdev; qemu-system-x86_64 -machine accel=kvm -m 256 \\
+    -monitor unix:/tmp/debootstrap-monitor,server,nowait \\
+    -serial  unix:/tmp/debootstrap-serial,server \\
+    -vga vmware -display none \\
+    -drive file="\$DEV",if=virtio \\
+    -kernel "$KERNEL" \\
+    -append "quiet rootdelay=10 root=/dev/vda init=/init-stage2.sh console=ttyS0,115200n8"
+I: To copy this image to another host, remember to run:
+I: extlinux -i /yourcopyplace/boot/extlinux/
+I: end of script...
+EOF
+
diff --git a/make-vm-debootstrap-efi.py b/make-vm-debootstrap-efi.py
new file mode 100755
index 0000000..15654f4
--- /dev/null
+++ b/make-vm-debootstrap-efi.py
@@ -0,0 +1,365 @@
+#!/usr/bin/python3
+
+import os
+import sys
+import glob
+import subprocess
+import tempfile
+import shutil
+import re
+import functools
+
+
+image = 'unstable.qcow2'
+
+
+parameters = dict(
+    release='unstable',
+    #mirror='http://fr.archive.ubuntu.com/ubuntu/',
+    mirror='http://apt:9999/debian/',
+    arch='amd64',
+    packages='''
+        apt
+        aptitude
+        bash
+        bash-completion
+        bind9-host
+        bmon
+        busybox
+        bzip2
+        curl
+        ed
+        grub2
+        htop
+        iftop
+        ifupdown
+        iotop
+        iperf
+        iproute2
+        iptables
+        iputils-ping
+        less
+        lftp
+        linux-image-amd64
+        locales
+        ncdu
+        ncurses-base
+        ncurses-term
+        net-tools
+        netbase
+        netcat
+        nload
+        openssh-client
+        openssh-server
+        psmisc
+        python3
+        ranger
+        rsync
+        screen
+        sed
+        socat
+        strace
+        tar
+        tcpdump
+        telnet
+        tmux
+        tree
+        tzdata
+        vim
+        vim-nox
+        vim-runtime
+        w3m
+        wget
+        zsh
+    '''.split(),
+)
+
+
+open8 = functools.partial(open, encoding='utf8')
+numerical_sort = lambda y: [int(x) if x.isdigit() else x
+                            for x in re.split('(\d+)', y)]
+
+
+def mlstrip(s):
+    return re.sub(r'^\s*', '', s, flags=re.MULTILINE)
+
+
+def run(*l, **kw):
+    print('run:', *l)
+    return subprocess.run(*l, **kw)
+
+
+def reexec_root():
+    # run as root
+    if os.geteuid() != 0:
+        os.execvp("sudo", ["sudo"] + sys.argv)
+
+
+def system_customization(rootdir, bootpartuuid, rootpartuuid, rootfsuuid):
+    os.unlink(rootdir + '/etc/localtime')
+    open8(rootdir + '/etc/zoneinfo', 'w').write('Europe/Paris\n')
+    shutil.copy(rootdir + '/usr/share/zoneinfo/Europe/Paris',
+                rootdir + '/etc/localtime')
+
+    open8(rootdir + '/etc/network/interfaces', 'w').write(mlstrip(
+        '''\
+            auto lo
+            iface lo inet loopback
+
+            auto eth0
+            iface eth0 inet dhcp
+        '''))
+    open8(rootdir + '/etc/hosts', 'w').write(mlstrip(
+        '''\
+            127.0.0.1 localhost localhost.localdomain debian
+
+            # the following lines are desirable for IPv6 capable hosts
+            ::1     localhost ip6-localhost ip6-loopback
+            ff02::1 ip6-allnodes
+            ff02::2 ip6-allrouters
+        '''))
+    open8(rootdir + '/etc/hostname', 'w').write('debian\n')
+    open8(rootdir + '/etc/fstab', 'w').write(mlstrip(
+        '''\
+            # <device> <mount point> <type> <options> <dump>  <pass>
+            PARTUUID={rid} / ext4 errors=remount-ro,relatime 0 1
+            PARTUUID={bid} /boot/firmware vfat errors=remount-ro,relatime 0 2
+        '''.format(bid=bootpartuuid,
+                   rid=rootpartuuid)))
+
+    # activate serial console
+    run([
+        'ln',
+        '-s',
+        '/lib/systemd/system/serial-getty@.service',
+        rootdir + '/etc/systemd/system/getty.target.wants/serial-getty@ttyS0.service',
+        ], check=True)
+    os.makedirs(rootdir + '/etc/systemd/system/serial-getty@ttyS0.service.d',
+                mode=0o755)
+    open8(rootdir +
+        '/etc/systemd/system/serial-getty@ttyS0.service.d/autologin.conf',
+        'w').write(mlstrip(
+        '''\
+            [Service]
+            ExecStart=
+            ExecStart=-/sbin/agetty --autologin root --login-pause --noclear %I 115200,38400,9600 vt102
+        '''))
+
+    script_dir = os.path.dirname(os.path.realpath(__file__))
+    os.makedirs(rootdir + '/boot/firmware/efi/boot', mode=0o755)
+    with tempfile.NamedTemporaryFile(mode='w', encoding='utf8') as f:
+        f.write(mlstrip(
+            '''\
+                search --set=root --no-floppy --fs-uuid {rid} --hint hd0,gpt2
+                configfile /boot/grub/grub.cfg
+            '''.format(rid=rootfsuuid)))
+        f.flush()
+        run([
+            script_dir + '/grub-2.02~beta3/grub-mkimage',
+            '-d', script_dir + '/grub-2.02~beta3/grub-core',
+            '-O', 'x86_64-efi',
+            '--output={}/boot/firmware/efi/boot/bootx64.efi'.format(rootdir),
+            '--prefix=/efi/boot',
+            '--config=' + f.name,
+            ] + list(glob.glob(script_dir + '/grub-2.02~beta3/grub-core/*.mod')),
+            check=True)
+
+    open8(rootdir + '/etc/default/keyboard', 'w').write(mlstrip(
+        '''\
+            XKBMODEL="pc105"
+            XKBLAYOUT="fr"
+            XKBVARIANT="bepo"
+            XKBOPTIONS=""
+        '''))
+
+    open8(rootdir + '/etc/default/locale', 'w').write(mlstrip(
+        '''\
+            LANG="en_US.UTF-8"
+            LC_TIME="en_DK.UTF-8"
+            LC_PAPER="en_GB.UTF-8"
+            LC_MEASUREMENT="en_GB.UTF-8"
+        '''))
+
+    for locale in ['fr_FR', 'en_US', 'en_GB', 'en_DK', 'de_DE']:
+        run([
+            'localedef',
+            '--prefix={}'.format(rootdir),
+            '-f', 'UTF-8',
+            '-i', locale,
+            '{}.UTF-8'.format(locale)
+            ], check=True)
+
+    open8(rootdir + '/etc/bash.bashrc', 'a').write(mlstrip(
+        '''\
+
+            # enable bash completion in interactive shells
+            if ! shopt -oq posix; then
+            if [ -f /usr/share/bash-completion/bash_completion ]; then
+                . /usr/share/bash-completion/bash_completion
+            elif [ -f /etc/bash_completion ]; then
+                . /etc/bash_completion
+            fi
+            fi
+            alias ls="ls --color=aut"
+            alias l="ls -CF"
+            alias ll="l -lh"
+            alias la="l -a"
+            alias e="vim"
+            alias rm='rm -i'
+            alias cp='cp -i'
+            alias mv='mv -i'
+            export PAGER=less
+            export EDITOR=vim
+            export VISUAL=vim
+        '''))
+
+    open8(rootdir + '/etc/vim/vimrc', 'w').write(mlstrip(
+        '''\
+            set nocompatible
+            filetype plugin indent on
+            set autoindent
+            set background=dark
+            set backspace=2
+            set hidden
+            set hlsearch
+            set ignorecase
+            set incsearch
+            set laststatus=2
+            set modelines=0
+            set nobackup
+            set nowritebackup
+            set ruler
+            set scrolloff=3
+            set shiftwidth=4
+            set showcmd
+            set showmatch
+            set statusline=%<%f%h%m%r%=%l,%c\ %P
+            set ts=4
+            set whichwrap=<,>,[,]
+            set wildmode=list:full
+            syntax on
+        '''))
+
+    open8(rootdir + '/etc/default/grub', 'w').write(mlstrip(
+        '''\
+            GRUB_DEFAULT=0
+            GRUB_TIMEOUT=10
+            GRUB_DISTRIBUTOR=Debian
+            GRUB_TERMINAL=console
+            GRUB_SERIAL_COMMAND="serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1"
+            GRUB_CMDLINE_LINUX_DEFAULT=""
+            GRUB_CMDLINE_LINUX="text console=ttyS0,115200n8 console=tty0 net.ifnames=0"
+        '''))
+
+    kernel_params  = 'ro text console=ttyS0,115200n8'
+    kernel_params += ' console=tty0 net.ifnames=0'
+    open8(rootdir + '/boot/grub/grub.cfg', 'w').write(mlstrip(
+        '''\
+            terminal_output console
+            serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
+            set default=0
+            set timeout=3
+            menuentry 'default' {{
+                search --set=root --file /boot/grub/grub.cfg --hint hd0,gpt2
+                linux /vmlinuz root=PARTUUID={rid} {kp}
+                initrd /initrd.img
+            }}
+        '''.format(rid=rootpartuuid, kp=kernel_params)))
+
+
+def make_image():
+    device = None
+    rootdirobj = tempfile.TemporaryDirectory()
+    rootdir = rootdirobj.name
+    try:
+        run(['qemu-img', 'create', '-f', 'qcow2', image, '10G'], check=True)
+
+        run(['modprobe', 'nbd', 'max_part=16'])
+
+        for d in sorted(glob.glob('/dev/nbd*'), key=numerical_sort):
+            if run(['qemu-nbd', '-c', d, image]).returncode == 0:
+                device = d
+                break
+        else:
+            print('No free nbd device found for qemu-nbd')
+            raise SystemExit(1)
+
+        run([
+            'sgdisk',
+            '--set-alignment=8192',
+            '--zap-all',
+            '--new=1:4M:+300M',
+            '--change-name=1:boot',
+            '--typecode=1:EF00',
+            '--largest-new=2',
+            '--change-name=2:root',
+            '--typecode=2:8300',
+            device,
+            ], check=True)
+
+        run(['partprobe', device])
+        run(['mkfs.fat', '-n', 'boot', device+'p1'], check=True)
+        run(['mkfs.ext4', '-q', '-L', 'root', device+'p2'], check=True)
+        run(['mount', device+'p2', rootdir], check=True)
+        os.makedirs(rootdir + '/boot/firmware', mode=0o755)
+        run(['mount', device+'p1', rootdir+'/boot/firmware'], check=True)
+
+        run([
+            'qemu-debootstrap',
+            '--arch=' + parameters['arch'],
+            '--include=' + ','.join(parameters['packages']),
+            '--components=main,universe',
+            parameters['release'],
+            rootdir,
+            parameters['mirror'],
+            ], check=True)
+
+        rootpartuuid = (run([
+            'partx',
+            '--noheadings',
+            '--show',
+            '--output',
+            'UUID',
+            device+'p2'], check=True, stdout=subprocess.PIPE)
+            .stdout
+            .decode('utf8')
+            .strip())
+        bootpartuuid = (run([
+            'partx',
+            '--noheadings',
+            '--show',
+            '--output',
+            'UUID',
+            device+'p1'], check=True, stdout=subprocess.PIPE)
+            .stdout
+            .decode('utf8')
+            .strip())
+
+        rootfsuuid = [line.split()[-1]
+                for line in (run([ 'tune2fs', '-l', device+'p2'],
+                                 check=True, stdout=subprocess.PIPE)
+                             .stdout
+                             .decode('utf8')
+                             .splitlines())
+                if "filesystem uuid" in line.lower()][0]
+
+        system_customization(rootdir, bootpartuuid, rootpartuuid, rootfsuuid)
+
+    finally:
+        if rootdir:
+            cmd = ['umount'] + \
+                    ['{rootdir}/{}'.format(x, rootdir=rootdir)
+                     for x in 'proc sys dev boot/firmware'.split()]
+            run(cmd)
+            run(['umount', rootdir])
+        if device:
+            run(['qemu-nbd', '-d', device])
+
+
+def main():
+    reexec_root()
+    make_image()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/make-vm-debootstrap.py.orig b/make-vm-debootstrap.py.orig
new file mode 100755
index 0000000..62f1165
--- /dev/null
+++ b/make-vm-debootstrap.py.orig
@@ -0,0 +1,331 @@
+#!/usr/bin/python3
+
+import os
+import sys
+import glob
+import subprocess
+import tempfile
+import shutil
+import re
+import functools
+
+
+
+
+parameters = dict(
+    release='unstable',
+    #mirror='http://fr.archive.ubuntu.com/ubuntu/',
+    mirror='http://apt:9999/debian/',
+    arch='amd64',
+    image = 'unstable-amd64.qcow2',
+    packages='''
+        apt
+        aptitude
+        bash
+        bash-completion
+        bind9-host
+        bmon
+        busybox
+        bzip2
+        curl
+        ed
+        grub2
+        htop
+        iftop
+        ifupdown
+        iotop
+        iperf
+        iproute2
+        iptables
+        iputils-ping
+        less
+        lftp
+        linux-image-amd64
+        locales
+        ncdu
+        ncurses-base
+        ncurses-term
+        net-tools
+        netbase
+        netcat
+        nload
+        openssh-client
+        openssh-server
+        psmisc
+        python3
+        ranger
+        rsync
+        screen
+        sed
+        socat
+        strace
+        tar
+        tcpdump
+        telnet
+        tmux
+        tree
+        tzdata
+        vim
+        vim-nox
+        vim-runtime
+        w3m
+        wget
+        zsh
+    '''.split(),
+)
+
+
+open8 = functools.partial(open, encoding='utf8')
+numerical_sort = lambda y: [int(x) if x.isdigit() else x
+                            for x in re.split('(\d+)', y)]
+
+
+def mlstrip(s):
+    return re.sub(r'^\s*', '', s, flags=re.MULTILINE)
+
+
+def run(*l, **kw):
+    print('run:', *l)
+    return subprocess.run(*l, **kw)
+
+
+def reexec_root():
+    # run as root
+    if os.geteuid() != 0:
+        os.execvp("sudo", ["sudo"] + sys.argv)
+
+
+def system_customization(device, rootdir, rootpartuuid, rootfsuuid):
+    os.unlink(rootdir + '/etc/localtime')
+    open8(rootdir + '/etc/zoneinfo', 'w').write('Europe/Paris\n')
+    shutil.copy(rootdir + '/usr/share/zoneinfo/Europe/Paris',
+                rootdir + '/etc/localtime')
+
+    open8(rootdir + '/etc/network/interfaces', 'w').write(mlstrip(
+        '''\
+            auto lo
+            iface lo inet loopback
+
+            auto eth0
+            iface eth0 inet dhcp
+        '''))
+    open8(rootdir + '/etc/hosts', 'w').write(mlstrip(
+        '''\
+            127.0.0.1 localhost localhost.localdomain debian
+
+            # the following lines are desirable for IPv6 capable hosts
+            ::1     localhost ip6-localhost ip6-loopback
+            ff02::1 ip6-allnodes
+            ff02::2 ip6-allrouters
+        '''))
+    open8(rootdir + '/etc/hostname', 'w').write('debian\n')
+    open8(rootdir + '/etc/fstab', 'w').write(mlstrip(
+        '''\
+            # <device> <mount point> <type> <options> <dump>  <pass>
+            PARTUUID={rid} / ext4 errors=remount-ro,relatime 0 1
+        '''.format(rid=rootpartuuid)))
+
+    # activate serial console
+    run([
+        'ln',
+        '-s',
+        '/lib/systemd/system/serial-getty@.service',
+        rootdir + '/etc/systemd/system/getty.target.wants/serial-getty@ttyS0.service',
+        ], check=True)
+    os.makedirs(rootdir + '/etc/systemd/system/serial-getty@ttyS0.service.d',
+                mode=0o755)
+    open8(rootdir +
+        '/etc/systemd/system/serial-getty@ttyS0.service.d/autologin.conf',
+        'w').write(mlstrip(
+        '''\
+            [Service]
+            ExecStart=
+            ExecStart=-/sbin/agetty --autologin root --login-pause --noclear %I 115200,38400,9600 vt102
+        '''))
+
+    open8(rootdir + '/etc/default/keyboard', 'w').write(mlstrip(
+        '''\
+            XKBMODEL="pc105"
+            XKBLAYOUT="fr"
+            XKBVARIANT="bepo"
+            XKBOPTIONS=""
+        '''))
+
+    open8(rootdir + '/etc/default/locale', 'w').write(mlstrip(
+        '''\
+            LANG="en_US.UTF-8"
+            LC_TIME="en_DK.UTF-8"
+            LC_PAPER="en_GB.UTF-8"
+            LC_MEASUREMENT="en_GB.UTF-8"
+        '''))
+
+    for locale in ['fr_FR', 'en_US', 'en_GB', 'en_DK', 'de_DE']:
+        run([
+            'localedef',
+            '--prefix={}'.format(rootdir),
+            '-f', 'UTF-8',
+            '-i', locale,
+            '{}.UTF-8'.format(locale)
+            ], check=True)
+
+    open8(rootdir + '/etc/bash.bashrc', 'a').write(mlstrip(
+        '''\
+
+            # enable bash completion in interactive shells
+            if ! shopt -oq posix; then
+            if [ -f /usr/share/bash-completion/bash_completion ]; then
+                . /usr/share/bash-completion/bash_completion
+            elif [ -f /etc/bash_completion ]; then
+                . /etc/bash_completion
+            fi
+            fi
+            alias ls="ls --color=aut"
+            alias l="ls -CF"
+            alias ll="l -lh"
+            alias la="l -a"
+            alias e="vim"
+            alias rm='rm -i'
+            alias cp='cp -i'
+            alias mv='mv -i'
+            export PAGER=less
+            export EDITOR=vim
+            export VISUAL=vim
+        '''))
+
+    open8(rootdir + '/etc/vim/vimrc', 'w').write(mlstrip(
+        '''\
+            set nocompatible
+            filetype plugin indent on
+            set autoindent
+            set background=dark
+            set backspace=2
+            set hidden
+            set hlsearch
+            set ignorecase
+            set incsearch
+            set laststatus=2
+            set modelines=0
+            set nobackup
+            set nowritebackup
+            set ruler
+            set scrolloff=3
+            set shiftwidth=4
+            set showcmd
+            set showmatch
+            set statusline=%<%f%h%m%r%=%l,%c\ %P
+            set ts=4
+            set whichwrap=<,>,[,]
+            set wildmode=list:full
+            syntax on
+        '''))
+
+    open8(rootdir + '/etc/default/grub', 'w').write(mlstrip(
+        '''\
+            GRUB_DEFAULT=0
+            GRUB_TIMEOUT=1
+            GRUB_TERMINAL=serial
+            GRUB_SERIAL_COMMAND="serial --speed=115200 --word=8 --parity=no --stop=1"
+            GRUB_CMDLINE_LINUX_DEFAULT=""
+            GRUB_CMDLINE_LINUX="text console=ttyS0,115200n8 console=tty0 net.ifnames=0"
+        '''))
+
+    kernel_params  = 'ro text console=ttyS0,115200n8'
+    kernel_params += ' console=tty0 net.ifnames=0'
+    open8(rootdir + '/boot/grub/grub.cfg', 'w').write(mlstrip(
+        '''\
+            terminal_output serial
+            serial --speed=115200 --word=8 --parity=no --stop=1
+            set default=0
+            set timeout=1
+            menuentry 'default' {{
+                search --set=root --file /boot/grub/grub.cfg --hint hd0,gpt2
+                linux /vmlinuz root=PARTUUID={rid} {kp}
+                initrd /initrd.img
+            }}
+        '''.format(rid=rootpartuuid, kp=kernel_params)))
+
+    run(['grub-install',
+         '--boot-directory=' + rootdir + '/boot',
+         device])
+
+
+def make_image():
+    device = None
+    rootdirobj = tempfile.TemporaryDirectory()
+    rootdir = rootdirobj.name
+    try:
+        run(['qemu-img', 'create', '-f', 'qcow2', parameters.image, '20G'],
+            check=True)
+
+        run(['modprobe', 'nbd', 'max_part=16'])
+
+        for d in sorted(glob.glob('/dev/nbd*'), key=numerical_sort):
+            if run(['qemu-nbd', '-c', d, parameters.image]).returncode == 0:
+                device = d
+                break
+        else:
+            print('No free nbd device found for qemu-nbd')
+            raise SystemExit(1)
+
+        run([
+            'sgdisk',
+            '--set-alignment=8192',
+            '--zap-all',
+            '--new=1:1M:+1M',
+            '--change-name=1:boot',
+            '--typecode=1:EF02',
+            '--largest-new=2',
+            '--change-name=2:root',
+            '--typecode=2:8300',
+            device,
+            ], check=True)
+
+        run(['partprobe', device])
+        run(['mkfs.ext4', '-q', '-L', 'root', device+'p2'], check=True)
+        run(['mount', device+'p2', rootdir], check=True)
+
+        run([
+            'qemu-debootstrap',
+            '--arch=' + parameters['arch'],
+            '--include=' + ','.join(parameters['packages']),
+            '--components=main,universe',
+            parameters['release'],
+            rootdir,
+            parameters['mirror'],
+            ], check=True)
+
+        rootpartuuid = (run([
+            'partx',
+            '--noheadings',
+            '--show',
+            '--output',
+            'UUID',
+            device+'p2'], check=True, stdout=subprocess.PIPE)
+            .stdout
+            .decode('utf8')
+            .strip())
+
+        rootfsuuid = [line.split()[-1]
+                for line in (run([ 'tune2fs', '-l', device+'p2'],
+                                 check=True, stdout=subprocess.PIPE)
+                             .stdout
+                             .decode('utf8')
+                             .splitlines())
+                if "filesystem uuid" in line.lower()][0]
+
+        system_customization(device, rootdir, rootpartuuid, rootfsuuid)
+
+    finally:
+        if rootdir:
+            run(['umount', rootdir])
+        if device:
+            run(['qemu-nbd', '-d', device])
+
+
+def main():
+    reexec_root()
+    make_image()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/make_vm_debootstrap.py b/make_vm_debootstrap.py
new file mode 100755
index 0000000..5317099
--- /dev/null
+++ b/make_vm_debootstrap.py
@@ -0,0 +1,299 @@
+#!/usr/bin/python3
+
+import os
+import sys
+import glob
+import subprocess
+import tempfile
+import shutil
+import re
+import functools
+
+
+parameters = dict(
+    release='unstable',
+    #mirror='http://fr.archive.ubuntu.com/ubuntu/',
+    mirror='http://apt:9999/debian/',
+    arch='amd64',
+    image = 'unstable-amd64.qcow2',
+    packages='''
+        apt
+        aptitude
+        bash
+        bash-completion
+        bind9-host
+        bmon
+        busybox
+        bzip2
+        curl
+        ed
+        htop
+        iftop
+        ifupdown
+        iotop
+        iperf
+        iproute2
+        iptables
+        iputils-ping
+        less
+        lftp
+        locales
+        ncdu
+        ncurses-base
+        ncurses-term
+        net-tools
+        netbase
+        netcat
+        nload
+        openssh-client
+        openssh-server
+        psmisc
+        python3
+        ranger
+        rsync
+        screen
+        sed
+        socat
+        strace
+        tar
+        tcpdump
+        telnet
+        tmux
+        tree
+        tzdata
+        vim
+        vim-nox
+        vim-runtime
+        w3m
+        wget
+        zsh
+    '''.split(),
+    kernel_package = 'linux-image-amd64',
+    serial_console = 'ttyS0',
+)
+
+
+open8 = functools.partial(open, encoding='utf8')
+numerical_sort = lambda y: [int(x) if x.isdigit() else x
+                            for x in re.split('(\d+)', y)]
+
+
+def mlstrip(s):
+    return re.sub(r'^\s*', '', s, flags=re.MULTILINE)
+
+
+def run(*l, **kw):
+    print('run:', *l)
+    return subprocess.run(*l, **kw)
+
+
+def reexec_root():
+    # run as root
+    if os.geteuid() != 0:
+        os.execvp("sudo", ["sudo"] + sys.argv)
+
+
+def system_customization(device, rootdir, rootpartuuid, rootfsuuid):
+    os.unlink(rootdir + '/etc/localtime')
+    open8(rootdir + '/etc/zoneinfo', 'w').write('Europe/Paris\n')
+    shutil.copy(rootdir + '/usr/share/zoneinfo/Europe/Paris',
+                rootdir + '/etc/localtime')
+
+    open8(rootdir + '/etc/network/interfaces', 'w').write(mlstrip(
+        '''\
+            auto lo
+            iface lo inet loopback
+
+            auto eth0
+            iface eth0 inet dhcp
+        '''))
+    open8(rootdir + '/etc/hosts', 'w').write(mlstrip(
+        '''\
+            127.0.0.1 localhost localhost.localdomain debian
+
+            # the following lines are desirable for IPv6 capable hosts
+            ::1     localhost ip6-localhost ip6-loopback
+            ff02::1 ip6-allnodes
+            ff02::2 ip6-allrouters
+        '''))
+    open8(rootdir + '/etc/hostname', 'w').write('debian\n')
+    open8(rootdir + '/etc/fstab', 'w').write(mlstrip(
+        '''\
+            # <device> <mount point> <type> <options> <dump>  <pass>
+            PARTUUID={rid} / ext4 errors=remount-ro,relatime 0 1
+        '''.format(rid=rootpartuuid)))
+
+    # activate serial console on parameter['serial_console']
+    console = parameters['serial_console']
+    run([
+        'ln',
+        '-s',
+        '/lib/systemd/system/serial-getty@.service',
+        rootdir + '/etc/systemd/system/getty.target.wants/serial-getty@%s.service' % console,
+        ], check=True)
+    os.makedirs(rootdir + '/etc/systemd/system/serial-getty@%s.service.d' % console,
+                mode=0o755)
+    open8(rootdir +
+        '/etc/systemd/system/serial-getty@%s.service.d/autologin.conf' % console,
+        'w').write(mlstrip(
+        '''\
+            [Service]
+            ExecStart=
+            ExecStart=-/sbin/agetty --autologin root --login-pause --noclear %I 115200,38400,9600 vt102
+        '''))
+
+    open8(rootdir + '/etc/default/keyboard', 'w').write(mlstrip(
+        '''\
+            XKBMODEL="pc105"
+            XKBLAYOUT="fr"
+            XKBVARIANT="bepo"
+            XKBOPTIONS=""
+        '''))
+
+    open8(rootdir + '/etc/default/locale', 'w').write(mlstrip(
+        '''\
+            LANG="en_US.UTF-8"
+            LC_TIME="en_DK.UTF-8"
+            LC_PAPER="en_GB.UTF-8"
+            LC_MEASUREMENT="en_GB.UTF-8"
+        '''))
+
+    for locale in ['fr_FR', 'en_US', 'en_GB', 'en_DK', 'de_DE']:
+        run([
+            'localedef',
+            '--prefix={}'.format(rootdir),
+            '-f', 'UTF-8',
+            '-i', locale,
+            '{}.UTF-8'.format(locale)
+            ], check=True)
+
+    open8(rootdir + '/etc/bash.bashrc', 'a').write(mlstrip(
+        '''\
+
+            # enable bash completion in interactive shells
+            if ! shopt -oq posix; then
+            if [ -f /usr/share/bash-completion/bash_completion ]; then
+                . /usr/share/bash-completion/bash_completion
+            elif [ -f /etc/bash_completion ]; then
+                . /etc/bash_completion
+            fi
+            fi
+            alias ls="ls --color=aut"
+            alias l="ls -CF"
+            alias ll="l -lh"
+            alias la="l -a"
+            alias e="vim"
+            alias rm='rm -i'
+            alias cp='cp -i'
+            alias mv='mv -i'
+            export PAGER=less
+            export EDITOR=vim
+            export VISUAL=vim
+        '''))
+
+    open8(rootdir + '/etc/vim/vimrc', 'w').write(mlstrip(
+        '''\
+            set nocompatible
+            filetype plugin indent on
+            set autoindent
+            set background=dark
+            set backspace=2
+            set hidden
+            set hlsearch
+            set ignorecase
+            set incsearch
+            set laststatus=2
+            set modelines=0
+            set nobackup
+            set nowritebackup
+            set ruler
+            set scrolloff=3
+            set shiftwidth=4
+            set showcmd
+            set showmatch
+            set statusline=%<%f%h%m%r%=%l,%c\ %P
+            set ts=4
+            set whichwrap=<,>,[,]
+            set wildmode=list:full
+            syntax on
+        '''))
+
+
+def make_image():
+    device = None
+    rootdirobj = tempfile.TemporaryDirectory()
+    rootdir = rootdirobj.name
+    try:
+        run(['qemu-img', 'create', '-f', 'qcow2', parameters['image'], '20G'],
+            check=True)
+
+        run(['modprobe', 'nbd', 'max_part=16'])
+
+        for d in sorted(glob.glob('/dev/nbd*'), key=numerical_sort):
+            if run(['qemu-nbd', '-c', d, parameters['image']]).returncode == 0:
+                device = d
+                break
+        else:
+            print('No free nbd device found for qemu-nbd')
+            raise SystemExit(1)
+
+        run([
+            'sgdisk',
+            '--set-alignment=8192',
+            '--zap-all',
+            '--largest-new=1',
+            '--change-name=1:root',
+            '--typecode=1:8300',
+            device,
+            ], check=True)
+
+        run(['partprobe', device])
+        run(['mkfs.ext4', '-q', '-L', 'root', device+'p1'], check=True)
+        run(['mount', device+'p1', rootdir], check=True)
+
+        run([
+            'qemu-debootstrap',
+            '--arch=' + parameters['arch'],
+            '--include=' + ','.join(parameters['packages']
+                                    + [parameters['kernel_package']]),
+            '--components=main,universe',
+            parameters['release'],
+            rootdir,
+            parameters['mirror'],
+            ], check=True)
+
+        rootpartuuid = (run([
+            'partx',
+            '--noheadings',
+            '--show',
+            '--output',
+            'UUID',
+            device+'p1'], check=True, stdout=subprocess.PIPE)
+            .stdout
+            .decode('utf8')
+            .strip())
+
+        rootfsuuid = [line.split()[-1]
+                for line in (run([ 'tune2fs', '-l', device+'p1'],
+                                 check=True, stdout=subprocess.PIPE)
+                             .stdout
+                             .decode('utf8')
+                             .splitlines())
+                if "filesystem uuid" in line.lower()][0]
+
+        system_customization(device, rootdir, rootpartuuid, rootfsuuid)
+
+    finally:
+        if rootdir:
+            run(['umount', rootdir])
+        if device:
+            run(['qemu-nbd', '-d', device])
+
+
+def main():
+    reexec_root()
+    make_image()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/make_vm_debootstrap_armhf.py b/make_vm_debootstrap_armhf.py
new file mode 100755
index 0000000..69fc807
--- /dev/null
+++ b/make_vm_debootstrap_armhf.py
@@ -0,0 +1,11 @@
+#!/usr/bin/python3
+
+import make_vm_debootstrap
+
+make_vm_debootstrap.parameters['arch'] = 'armhf'
+make_vm_debootstrap.parameters['image'] = 'unstable-armhf.qcow2'
+make_vm_debootstrap.parameters['kernel_package'] = 'linux-image-armmp-lpae'
+make_vm_debootstrap.parameters['serial_console'] = 'ttyAMA0'
+
+if __name__ == '__main__':
+    make_vm_debootstrap.main()
diff --git a/start-arm-vm.sh b/start-arm-vm.sh
new file mode 100755
index 0000000..e801bc1
--- /dev/null
+++ b/start-arm-vm.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+set -eux
+
+snapshot=${SNAPSHOT:-on}
+extract_kernel=off
+
+[[ "$extract_kernel" = on ]] && ./extract-kernel.py
+
+exec qemu-system-arm -M virt -m 128M -nographic \
+    -kernel vmlinuz -initrd initrd.img \
+    -append 'root=/dev/vda1 rootwait=10 console=ttyAMA0,115200n8' \
+    -drive file=unstable-armhf.qcow2,snapshot=${snapshot},if=none,id=hd0 \
+    -device virtio-blk-device,drive=hd0 \
+    -device driver=virtio-net-device,netdev=net0 -netdev user,id=net0
-- 
cgit v1.2.3