summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHelmut Grohne <helmut@subdivi.de>2022-12-31 07:44:49 +0100
committerHelmut Grohne <helmut@subdivi.de>2022-12-31 07:44:49 +0100
commita7e264672d431fc3a36b47c48ab016b20b03071b (patch)
tree8e6ad944ea49d89948902fdb2557eeb9890b3af6
parentc856e06999fdc2b992e07c1b82b4af08c834f78b (diff)
parent947506522176e2b966fa1dc6389fa366322dec12 (diff)
downloaddebvm-a7e264672d431fc3a36b47c48ab016b20b03071b.tar.gz
Merge branch main into debian
-rw-r--r--.gitlab-ci.yml66
-rwxr-xr-x9pmounthook/customize.sh42
-rw-r--r--README.md4
-rwxr-xr-xdebvm-create220
-rwxr-xr-xdebvm-run130
-rwxr-xr-xtests/create-and-run.sh36
6 files changed, 370 insertions, 128 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b2ce36c..6427330 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,7 +1,6 @@
image: debian:sid-slim
shellcheck:
- stage: test
script:
- apt-get update
- apt-get dist-upgrade --yes
@@ -9,71 +8,42 @@ shellcheck:
- shellcheck debvm-*
codespell:
- stage: test
script:
- apt-get update
- apt-get dist-upgrade --yes
- apt-get --no-install-recommends --yes install codespell
- codespell debvm-*
-.test_script:
- script:
- - timeout 240s ./debvm-run -s 2222 &
- - |
- timeout=5
- sshopt="-o StrictHostKeyChecking=no $(if test "$RELEASE" = jessie; then echo -o PubkeyAcceptedKeyTypes=+ssh-rsa; fi)"
- ts=$(sleepenh 0 || [ $? -eq 1 ])
- for i in $(seq 30); do
- rv=0
- ssh $sshopt -o ConnectTimeout="$timeout" -i ~/.ssh/id -p 2222 root@localhost echo success || rv=$?
- test $rv -eq 0 && break
- ts=$(sleepenh "$ts" "$timeout" || [ $? -eq 1 ]);
- if test $i -eq 30; then
- echo "timeout reached" >&2
- exit 1
- fi
- done
- - ssh $sshopt -i ~/.ssh/id -p 2222 root@localhost poweroff
-
release_test:
- extends: .test_script
- stage: test
parallel:
matrix:
- RELEASE:
- - sid
- - bookworm
- - bullseye
- - buster
- - stretch
- - jessie
- before_script:
+ - sid
+ - bookworm
+ - bullseye
+ - buster
+ - stretch
+ - jessie
+ script:
- apt-get update
- apt-get dist-upgrade --yes
- apt-get --no-install-recommends --yes install e2fsprogs genext2fs mmdebstrap openssh-client sleepenh qemu-kvm
- - ssh-keygen -f ~/.ssh/id -N ''
- - ./debvm-create -k ~/.ssh/id.pub -r "$RELEASE"
+ - PATH=.:$PATH ./tests/create-and-run.sh $(dpkg --print-architecture) "$RELEASE"
arch_test:
- extends: .test_script
- stage: test
parallel:
matrix:
- ARCHITECTURE:
- - arm64
- - armhf
- - i386
- - mips64el
- - mipsel
- - ppc64el
- - s390x
- before_script:
- - |
- if [ ! -e /proc/sys/fs/binfmt_misc/status ]; then
- mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc
- fi
+ - arm64
+ - armhf
+ - i386
+ - mips64el
+ - mipsel
+ - ppc64el
+ - s390x
+ script:
+ - test -e /proc/sys/fs/binfmt_misc/status || mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc
- apt-get update
- apt-get dist-upgrade --yes
- apt-get --no-install-recommends --yes install e2fsprogs genext2fs mmdebstrap openssh-client sleepenh qemu-system binfmt-support arch-test qemu-user-static
- - ssh-keygen -f ~/.ssh/id -N ''
- - ./debvm-create -k ~/.ssh/id.pub -a $ARCHITECTURE
+ - PATH=.:$PATH ./tests/create-and-run.sh "$ARCHITECTURE" sid
diff --git a/9pmounthook/customize.sh b/9pmounthook/customize.sh
new file mode 100755
index 0000000..67c4240
--- /dev/null
+++ b/9pmounthook/customize.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+# Copyright 2022 Helmut Grohne <helmut@subdivi.de>
+# SPDX-License-Identifier: MIT
+#
+# This is a mmdebstrap customize hook that adds a systemd-generator that causes
+# 9p filesystems to be automatically mounted to /media/$SOMETAG during boot.
+# You can enable it by passing the containing directory to --hook-dir.
+# In order to add a 9p filesystem to your VM, pass
+# -virtfs local,security_model=none,mount_tag=$SOMETAG,path=$SOMEDIR
+# Note that the linux-image-cloud-* does not include a 9p driver.
+
+set -eu
+GENERATOR_PATH="$1/etc/systemd/system-generators/9p-generator"
+mkdir -p "${GENERATOR_PATH%/*}"
+cat >"$GENERATOR_PATH" << 'ENDOFGENERATOR'
+#!/bin/sh
+
+UNITDIR=$1
+
+modprobe 9pnet_virtio || exit 0
+
+for tagfile in /sys/bus/virtio/devices/*/mount_tag; do
+ tag=$(cat "$tagfile") || continue
+ test -z "$tag" && continue
+ mountpoint="/media/$tag"
+ mkdir -p "$mountpoint"
+ unitname="$(systemd-escape -p "$mountpoint").mount"
+ cat > "$UNITDIR/$unitname" <<ENDOFUNIT
+[Unit]
+Description=9p mount for tag $tag
+
+[Mount]
+What=$tag
+Where=$mountpoint
+Type=9p
+Options=trans=virtio
+ENDOFUNIT
+ mkdir -p "$UNITDIR/remote-fs.target.wants"
+ ln -s "../$unitname" "$UNITDIR/remote-fs.target.wants/$unitname"
+done
+ENDOFGENERATOR
+chmod 755 "$GENERATOR_PATH"
diff --git a/README.md b/README.md
index fd1c8cc..8109b2a 100644
--- a/README.md
+++ b/README.md
@@ -45,8 +45,8 @@ 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.
+ * `/vmlinuz` or `/vmlinux` (depending on the architecture) 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.
diff --git a/debvm-create b/debvm-create
index be24358..e8bcf92 100755
--- a/debvm-create
+++ b/debvm-create
@@ -4,10 +4,91 @@
# shellcheck disable=SC2016 # Intentional quoting technique
+: <<'POD2MAN'
+=head1 NAME
+
+debvm-create - Create a VM image for various Debian releases and architectures
+
+=head1 SYNOPSIS
+
+B<debvm-create> [B<-a> I<architecture>] [B<-h> I<hostname>] [B<-k> I<sshkey>] [B<-m> I<mirror>] [B<-o> I<output>] [B<-p> I<package>] [B<-r> I<release>] [B<-z> I<size_in_GB>] [B<--> I<mmdebstrap options>]
+
+=head1 DESCRIPTION
+
+B<debvm-create> is essentially a thin wrapper around B<mmdebstrap> for creating a raw ext4 filesystem image for booting with B<debvm-run>.
+The purpose of these images primarily is testing the different releases and architectures without access to a physical machine of that architecture.
+Beyond essential packages, the image will contain B<apt>, an init system and a suitable kernel package.
+Notably absent is a bootloader and a partition table.
+In order to boot such an image, one is supposed to extract the kernel and initrd from the image and pass it to a suitable bootloader.
+No user account is created and root can login without specifying a password.
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<-a> I<architecture>, B<--architecture>=I<architecture>
+
+Specify a Debian architecture name.
+By default, the native architecture is being used.
+A suitable kernel image is automatically selected and installed into the image.
+
+=item B<-h> I<hostname>, B<--hostname>=I<hostname>
+
+Set the hostname of the virtual machine.
+By default, the hostname is B<testvm>.
+
+=item B<-k> I<sshkey>, B<--sshkey>=I<sshkey>
+
+Install the given ssh public key file into the virtual machine image for the root user.
+This option also causes the ssh server to be installed.
+By default, no key or server is installed.
+
+=item B<-m> I<mirror>, B<--mirror>=I<mirror>
+
+Specify the Debian mirror to be used for downloading packages and to be configured inside the virtual machine image.
+By default, B<http://deb.debian.org/debian> is being used.
+
+=item B<-o> I<output>, B<--output>=I<output>
+
+Specify the file name of the resulting virtual machine image.
+By default, it is written to B<rootfs.ext4>.
+
+=item B<-p> I<package>, B<--package>=I<package>
+
+Request additional packages to be installed into the virtual machine image.
+This option can be specified multiple times and packages can be separated by a comma.
+Package recommendations are not honoured.
+If a linux-image is passed here, it will replace the one selected by default.
+
+=item B<-r> I<release>, B<--release>=I<release>
+
+Use the given Debian release.
+By default, B<unstable> is being used.
+
+=item B<-z> I<size_in_GB>, B<--size>=I<size_in_GB>
+
+Specify the minimum image size in giga bytes.
+The resulting image will be grown as a sparse file to this size if necessary.
+The default is 1 GB.
+
+=item B<--> I<mmdebstrap options>
+
+All options beyond a double dash are passed to B<mmdebstrap> before the suite, target and mirror specification.
+This can be used to provide additional hooks for image customization.
+
+=back
+
+=head1 SEE ALSO
+
+ debvm-run(1) mmdebstrap(1)
+
+=cut
+POD2MAN
+
set -u
ARCHITECTURE=$(dpkg --print-architecture)
-IMAGE=rootfs.ext2
+IMAGE=rootfs.ext4
INCLUDE_PACKAGES=init
MIRROR="http://deb.debian.org/debian"
SIZE=$((1024*1024*1024))
@@ -15,67 +96,88 @@ SSHKEY=
SUITE=unstable
VMNAME=testvm
+nth_arg() {
+ shift "$1"
+ printf "%s" "$1"
+}
+
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] [-- mmdebstrap options]"
+ die "usage: $0 [-a architecture] [-h hostname] [-k sshkey] [-m mirror] [-o output] [-p packages] [-r release] [-z size_in_GB] [-- mmdebstrap options]"
+}
+usage_error() {
+ echo "error: $*" 1>&2
+ usage
}
+opt_architecture() {
+ ARCHITECTURE=$1
+}
+opt_hostname() {
+ VMNAME=$1
+}
+opt_mirror() {
+ MIRROR=$1
+}
+opt_sshkey() {
+ SSHKEY=$1
+}
+opt_output() {
+ IMAGE=$1
+}
+opt_package() {
+ INCLUDE_PACKAGES="$INCLUDE_PACKAGES,$1"
+}
+opt_release() {
+ SUITE=$1
+}
+opt_size() {
+ SIZE=$(($1*1024*1024*1024))
+}
-while test "$#" -gt 0; do
- case "$1" in
- -a)
- test "$#" -eq 1 && usage
- ARCHITECTURE=$2
- shift 2
+while getopts :a:h:k:m:o:p:r:z:-: OPTCHAR; do
+ case "$OPTCHAR" in
+ a) opt_architecture "$OPTARG" ;;
+ h) opt_hostname "$OPTARG" ;;
+ k) opt_sshkey "$OPTARG" ;;
+ m) opt_mirror "$OPTARG" ;;
+ o) opt_output "$OPTARG" ;;
+ p) opt_package "$OPTARG" ;;
+ r) opt_release "$OPTARG" ;;
+ z) opt_size "$OPTARG" ;;
+ -)
+ case "$OPTARG" in
+ help)
+ usage
+ ;;
+ architecture|hostname|mirror|output|package|release|size|sshkey)
+ test "$OPTIND" -gt "$#" && usage_error "missing argument for --$OPTARG"
+ "opt_$OPTARG" "$(nth_arg "$OPTIND" "$@")"
+ OPTIND=$((OPTIND+1))
+ ;;
+ architecture=*|hostname=*|mirror=*|output=*|package=*|release=*|size=*|sshkey=*)
+ "opt_${OPTARG%%=*}" "${OPTARG#*=}"
+ ;;
+ *)
+ usage_error "unrecognized option --$OPTARG"
+ ;;
+ esac
;;
- -h)
- test "$#" -eq 1 && usage
- VMNAME=$2
- shift 2
+ :)
+ usage_error "missing argument for -$OPTARG"
;;
- -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
- ;;
- --)
- shift
- break
+ '?')
+ usage_error "unrecognized option -$OPTARG"
;;
*)
- usage
+ die "internal error while parsing command options, please report a bug"
;;
esac
done
+shift "$((OPTIND - 1))"
if test -n "$SSHKEY" && ! test -f "$SSHKEY"; then
die "error: ssh keyfile '$SSHKEY' not found"
@@ -111,8 +213,6 @@ case "$SUITE" in
;;
esac
-
-
KERNEL_SUFFIX=-$ARCHITECTURE
case "$ARCHITECTURE" in
amd64|arm64)
@@ -138,7 +238,13 @@ case "$ARCHITECTURE" in
;;
esac
-INCLUDE_PACKAGES="$INCLUDE_PACKAGES,linux-image$KERNEL_SUFFIX"
+case ",$INCLUDE_PACKAGES," in
+ *,linux-image-*)
+ ;;
+ *)
+ INCLUDE_PACKAGES="$INCLUDE_PACKAGES,linux-image$KERNEL_SUFFIX"
+ ;;
+esac
if test -n "$SSHKEY"; then
INCLUDE_PACKAGES="$INCLUDE_PACKAGES,openssh-server"
@@ -196,7 +302,7 @@ if test -n "$SSHKEY"; then
"$@"
fi
-set -- --skip=cleanup/apt "$@"
+set -- --skip=cleanup/apt/lists "$@"
# Make dpkg --set-selections to work.
set -- '--customize-hook=chroot "$1" apt-cache dumpavail | chroot "$1" dpkg --update-avail' "$@"
@@ -205,8 +311,7 @@ if test "$DEBVER" -le 8; then
# 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, see #1026765
- set -- --extract-hook='chroot "$1" pam-auth-update --package --force' "$@"
+ set -- --hook-dir=/usr/share/mmdebstrap/hooks/jessie-or-older "$@"
fi
if test "$DEBVER" -ge 12; then
@@ -221,8 +326,11 @@ set -ex
mmdebstrap "$@"
-truncate -s "$SIZE" "$IMAGE"
-/sbin/resize2fs "$IMAGE"
+IMAGESIZE=$(stat -c %s "$IMAGE")
+if test "$IMAGESIZE" -lt "$SIZE"; then
+ truncate -s "$SIZE" "$IMAGE"
+ /sbin/resize2fs "$IMAGE"
+fi
/sbin/tune2fs -L debvm -i 0 -O extents,uninit_bg,dir_index,has_journal "$IMAGE"
# Must fsck after tune2fs: https://ext4.wiki.kernel.org/index.php/UpgradeToExt4
-/sbin/fsck.ext4 -fDp rootfs.ext2
+/sbin/fsck.ext4 -fDp "$IMAGE"
diff --git a/debvm-run b/debvm-run
index edb01ad..d6a5422 100755
--- a/debvm-run
+++ b/debvm-run
@@ -2,46 +2,132 @@
# Copyright 2022 Helmut Grohne <helmut@subdivi.de>
# SPDX-License-Identifier: MIT
+: <<'POD2MAN'
+=head1 NAME
+
+debvm-run - Run a VM image created by debvm-create
+
+=head1 SYNOPSIS
+
+B<debvm-run> [B<-g>] [B<-i> I<image>] [B<-s> I<sshport>] [B<--> I<qemu options>]
+
+=head1 DESCRIPTION
+
+B<debvm-run> is essentially a thin wrapper around B<qemu> for running a virtual machine image created by B<debvm-create> or something compatible.
+The virtual machine image is expected to be a raw ext4 image with file system label B<debvm>.
+The architecture of the machine is detected from the contained B</bin/true>.
+It must contain a symbolic link pointing to a kernel image at B</vmlinuz> or B</vmlinux> depending on the architecture and a symbolic link pointing to an initrd image at B</initrd.img>.
+Both are extracted and passed to B<qemu>.
+A net interface configured for user mode is added automatically.
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<-g>, B<--graphical>
+
+By default, the option B<-nographic> is passed to B<qemu> and one interacts with the serial console of the machine.
+This configuration is skipped in the presence of this option.
+
+=item B<-i> I<image>, B<--image>=I<image>
+
+This option specifies the location of the virtual machine image file.
+By default B<rootfs.ext4> in the working directory is used.
+
+=item B<-s> I<sshport>, B<--sshport>=I<sshport>
+
+If given, B<qemu> is configured to pass connections to I<127.0.0.1:sshport> to port 22 of the virtual machine.
+
+=item B<--> I<qemu options>
+
+All options beyond a double dash are passed to B<qemu>.
+This can be used to configure additional hardware components.
+One possible use of this method is passing B<-snapshot> to avoid modifying the virtual machine image.
+
+=back
+
+=head1 LIMITATIONS
+
+Due to the way kernel and bootloader are being extracted before running B<qemu>, one cannot upgrade a kernel and then just reboot.
+Attempting to do so, will still use the old kernel.
+Instead, B<qemu> must be terminated and B<debvm-run> should be launched again to pick up the new kernel.
+In order to avoid accidental reboots, one may pass B<-no-reboot> to B<qemu>.
+
+=head1 SEE ALSO
+
+ debvm-create(1) qemu(1)
+
+=cut
+POD2MAN
+
set -u
-IMAGE=rootfs.ext2
+IMAGE=rootfs.ext4
SSHPORT=
GRAPHICAL=
+nth_arg() {
+ shift "$1"
+ printf "%s" "$1"
+}
+
die() {
echo "$*" 1>&2
exit 1
}
-
usage() {
die "usage: $0 [-g] [-i image] [-s sshport] [-- qemu options]"
}
+usage_error() {
+ echo "error: $*" 1>&2
+ usage
+}
-while test "$#" -gt 0; do
- case "$1" in
- -g)
- GRAPHICAL=1
- shift
- ;;
- -i)
- test "$#" -eq 1 && usage
- IMAGE=$2
- shift 2
+opt_graphical() {
+ GRAPHICAL=1
+}
+opt_image() {
+ IMAGE=$1
+}
+opt_sshport() {
+ SSHPORT=$1
+}
+
+while getopts :gi:s:-: OPTCHAR; do
+ case "$OPTCHAR" in
+ g) opt_graphical ;;
+ i) opt_image "$OPTARG" ;;
+ s) opt_sshport "$OPTARG" ;;
+ -)
+ case "$OPTARG" in
+ help)
+ usage
+ ;;
+ graphical|image|sshport)
+ test "$OPTIND" -gt "$#" && usage_error "missing argument for --$OPTARG"
+ "opt_$OPTARG" "$(nth_arg "$OPTIND" "$@")"
+ OPTIND=$((OPTIND+1))
+ ;;
+ image=*|sshport=*)
+ "opt_${OPTARG%%=*}" "${OPTARG#*=}"
+ ;;
+ *)
+ usage_error "unrecognized option --$OPTARG"
+ ;;
+ esac
;;
- -s)
- test "$#" -eq 1 && usage
- SSHPORT=$2
- shift 2
+ :)
+ usage_error "missing argument for -$OPTARG"
;;
- --)
- shift
- break
+ '?')
+ usage_erro "unrecognized option -$OPTARG"
;;
*)
- usage
+ die "internal error while parsing command options, please report a bug"
;;
esac
done
+shift "$((OPTIND - 1))"
test -f "$IMAGE" || die "image '$IMAGE' not found"
test -s "$IMAGE" || die "image '$IMAGE' is empty"
@@ -140,9 +226,9 @@ else
;;
esac
fi
-if test "$MAX_SMP" -gt 1; then
+if test -z "$MAX_SMP" || test "$MAX_SMP" -gt 1; then
NPROC=$(nproc)
- test "$NPROC" -gt "$MAX_SMP" && NPROC=$MAX_SMP
+ test -n "$MAX_SMP" && test "$NPROC" -gt "$MAX_SMP" && NPROC=$MAX_SMP
set -- -smp "$NPROC" "$@"
fi
diff --git a/tests/create-and-run.sh b/tests/create-and-run.sh
new file mode 100755
index 0000000..aafd41d
--- /dev/null
+++ b/tests/create-and-run.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+# shellcheck disable=SC2086
+
+if test "$#" -ne 2; then
+ echo "$(basename $0) takes two positional arguments. architecture and release" 1>&2
+ exit 1
+fi
+
+set -ex
+
+cleanup() {
+ rm -f ssh_id ssh_id.pub test.ext4
+}
+
+trap cleanup EXIT INT TERM QUIT
+
+ssh-keygen -f ssh_id -N ''
+debvm-create -k ssh_id.pub -o test.ext4 -a "$1" -r "$2"
+
+timeout 240s debvm-run -s 2222 -i test.ext4 &
+timeout=5
+sshopt="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $(if test "$2" = jessie; then echo -o PubkeyAcceptedKeyTypes=+ssh-rsa; fi)"
+ts=$(sleepenh 0 || [ $? -eq 1 ])
+for i in $(seq 30); do
+ rv=0
+ ssh $sshopt -o ConnectTimeout="$timeout" -i ssh_id -p 2222 root@localhost echo success || rv=$?
+ test $rv -eq 0 && break
+ ts=$(sleepenh "$ts" "$timeout" || [ $? -eq 1 ]);
+ if test "$i" -eq 30; then
+ echo "timeout reached" >&2
+ exit 1
+ fi
+done
+ssh $sshopt -i ssh_id -p 2222 root@localhost poweroff
+wait