From fb6fa99c0da96a45d458b2ffa0b9b2fe8890ac36 Mon Sep 17 00:00:00 2001 From: VG Date: Sun, 6 Nov 2016 17:32:20 +0100 Subject: move to a subdir since this repo will have other debootstrap scripts --- qemu-headless/make-vm-debootstrap-efi.py | 365 +++++++++++++++++++++++++++++++ 1 file changed, 365 insertions(+) create mode 100755 qemu-headless/make-vm-debootstrap-efi.py (limited to 'qemu-headless/make-vm-debootstrap-efi.py') diff --git a/qemu-headless/make-vm-debootstrap-efi.py b/qemu-headless/make-vm-debootstrap-efi.py new file mode 100755 index 0000000..15654f4 --- /dev/null +++ b/qemu-headless/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( + '''\ + # + 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() -- cgit v1.2.3