summaryrefslogtreecommitdiff
path: root/bin/debefivm-run
diff options
context:
space:
mode:
authorHelmut Grohne <helmut@subdivi.de>2025-04-06 07:59:25 +0200
committerHelmut Grohne <helmut@subdivi.de>2025-04-06 17:44:08 +0200
commit6f2a356ca10ab97ed257097996593d707eca9bd7 (patch)
tree4ba99a0bfa2c9f5967b47c17ab4e819756a0f787 /bin/debefivm-run
parent1c6688cb9aeffdf2027003423d0ab792045952a8 (diff)
downloaddebvm-6f2a356ca10ab97ed257097996593d707eca9bd7.tar.gz
add a new family of wrappers for EFI based images
debefivm-create is based on mmdebstrap-autopkgtest-build-qemu, which is is co-authored with Johannes Schauer Marin Rodrigues. Also thanks to Jochen Sprickerhof for suggesting the --rootsize option for use in Debusine.
Diffstat (limited to 'bin/debefivm-run')
-rwxr-xr-xbin/debefivm-run401
1 files changed, 401 insertions, 0 deletions
diff --git a/bin/debefivm-run b/bin/debefivm-run
new file mode 100755
index 0000000..c93d7d2
--- /dev/null
+++ b/bin/debefivm-run
@@ -0,0 +1,401 @@
+#!/bin/sh
+# SPDX-FileCopyrightText: 2024-2025 Helmut Grohne <helmut@subdivi.de>
+# SPDX-License-Identifier: MIT
+
+: <<'POD2MAN'
+=head1 NAME
+
+debefivm-run - Run a virtual machine from an EFI-bootable raw disk image
+
+=head1 SYNOPSIS
+
+B<-debefivm-run> [B<-a>] I<architecture>] [B<-i> F<image>] [B<-s> I<sshport>] [B<--> I<qemu options>]
+
+=head1 DESCRIPTION
+
+B<debefivm-run> is essentially a thing wrapper around B<qemu> for running a virtual machine from an UEFI bootable raw disk image.
+Such an image may be created using B<debefivm-create> or with another image creator, but its use is limited to architectures supporting EFI booting.
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<-a> I<architecture>, B<--architecture>=I<architecture>
+
+Override the Debian architecture of the provided image.
+If the image uses architecture-specific type UUIDs for the root partition, the architecture can be detected.
+Otherwise, the host's architecture is assumed.
+The images created by B<debefivm-create> employ these UUIDs.
+The value is used to determine the correct emulator binary as well as suitable EFI firmware.
+
+=item B<--efi-vars>=F<variablefile>
+
+EFI variables can be changed and retained across reboots of a virtual machine if a separate variable file is supplied.
+The passed file is created from a template if absent.
+If absent, a read-only variable store will be supplied to the virtual machine.
+
+=item B<-i> F<image>, B<--image>=F<image>
+
+This option specifies the location of the virtual machine image file.
+By default F<vm.img> in the working directory is used.
+
+=item B<--netopt>=I<option>
+
+B<debefivm-run> sets up a user mode network by default.
+It therefore passes a B<-netdev> option to B<qemu>.
+Using this option, you can customize the value of that B<-netdev> option.
+For instance, you can set up additional port forwards by passing e.g. C<--netopt hostfwd=:127.0.0.1:8080-:80>.
+It can be used multiple times.
+
+=item B<--skip>=I<task>
+
+Skip a particular task or feature.
+The option may be specified multiple times or list multiple tasks to be skipped by separating them with a comma.
+By default, no tasks are skipped.
+The following tasks may be skipped.
+
+=over 4
+
+=item B<network>
+
+Do not configure a network card.
+Use this if you want to configure network on your own.
+This should also be passed in addition to passing C<-nic none> when you want to disable networking.
+
+=item B<rngdev>
+
+Do not pass a random number generator device.
+
+=back
+
+=item B<--transport>=I<transport>
+
+When B<debefivm-run> adds devices to B<qemu>, it has to select a transport and it most often guesses B<pci>.
+When specifying a different machine such as B<-machine microvm>, a different transport such as B<device> may be needed.
+
+=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.
+You can connect to your virtual machine without updating your known hosts like this:
+
+ ssh -o NoHostAuthenticationForLocalhost=yes -p $sshport root@127.0.0.1
+
+The option is a shorthand for C<--netopt hostfwd=tcp:127.0.0.1:sshport-:22>.
+
+=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 SEE ALSO
+
+ debefivm-create(1) debvm-run(1) qemu(1)
+
+=cut
+POD2MAN
+
+set -u
+
+EFI_VARS=
+GRAPHICAL=
+IMAGE=vm.img
+IMAGE_ARCH=
+NATIVE_ARCH=$(dpkg --print-architecture)
+NETOPTS=
+SKIP=,
+SSHPORT=
+TRANSPORT=
+
+nth_arg() {
+ shift "$1"
+ printf "%s" "$1"
+}
+
+die() {
+ echo "error: $*" >&2
+ exit 1
+}
+usage() {
+ die "usage: $0 [-a architecture] [-g] [-i image] [-s sshport] [-- qemu options]"
+}
+usage_error() {
+ echo "error: $*" 1>&2
+ usage
+}
+
+opt_architecture() {
+ IMAGE_ARCH=$1
+}
+opt_efi_vars() {
+ EFI_VARS=$1
+}
+opt_graphical() {
+ GRAPHICAL=1
+}
+opt_help() {
+ usage
+}
+opt_image() {
+ IMAGE=$1
+}
+opt_netopt() {
+ NETOPTS="$NETOPTS,$1"
+}
+opt_skip() {
+ SKIP="$SKIP$1,"
+}
+opt_sshport() {
+ SSHPORT=$1
+}
+opt_transport() {
+ TRANSPORT=$1
+}
+
+while getopts :a:gi:s:-: OPTCHAR; do
+ case "$OPTCHAR" in
+ a) opt_architecture "$OPTARG" ;;
+ g) opt_graphical ;;
+ i) opt_image "$OPTARG" ;;
+ s) opt_sshport "$OPTARG" ;;
+ -)
+ case "$OPTARG" in
+ graphical|help)
+ "opt_$OPTARG"
+ ;;
+ architecture|efi-vars|image|netopt|skip|sshport|transport)
+ test "$OPTIND" -gt "$#" && usage_error "missing argument for --$OPTARG"
+ "opt_$OPTARG" "$(nth_arg "$OPTIND" "$@")"
+ OPTIND=$((OPTIND+1))
+ ;;
+ architecture=*|efi-vars=*|image=*|netopt=*|skip=*|sshport=*|transport=*)
+ "opt_${OPTARG%%=*}" "${OPTARG#*=}"
+ ;;
+ *)
+ usage_error "unrecognized option --$OPTARG"
+ ;;
+ esac
+ ;;
+ :)
+ usage_error "missing argument for -$OPTARG"
+ ;;
+ '?')
+ usage_error "unrecognized option -$OPTARG"
+ ;;
+ *)
+ die "internal error while parsing command options, please report a bug"
+ ;;
+ esac
+done
+shift "$((OPTIND - 1))"
+
+if test -n "$SSHPORT"; then
+ opt_netopt "hostfwd=tcp:127.0.0.1:$SSHPORT-:22"
+fi
+
+test -f "$IMAGE" || die "image '$IMAGE' not found"
+test -s "$IMAGE" || die "image '$IMAGE' is empty"
+
+check_skip() {
+ case "$SKIP" in
+ *",$1,"*) return 0 ;;
+ *) return 1 ;;
+ esac
+}
+
+if test -z "$IMAGE_ARCH"; then
+ PARTITIONS=$(partx --show --noheadings --output type "$IMAGE")
+
+ case "$PARTITIONS" in
+ *4f68bce3-e8cd-4db1-96e7-fbcaf984b709*)
+ IMAGE_ARCH=amd64
+ ;;
+ *b921b045-1df0-41c3-af44-4c6f280d3fae*)
+ IMAGE_ARCH=arm64
+ ;;
+ *72ec70a6-cf74-40e6-bd49-4bda08e8f224*)
+ IMAGE_ARCH=riscv64
+ ;;
+ *69dad710-2ce4-4e3c-b16c-21a1d49abed3*)
+ IMAGE_ARCH=armhf
+ ;;
+ *44479540-f297-41b2-9af7-d131d5f0458a*)
+ IMAGE_ARCH=i386
+ ;;
+ *)
+ echo "cannot detect image architecture from gpt, assuming $NATIVE_ARCH" >&2
+ IMAGE_ARCH="$NATIVE_ARCH"
+ ;;
+ esac
+fi
+
+# Translate IMAGE_ARCH (a Debian architecture) to a Debian CPU name.
+# This utilizes the QEMU Debian package symlink mapping that ensures that
+# calling qemu-system-${DEB_HOST_ARCH_CPU} will run the QEMU binary providing
+# the correct emulator for that CPU.
+IMAGEARCHCPU="$(dpkg-architecture --force --host-arch "$IMAGE_ARCH" --query DEB_HOST_ARCH_CPU)"
+QEMU="qemu-system-$IMAGEARCHCPU"
+CPU=
+MACHINE=
+MAX_SMP=
+
+case "$IMAGEARCHCPU" in
+ amd64)
+ QEMU=qemu-system-x86_64
+ MACHINE="type=q35"
+ BIOSCODE='/usr/share/OVMF/OVMF_CODE_4M.fd'
+ BIOSDATA='/usr/share/OVMF/OVMF_VARS_4M.fd'
+ BIOSPACKAGE=ovmf
+ ;;
+ arm)
+ CPU=max
+ MACHINE="type=virt,highmem=off"
+ MAX_SMP=8
+ BIOSCODE='/usr/share/AAVMF/AAVMF32_CODE.fd'
+ BIOSDATA='/usr/share/AAVMF/AAVMF32_VARS.fd'
+ BIOSPACKAGE=qemu-efi-arm
+ ;;
+ arm64)
+ CPU=max,pauth-impdef=on
+ MACHINE="type=virt,gic-version=max"
+ BIOSCODE='/usr/share/AAVMF/AAVMF_CODE.fd'
+ BIOSDATA='/usr/share/AAVMF/AAVMF_VARS.fd'
+ BIOSPACKAGE=qemu-efi-aarch64
+ ;;
+ i386)
+ BIOSCODE='/usr/share/OVMF/OVMF32_CODE_4M.secboot.fd'
+ BIOSDATA='/usr/share/OVMF/OVMF32_VARS_4M.fd'
+ BIOSPACKAGE=ovfm-ia32
+ ;;
+ riscv64)
+ MACHINE="type=virt"
+ BIOSCODE='/usr/share/qemu-efi-riscv64/RISCV_VIRT_CODE.fd'
+ BIOSDATA='/usr/share/qemu-efi-riscv64/RISCV_VIRT_VARS.fd'
+ BIOSPACKAGE=qemu-efi-riscv64
+ ;;
+ *)
+ die "support for $IMAGE_ARCH is not implemented"
+ ;;
+esac
+
+test -e "$BIOSCODE" ||
+ die "cannot file firmware file $BIOSCODE. Is $BIOSPACKAGE installed?"
+test -e "$BIOSDATA" ||
+ die "cannot file firmware file $BIOSDATA. Is $BIOSPACKAGE installed?"
+
+# Assign the default late to allow both cli and arch-specific overrides.
+: "${TRANSPORT:=pci}"
+
+comma_escape() {
+ # If a filename contains a comma, then that comma must be escaped by
+ # prefixing it with another comma or otherwise output filenames are
+ # able to inject options to qemu (and load the wrong file).
+ comma_escape_str="$1"
+ while test "${comma_escape_str%,*}" != "$comma_escape_str"; do
+ printf "%s,," "${comma_escape_str%%,*}"
+ comma_escape_str="${comma_escape_str#*,}"
+ done
+ printf "%s" "$comma_escape_str"
+}
+
+EFI_VAR_DRIVE="if=pflash,format=raw,unit=1"
+if test -n "$EFI_VARS"; then
+ if ! test -e "$EFI_VARS"; then
+ cp "$BIOSDATA" "$EFI_VARS"
+ fi
+ EFI_VAR_DRIVE="$EFI_VAR_DRIVE,read-only=off,file=$(comma_escape "$EFI_VARS")"
+else
+ EFI_VAR_DRIVE="$EFI_VAR_DRIVE,read-only=on,file=$(comma_escape "$BIOSDATA")"
+fi
+
+ENABLE_KVM=no
+if test "$NATIVE_ARCH" = "$IMAGE_ARCH"; then
+ ENABLE_KVM=yes
+fi
+if test "$ENABLE_KVM" = yes; then
+ if ! command -v "$QEMU" >/dev/null 2>&1; then
+ # Fall back to kvm in case we badly guessed qemu.
+ QEMU=kvm
+ fi
+ MACHINE="${MACHINE:+$MACHINE,}accel=kvm:tcg"
+ # While kvm will fall back gracefully, only override CPU when we expect
+ # kvm to work.
+ if test -w /dev/kvm; then
+ CPU=max
+ # kvm: "max" will become "host", intended.
+ # tcg: "max" will actually work, "host" would not.
+ fi
+fi
+
+if test -n "$MACHINE"; then
+ set -- -machine "$MACHINE" "$@"
+fi
+
+set -- \
+ -no-user-config \
+ -nodefaults \
+ -chardev stdio,id=console,mux=on,signal=off \
+ -serial chardev:console \
+ -name "debefivm-run $IMAGE" \
+ -m 1G \
+ -drive "if=pflash,format=raw,unit=0,read-only=on,file=$(comma_escape "$BIOSCODE")" \
+ -drive "$EFI_VAR_DRIVE" \
+ -drive "id=root,media=disk,format=raw,discard=unmap,file=$(comma_escape "$IMAGE"),if=none,cache=unsafe" \
+ -device "virtio-blk-$TRANSPORT,drive=root,serial=root" \
+ "$@"
+
+if test -n "$CPU"; then
+ set -- -cpu "$CPU" "$@"
+fi
+
+NPROC=$(nproc)
+if test "$NPROC" -gt 1; then
+ test -n "$MAX_SMP" && test "$NPROC" -gt "$MAX_SMP" && NPROC=$MAX_SMP
+ set -- -smp "$NPROC" "$@"
+fi
+if ! check_skip rngdev; then
+ set -- \
+ -device "virtio-rng-$TRANSPORT,rng=rng0" \
+ -object rng-random,filename=/dev/urandom,id=rng0 \
+ "$@"
+fi
+
+if test -z "$GRAPHICAL"; then
+ set -- -nographic "$@"
+else
+ case "$KERNELARCH" in
+ amd64|i386)
+ set -- -vga virtio "$@"
+ ;;
+ *)
+ set -- \
+ -device "virtio-gpu-gl-$TRANSPORT" \
+ -display gtk,gl=on \
+ "$@"
+ ;;
+ esac
+ set -- \
+ -device "virtio-keyboard-$TRANSPORT" \
+ -device "virtio-tablet-$TRANSPORT" \
+ "$@"
+fi
+
+DNSSEARCH=$(dnsdomainname)
+if test -z "$DNSSEARCH"; then
+ DNSSEARCH=$(sed -n 's/^\s*search\s*\.\?//p;T;q' /etc/resolv.conf)
+fi
+if test -n "$DNSSEARCH"; then
+ NETOPTS=",domainname=$DNSSEARCH$NETOPTS"
+fi
+
+if ! check_skip network; then
+ set -- \
+ -netdev "user,id=net0$NETOPTS" \
+ -device "virtio-net-$TRANSPORT,netdev=net0" \
+ "$@"
+fi
+
+echo "+ $QEMU $*" 1>&2
+exec "$QEMU" "$@"