diff options
author | Helmut Grohne <helmut@subdivi.de> | 2022-12-20 14:29:17 +0100 |
---|---|---|
committer | Helmut Grohne <helmut@subdivi.de> | 2022-12-20 14:29:17 +0100 |
commit | e3258ab831172b3760577507b0437365f343cc5e (patch) | |
tree | 9a007fca948beb324dc4ea5acd10382665d73586 | |
download | debvm-e3258ab831172b3760577507b0437365f343cc5e.tar.gz |
initial checkin
-rw-r--r-- | README.md | 59 | ||||
-rwxr-xr-x | debvm-create | 158 | ||||
-rwxr-xr-x | debvm-run | 124 |
3 files changed, 341 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..63a2c0f --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +What is debvm? +============== + +The purpose of this tool is creating and running simple virtual machines based +on Debian releases. A typical application is testing. Consequently, this has +been designed to work without root privileges[^1]. Rather than do thing itself, +it builds primarily on +[mmdebstrap](https://gitlab.mister-muffin.de/josch/mmdebstrap/) and +[qemu](http://www.qemu.org/). + +How do you use it? +================== + +The first tool is `debvm-create`. It creates a rootfs ext4 raw filesystem +image. This image has configurable architecture, hostname, root's ssh key, +Debian mirror, and release as well as image size and included packages. In +essence, it is a wrapper constructing a complex `mmdebstrap` invocation. + +Given such an image, `debvm-run` can be used to run it. It extracts the kernel +and initrd from the filesystem image and constructs a suitable `qemu` +invocation. + +What do I need? +=============== + +A regular user account[^1] suffices. The following Debian packages should be +installed: + * `arch-test` (when running foreign images) + * `e2fsprogs` + * `genext2fs` (used by `mmdebstrap`) + * `mmdebstrap` + * `qemu-system-something` + * `qemu-user-static` (when creating foreign images) + +What is this image format precisely? +==================================== + +The image is a sparse ext4 file system image. It contains the root filesystem +of a (Debian) installation including an init system and a kernel. There is no +partition table or bootloader. The following paths are assumed inside: + * `/bin/true` is used to detect the architecture of an image + * `/vmlinuz` must be a symbolic link pointing to a regular file containing + the kernel. + * `/initrd.img` must be a symbolic link pointing to a regular file containing + the initrd image. + +License +======= + +The debvm tools are licensed under the MIT license. + +Contributors +============ + + * Helmut Grohne (main author) + * Johannes Schauer Marin Rodrigues (main author of `mmdebstrap`) + +[^1] This technically is a lie. It employs user namespaces and thus requires + the setuid binary `newuidmap` as well as a suitable subuid allocation. diff --git a/debvm-create b/debvm-create new file mode 100755 index 0000000..6728e90 --- /dev/null +++ b/debvm-create @@ -0,0 +1,158 @@ +#!/bin/sh +# Copyright 2022 Helmut Grohne <helmut@subdivi.de> +# SPDX-License-Identifier: MIT + +# shellcheck disable=SC2016 # Intentional quoting technique + +set -u + +ARCHITECTURE=$(dpkg --print-architecture) +IMAGE=rootfs.ext2 +INCLUDE_PACKAGES=init +MIRROR="http://deb.debian.org/debian" +SIZE=$((1024*1024*1024)) +SSHKEY= +SUITE=unstable +VMNAME=testvm + +die() { + echo "$*" 1>&2 + exit 1 +} + +usage() { + die "usage: $0 [-a architecture] [-h hostname] [-k sshkey] [-m mirror] [-o output] [-p packages] [-r release] [-s size_in_GB]" +} + + +while test "$#" -gt 0; do + case "$1" in + -a) + test "$#" -eq 1 && usage + ARCHITECTURE=$2 + shift 2 + ;; + -h) + test "$#" -eq 1 && usage + VMNAME=$2 + shift 2 + ;; + -k) + test "$#" -eq 1 && usage + SSHKEY=$2 + shift 2 + ;; + -m) + test "$#" -eq 1 && usage + MIRROR=$2 + shift 2 + ;; + -o) + test "$#" -eq 1 && usage + IMAGE=$2 + shift 2 + ;; + -p) + test "$#" -eq 1 && usage + INCLUDE_PACKAGES="$INCLUDE_PACKAGES,$2" + shift 2 + ;; + -r) + test "$#" -eq 1 && usage + SUITE=$2 + shift 2 + ;; + -s) + test "$#" -eq 1 && usage + SIZE=$(($2*1024*1024*1024)) + shift 2 + ;; + *) + usage + ;; + esac +done + +if test -n "$SSHKEY" && ! test -f "$SSHKEY"; then + die "error: ssh keyfile '$SSHKEY' not found" +fi + +KERNEL_SUFFIX= +if test "$ARCHITECTURE" = amd64; then + KERNEL_SUFFIX=-cloud +fi +if test "$SUITE" = jessie || test "$SUITE" = stretch; then + KERNEL_SUFFIX= +fi + +INCLUDE_PACKAGES="$INCLUDE_PACKAGES,linux-image$KERNEL_SUFFIX-$ARCHITECTURE" + +if test -n "$SSHKEY"; then + INCLUDE_PACKAGES="$INCLUDE_PACKAGES,openssh-server" +fi + +# construct mmdebstrap options as $@: +set -- \ + --verbose \ + --variant=apt \ + "--architecture=$ARCHITECTURE" \ + "--include=$INCLUDE_PACKAGES" + +# unless we set up a fstab, / will be read-only +case "$ARCHITECTURE" in + s390x) + DISKDEV=vda + ;; + *) + DISKDEV=sda + ;; +esac +set -- "$@" "--customize-hook=echo '/dev/$DISKDEV / ext4 defaults 0 1' >"'"$1/etc/fstab"' + +# set up a hostname +set -- "$@" \ + "--customize-hook=echo $VMNAME >"'"$1/etc/hostname"' \ + "--customize-hook=echo 127.0.0.1 localhost $VMNAME >"'"$1/etc/hosts"' + +# allow password-less root login +set -- "$@" '--customize-hook=sed -i -e "s/^root:\\*:/root::/" $1/etc/shadow' +#set -- "$@" '--customize-hook=chroot "$1" passwd --delete root' + +# dhcp on all network interfaces +set -- "$@" \ + '--customize-hook=systemctl --root="$1" enable systemd-networkd.service' \ + "--customize-hook=printf '"'[Match]\nName=en*\nName=eth*\n[Network]\nDHCP=yes\n'"'"' > "$1/etc/systemd/network/20-wired.network"' + +# add ssh key for root +if test -n "$SSHKEY"; then + set -- "$@" \ + '--customize-hook=mkdir -p "$1/root/.ssh"' \ + "--customize-hook=upload $SSHKEY /root/.ssh/authorized_keys" +fi + +set -- "$@" '--customize-hook=chroot "$1" apt update' + +case "$SUITE" in + jessie) + # Use obsolete and expired keys. + set -- "$@" '--keyring=/usr/share/keyrings/debian-archive-removed-keys.gpg' + set -- "$@" --aptopt='Apt::Key::gpgvcommand "/usr/libexec/mmdebstrap/gpgvnoexpkeysig"' + # chfn does not work, because libpam-runtime.postinst is late setting up /etc/pam.d/common-auth et al + set -- "$@" --extract-hook='chroot "$1" pam-auth-update --package --force' + ;; + buster) + # We need /var/lib/dpkg/available for dpkg --set-selections to work. + set -- "$@" '--customize-hook=cat "$1"/var/lib/apt/lists/*_Packages | chroot "$1" dpkg --update-avail' + ;; +esac + +# suite target mirror +set -- "$@" "$SUITE" "$IMAGE" "deb $MIRROR $SUITE main" + +set -ex + +mmdebstrap "$@" + +truncate -s "$SIZE" "$IMAGE" +/sbin/resize2fs "$IMAGE" +/sbin/tune2fs -i 0 -O extents,uninit_bg,dir_index,has_journal "$IMAGE" diff --git a/debvm-run b/debvm-run new file mode 100755 index 0000000..112bd40 --- /dev/null +++ b/debvm-run @@ -0,0 +1,124 @@ +#!/bin/sh +# Copyright 2022 Helmut Grohne <helmut@subdivi.de> +# SPDX-License-Identifier: MIT + +set -u + +IMAGE=rootfs.ext2 +SSHPORT= +GRAPHICAL= + +die() { + echo "$*" 1>&2 + exit 1 +} + +usage() { + die "usage: $0 [-g] [-i image] [-s sshport] [-- qemu options]" +} + +while test "$#" -gt 0; do + case "$1" in + -g) + GRAPHICAL=1 + shift + ;; + -i) + test "$#" -eq 1 && usage + IMAGE=$2 + shift 2 + ;; + -s) + test "$#" -eq 1 && usage + SSHPORT=$2 + shift 2 + ;; + --) + shift + break + ;; + *) + usage + ;; + esac +done + +test -f "$IMAGE" || die "image '$IMAGE' not found" + +KERNELNAME=$(/sbin/debugfs "$IMAGE" -R "stat vmlinuz" | sed 's/Fast link dest: "\(.*\)"/\1/;t;d') +INITRDNAME=$(/sbin/debugfs "$IMAGE" -R "stat initrd.img" | sed 's/Fast link dest: "\(.*\)"/\1/;t;d') + +cleanup() { + set +x + test -n "$KERNELTMP" && rm -f "$KERNELTMP" + test -n "$INITRDTMP" && rm -f "$INITRDTMP" +} + +trap cleanup EXIT INT TERM QUIT + +KERNELTMP=$(mktemp) +INITRDTMP=$(mktemp) + +ARCHITECTURE=$(dpkg --print-architecture) +VMARCH=$ARCHITECTURE +if command -v elf-arch >/dev/null 2>&1; then + /sbin/debugfs "$IMAGE" -R "cat /bin/true" > "$KERNELTMP" + VMARCH=$(elf-arch "$KERNELTMP") +fi +case "$VMARCH" in + arm64|s390x) + DISKDEV=vda + ;; + *) + DISKDEV=sda + ;; +esac + +KERNEL_CMDLINE=root=/dev/$DISKDEV +NETDEV="user,id=net0" + +set -- \ + -m 1G \ + -smp "$(nproc)" \ + -kernel "$KERNELTMP" \ + -initrd "$INITRDTMP" \ + -drive "media=disk,format=raw,discard=unmap,file=$IMAGE" \ + -device "virtio-net-pci,netdev=net0" \ + "$@" + +if test "$ARCHITECTURE" = "$VMARCH"; then + QEMU=kvm + set -- -enable-kvm -cpu host "$@" +else + case "$VMARCH" in + arm64) + QEMU=qemu-system-aarch64 + set -- -machine virt -cpu max "$@" + ;; + *) + QEMU="qemu-system-$VMARCH" + ;; + esac +fi +if test "$VMARCH" = arm64; then + set -- -nographic "$@" +elif test "$VMARCH" = s390x; then + set -- -M s390-ccw-virtio -nographic "$@" +elif test -z "$GRAPHICAL"; then + KERNEL_CMDLINE="$KERNEL_CMDLINE console=ttyS0" + set -- -nographic "$@" +fi +if test -n "$SSHPORT"; then + NETDEV="$NETDEV,hostfwd=tcp:127.0.0.1:$SSHPORT-:22" +fi +set -- \ + -append "$KERNEL_CMDLINE" \ + -netdev "$NETDEV" \ + "$@" + +set -ex + +/sbin/debugfs "$IMAGE" -R "cat $KERNELNAME" > "$KERNELTMP" +/sbin/debugfs "$IMAGE" -R "cat $INITRDNAME" > "$INITRDTMP" + +"$QEMU" "$@" |