diff options
author | Helmut Grohne <helmut@subdivi.de> | 2022-12-31 07:44:49 +0100 |
---|---|---|
committer | Helmut Grohne <helmut@subdivi.de> | 2022-12-31 07:44:49 +0100 |
commit | a7e264672d431fc3a36b47c48ab016b20b03071b (patch) | |
tree | 8e6ad944ea49d89948902fdb2557eeb9890b3af6 | |
parent | c856e06999fdc2b992e07c1b82b4af08c834f78b (diff) | |
parent | 947506522176e2b966fa1dc6389fa366322dec12 (diff) | |
download | debvm-a7e264672d431fc3a36b47c48ab016b20b03071b.tar.gz |
Merge branch main into debian
-rw-r--r-- | .gitlab-ci.yml | 66 | ||||
-rwxr-xr-x | 9pmounthook/customize.sh | 42 | ||||
-rw-r--r-- | README.md | 4 | ||||
-rwxr-xr-x | debvm-create | 220 | ||||
-rwxr-xr-x | debvm-run | 130 | ||||
-rwxr-xr-x | tests/create-and-run.sh | 36 |
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" @@ -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" @@ -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 |