summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHelmut Grohne <helmut@subdivi.de>2022-12-20 14:29:17 +0100
committerHelmut Grohne <helmut@subdivi.de>2022-12-20 14:29:17 +0100
commite3258ab831172b3760577507b0437365f343cc5e (patch)
tree9a007fca948beb324dc4ea5acd10382665d73586
downloaddebvm-e3258ab831172b3760577507b0437365f343cc5e.tar.gz
initial checkin
-rw-r--r--README.md59
-rwxr-xr-xdebvm-create158
-rwxr-xr-xdebvm-run124
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" "$@"