summaryrefslogtreecommitdiff
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
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.
-rwxr-xr-xbin/debefivm-create498
-rwxr-xr-xbin/debefivm-run401
-rwxr-xr-xbin/debefivm-ukify329
-rw-r--r--debian/clean3
-rw-r--r--debian/debvm.manpages3
-rwxr-xr-xdebian/rules13
6 files changed, 1242 insertions, 5 deletions
diff --git a/bin/debefivm-create b/bin/debefivm-create
new file mode 100755
index 0000000..6efc204
--- /dev/null
+++ b/bin/debefivm-create
@@ -0,0 +1,498 @@
+#!/bin/sh
+# Copyright 2023 Johannes Schauer Marin Rodrigues <josch@debian.org>
+# SPDX-FileCopyrightText: 2023-2025 Helmut Grohne <helmut@subdivi.de>
+# SPDX-License-Identifier: MIT
+
+# shellcheck disable=SC2016 # Intentional quoting technique
+
+: <<'POD2MAN'
+=head1 NAME
+
+debefivm-create - Create an EFI-bootable VM image for various Debian releases and architectures
+
+=head1 SYNOPSIS
+
+B<debefivm-create> [B<-h> I<hostname>] [B<-k> F<sshkey>] [B<-o> F<output>] [B<-r> I<release>] [B<-s> <task>] [B<-z> I<size>] [B<--> I<mmdebstrap options>]
+
+=head1 DESCRIPTION
+
+Create an EFI-bootable disk image image booting into Debian.
+This essentially is a thin wrapper around B<mmdebstrap> much like B<debvm-create>.
+The resulting image contains a GPT partition table with an EFI system partition.
+A Unified Kernel Image is constructed from the installed kernel and initrd and placed into said partition.
+As a result, such an image may be booted with a variety of virtual machine emulators such as incus, libvirt or VirtualBox.
+It may also be booted using B<debefivm-run>.
+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>
+
+Set the architecture of the virtual machine.
+This option is forwarded to B<mmdebstrap> and multiple architectures may be supplied.
+The first architecture is used to assemble the Unified Kernel Image.
+It defaults to the native architecture of the installation.
+
+=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> F<sshkey>, B<--sshkey>=F<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.
+To connect to the vm, pass a port number to B<debefivm-run> with the B<-s> option.
+
+=item B<-o> F<output>, B<--output>=F<output>
+
+Specify the file name of the resulting virtual machine image.
+By default, it is written to F<vm.img>.
+
+=item B<-r> I<release>, B<--release>=I<release>
+
+Use the given Debian release.
+By default, B<unstable> is being used.
+If you pass a complete F<sources.list> that includes release names to B<mmdebstrap>, you may pass an empty string here.
+
+=item B<-s> I<task>, 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<autologin>
+
+Skips adding a the customize-autologin.sh to B<mmdebstrap> that configures
+automatic root login on a serial console and also parses the C<TERM> kernel
+cmdline and passes it as C<TERM> to B<agetty>.
+
+=item B<kernel>
+
+skips installing a linux kernel image.
+This can be useful to install a kernel without a package.
+If a kernel is installed via B<mmdebstrap> option C<--include>, automtatic kernel installation is automatically skipped.
+
+=item B<packagelists>
+
+reduces the package lists inside the image.
+The B<available> database for B<dpkg> is not created.
+The package lists used by B<apt> are deleted.
+This generally produces a smaller image, but you need to run B<apt update> before installing packages and B<dpkg --set-selections> does not work.
+
+=item B<systemdnetwork>
+
+skips installing B<libnss-resolve> as well as automatic network configuration via B<systemd-networkd>.
+
+=item B<usrmerge>
+
+By default B<debefivm-create> adds a hook to enable merged-/usr without the B<usrmerge> package given a sufficiently recent Debian release.
+Without the hook, dependencies will pull the B<usrmerge> package as needed, which may result in a larger installation.
+
+=back
+
+=item B<--rootsize>=I<size>
+
+This option may be used as an alternative to B<--imagesize> to determine the size of the output image.
+It is also composed of an integer with optional unit.
+Unlike B<--imagesize> it determines the size of the root filesystem partition and the resulting image will be slightly larger than the size given here.
+
+=item B<-z> I<size>, B<--imagesize>=I<size>
+
+Specify the total image size as an integer and optional unit (example: 10K is 10*1024).
+Units are K,M,G,T (powers of 1024).
+The disk image contains an EFI system partition and an ext4 root filesystem.
+The EFI partition has a static size of about 128MB and the remaining space will be allocated to the root filesystem.
+The default is 10 GB.
+
+=item B<--> I<mmdebstrap options>
+
+All options beyond a double dash are passed to B<mmdebstrap> after the suite and target specification.
+This can be used to provide additional hooks for image customization.
+You can also request additional packages to be installed into the image using B<mmdebstrap>'s B<--include> option.
+Any positional arguments passed here will be treated as mirror specifications by B<mmdebstrap>.
+The B<--architecture> option should not be given here as B<debefivm-create> needs to know its value to construct a Unified Kernel Image.
+
+=back
+
+=head1 EXAMPLES
+
+Create an image suitable for use with B<autopkgtest-virt-qemu> B<--efi>.
+
+ debefivm-create ... -- --include=linux-image-generic,libpam-systemd,passwd,python3 --hook-dir=/usr/share/mmdebstrap/hooks/useradd --customize-hook=/usr/share/autopkgtest/setup-commands/setup-testbed
+
+=head1 SEE ALSO
+
+ debefivm-run(1) debefivm-ukify(1) debvm-create(1) mmdebstrap(1)
+
+=cut
+POD2MAN
+
+set -eu
+
+DEBARCH=$(dpkg --print-architecture)
+EFILABEL=efisys
+FAT_SIZE_KIB=$((126*1024))
+IMAGE=vm.img
+SKIP=,
+SSHKEY=
+SUITE=unstable
+ROOTLABEL=rootfs
+TOT_SIZE_KIB=$((10*1024*1024))
+VMNAME=testvm
+
+SHARE_DIR="${0%/*}/../share"
+BIN_DIR="${0%/*}/../bin/"
+
+# Disk layout:
+# * GPT partition table in the front. Oversize to align.
+GPT_FRONT_SIZE_KIB=1024
+# * FAT partition containing the EFI boot loader. Size configured.
+FAT_OFFSET_KIB=$GPT_FRONT_SIZE_KIB
+# * Main ext4 root partition. Compute as remaining size.
+EXT4_OFFSET_KIB=$((FAT_OFFSET_KIB + FAT_SIZE_KIB))
+# EXT4_SIZE_KIB and TOT_SIZE_KIB depend on one another.
+# * GPT backup table in the back. Oversize to align.
+GPT_BACK_SIZE_KIB=1024
+
+NON_EXT4_SIZE_KIB=$((EXT4_OFFSET_KIB + GPT_BACK_SIZE_KIB))
+EXT4_SIZE_KIB=$((TOT_SIZE_KIB - NON_EXT4_SIZE_KIB))
+
+nth_arg() {
+ shift "$1"
+ printf "%s" "$1"
+}
+
+die() {
+ echo "$*" >&2
+ exit 1
+}
+usage() {
+ die "usage: $0 [-a architecture] [-h hostname] [-k sskey] [-o output] [-r release] [-s task] [-z imagesize] [-- mmdebstrap options]"
+}
+usage_error() {
+ echo "error: $*" 1>&2
+ usage
+}
+
+opt_architecture() {
+ DEBARCH="$1"
+}
+opt_hostname() {
+ VMNAME=$1
+}
+opt_output() {
+ IMAGE=$1
+ if test "${IMAGE#-}" != "$IMAGE"; then
+ IMAGE="./$IMAGE"
+ fi
+}
+opt_release() {
+ SUITE="$1"
+}
+opt_rootsize() {
+ EXT4_SIZE_KIB="$(numfmt --to=none --to-unit=Ki --from=iec "$1")" ||
+ die "failed to parse size '$1'"
+ TOT_SIZE_KIB=$((EXT4_SIZE_KIB + NON_EXT4_SIZE_KIB))
+}
+opt_skip() {
+ SKIP="$SKIP$1,"
+}
+opt_sshkey() {
+ SSHKEY=$1
+}
+opt_imagesize() {
+ TOT_SIZE_KIB="$(numfmt --to=none --to-unit=Ki --from=iec "$1")" ||
+ die "failed to parse size '$1'"
+ EXT4_SIZE_KIB=$((TOT_SIZE_KIB - NON_EXT4_SIZE_KIB))
+}
+
+while getopts :a:h:k:o:r:s:z:-: OPTCHAR; do
+ case "$OPTCHAR" in
+ a) opt_architecture "$OPTARG" ;;
+ h) opt_hostname "$OPTARG" ;;
+ k) opt_sshkey "$OPTARG" ;;
+ o) opt_output "$OPTARG" ;;
+ r) opt_release "$OPTARG" ;;
+ s) opt_skip "$OPTARG" ;;
+ z) opt_imagesize "$OPTARG" ;;
+ -)
+ case "$OPTARG" in
+ help)
+ usage
+ ;;
+ architecture|hostname|imagesize|output|release|rootsize|skip|sshkey)
+ test "$OPTIND" -gt "$#" && usage_error "missing argument for --$OPTARG"
+ "opt_$OPTARG" "$(nth_arg "$OPTIND" "$@")"
+ OPTIND=$((OPTIND+1))
+ ;;
+ architecture=*|hostname=*|imagesize=*|output=*|release=*|rootsize=*|size=*|skip=*|sshkey=*)
+ "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 "$SSHKEY" && ! test -f "$SSHKEY"; then
+ die "error: ssh keyfile '$SSHKEY' not found"
+fi
+
+check_skip() {
+ case "$SKIP" in
+ *",$1,"*) return 0 ;;
+ *) return 1 ;;
+ esac
+}
+
+test "$EXT4_SIZE_KIB" -lt "$((1024*64))" &&
+ die "implausibly small disk size $TOT_SIZE_KIB"
+
+# In a multiarch setting, we cannot tell in advance which architecture will be
+# supplying the Linux kernel image, but that's the architecture we need the EFI
+# stub for. Hence fail early.
+test "${DEBARCH%,*}" = "$DEBARCH" ||
+ die "multiarch images are not yet supported"
+
+case "${DEBARCH%%,*}" in
+ amd64)
+ EFIARCH=x64
+ ROOTGUID=4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709
+ ;;
+ arm64)
+ EFIARCH=aa64
+ ROOTGUID=B921B045-1DF0-41C3-AF44-4C6F280D3FAE
+ ;;
+ armhf)
+ EFIARCH=arm
+ ROOTGUID=69DAD710-2CE4-4E3C-B16C-21A1D49ABED3
+ ;;
+ i386)
+ EFIARCH=ia32
+ ROOTGUID=44479540-F297-41B2-9AF7-D131D5F0458A
+ ;;
+ riscv64)
+ EFIARCH=riscv64
+ ROOTGUID=72EC70A6-CF74-40E6-BD49-4BDA08E8F224
+ ;;
+ *)
+ die "unsupported architecture: $DEBARCH"
+ ;;
+esac
+
+test -e "$IMAGE" &&
+ die "output file $IMAGE already exists"
+
+if command -v ukify >/dev/null; then
+ UKIFY=ukify
+ UKIFY_VERBOSE=
+else
+ UKIFY="${BIN_DIR}debefivm-ukify"
+ UKIFY_VERBOSE=--verbose
+fi
+
+WORKDIR=
+
+cleanup() {
+ test -n "$WORKDIR" && rm -Rf "$WORKDIR"
+}
+cleanup_abort() {
+ cleanup
+ echo aborted >&2
+ exit 2
+}
+trap cleanup EXIT
+trap cleanup_abort HUP INT TERM QUIT
+
+WORKDIR=$(mktemp -d)
+
+truncate --size="${TOT_SIZE_KIB}K" "$IMAGE"
+
+# Stuff most of our options before the user options to allow them to override
+# ours.
+#
+# We skip replacing start-stop-daemon, because policy-rc.d nowadays works well
+# enough and then we have to not restore it before calling mkfs.ext4.
+set -- \
+ --mode=root \
+ --variant=apt \
+ --architecture="$DEBARCH" \
+ --skip=chroot/start-stop-daemon \
+ "$@"
+
+if ! check_skip kernel; then
+ set -- "--customize-hook=$SHARE_DIR/customize-kernel.sh" "$@"
+fi
+
+set -- \
+ "--customize-hook=printf 'LABEL=%s / ext4 defaults 0 0' '$ROOTLABEL' >"'"$1/etc/fstab"' \
+ "--include=?narrow(?exact-name(systemd-boot),?architecture(${DEBARCH%%,*}))" \
+ '--include=systemd-sysv' \
+ "$SUITE" \
+ /dev/null \
+ "$@"
+
+# set up a hostname
+ETC_HOSTS_TEMPLATE='127.0.0.1 localhost\n127.0.1.1 %s\n::1 ip6-localhost ip6-loopback\n'
+set -- \
+ "--customize-hook=echo '$VMNAME' >"'"$1/etc/hostname"' \
+ "--customize-hook=printf '$ETC_HOSTS_TEMPLATE' '$VMNAME' >"'"$1/etc/hosts"' \
+ "$@"
+
+# allow password-less root login
+set -- "--customize-hook=$SHARE_DIR/customize-delete-rootpw.sh" "$@"
+
+if ! check_skip systemdnetwork; then
+ # dhcp on all network interfaces, and add a dns resolver
+ set -- \
+ "--customize-hook=$SHARE_DIR/customize-networkd.sh" \
+ '--include=?not(?virtual)?exact-name(libnss-resolve)' \
+ "--customize-hook=$SHARE_DIR/customize-resolved.sh" \
+ "$@"
+fi
+
+# add ssh key for root
+if test -n "$SSHKEY"; then
+ set -- \
+ --include=openssh-server \
+ '--customize-hook=mkdir -m700 -p "$1/root/.ssh"' \
+ "--customize-hook=upload $SSHKEY /root/.ssh/authorized_keys" \
+ "$@"
+fi
+
+if ! check_skip packagelists; then
+ set -- --skip=cleanup/apt/lists "$@"
+ set -- "--customize-hook=$SHARE_DIR/customize-dpkgavailable.sh" "$@"
+fi
+
+if ! check_skip usrmerge; then
+ # Avoid the usrmerge package
+ set -- --hook-dir=/usr/share/mmdebstrap/hooks/maybe-merged-usr "$@"
+fi
+
+if ! check_skip autologin; then
+ set -- \
+ --include=login \
+ "--customize-hook=$SHARE_DIR/customize-autologin.sh" "$@"
+fi
+
+# By default, Debian mounts the EFI System Partition to /boot/efi. Keep that
+# default.
+BOOT_EFI_MOUNT_TEMPLATE='[Unit]\nDescription=EFI System Partition\n\n[Mount]\nWhat=LABEL=%s\nWhere=/boot/efi\nType=vfat\nOptions=umask=0077,rw,nodev,nosuid,noexec,nosymfollow\n\n[Install]\nWantedBy=local-fs.target\n'
+set -- \
+ '--customize-hook=mkdir "$1/boot/efi"' \
+ "--customize-hook=printf '$BOOT_EFI_MOUNT_TEMPLATE' '$EFILABEL' >"'"$1/etc/systemd/system/boot-efi.mount"' \
+ '--customize-hook=systemctl --root "$1" enable boot-efi.mount' \
+ "$@"
+
+# Pass the remaining hooks last. These collect data from the generated tree and
+# create the resulting filesystem. All user-supplied customizations should have
+# run before these.
+#
+# We download the artifacts that end up being turned into the EFI.
+EXT4_OPTIONS="offset=$((EXT4_OFFSET_KIB * 1024)),assume_storage_prezeroed=1"
+set -- "$@" \
+ "--customize-hook=download vmlinuz '$WORKDIR/kernel'" \
+ "--customize-hook=download initrd.img '$WORKDIR/initrd'" \
+ "--customize-hook=download '/usr/lib/systemd/boot/efi/linux$EFIARCH.efi.stub' '$WORKDIR/stub'" \
+ '--customize-hook=mount --bind "$1" "$1/mnt"' \
+ '--customize-hook=mount -t tmpfs tmpfs "$1/mnt/dev" -o mode=0755' \
+ '--customize-hook=rm -f "$1/usr/sbin/policy-rc.d"' \
+ '--customize-hook=/sbin/mkfs.ext4 -d "$1/mnt" -L '"'$ROOTLABEL' -E '$EXT4_OPTIONS' '$IMAGE' '${EXT4_SIZE_KIB}K'" \
+ '--customize-hook=umount --lazy "$1/mnt"'
+
+set -- mmdebstrap "$@"
+
+# We need to write the resulting $IMAGE from inside the namespace used by
+# mmdebstrap. If we were to have mmdebstrap --mode=unshare set up that
+# namespace, we could end up being unable to write it if a leading directory
+# component in $IMAGE were not not grant execute permission to any of the
+# subuids mapped. This typically is the case when using PrivateTmp=yes. With
+# that setting, there is a 0700 directory owned by the current user that
+# happens to not be mapped by mmdebstrap.
+#
+# Instead, we take matters into our own hands and set up the namespaces outside
+# mmdebstrap asking it to assume root. In addition to what mmdebstrap would do
+# by itself, we also map the current user to 65536. The precise uid does not
+# matter, but having it mapped allows CAP_DAC_OVERRIDE to be used for writing
+# the image.
+if test "$(id -u)" != 0; then
+ set -- \
+ unshare \
+ --user \
+ --mount \
+ --ipc \
+ --pid \
+ --uts \
+ --fork \
+ --kill-child=TERM \
+ --map-user=65536 \
+ --map-users=auto \
+ --map-group=65536 \
+ --map-groups=auto \
+ --propagation=private \
+ --mount-proc \
+ --setuid=0 \
+ --setgid=0 \
+ "$@"
+fi
+echo "+ $*" >&2
+"$@" || die "mmdebstrap failed"
+
+CMDLINE="root=LABEL=$ROOTLABEL rw"
+case "${DEBARCH%%,*}" in
+ amd64|i386)
+ CMDLINE="$CMDLINE console=ttyS0"
+ ;;
+esac
+
+set -- "$UKIFY" \
+ build \
+ $UKIFY_VERBOSE \
+ --efi-arch "$EFIARCH" \
+ --linux "$WORKDIR/kernel" \
+ --initrd "$WORKDIR/initrd" \
+ --cmdline "$CMDLINE" \
+ --stub "$WORKDIR/stub" \
+ --output "$WORKDIR/efiimg"
+echo "+ $*" >&2
+"$@" ||
+ die "failed to generate UKI"
+
+rm -f "$WORKDIR/kernel" "$WORKDIR/initrd" "$WORKDIR/stub"
+
+truncate -s "${FAT_SIZE_KIB}K" "$WORKDIR/fat"
+/sbin/mkfs.fat -n "$EFILABEL" -F 32 --invariant "$WORKDIR/fat"
+mmd -i "$WORKDIR/fat" EFI EFI/BOOT
+mcopy -i "$WORKDIR/fat" "$WORKDIR/efiimg" "::EFI/BOOT/boot$EFIARCH.efi"
+
+rm -f "$WORKDIR/efiimg"
+
+/sbin/sfdisk "$IMAGE" <<EOF
+label: gpt
+unit: sectors
+
+start=${FAT_OFFSET_KIB}KiB, size=${FAT_SIZE_KIB}KiB, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B
+start=$((FAT_OFFSET_KIB + FAT_SIZE_KIB))KiB, type=$ROOTGUID
+EOF
+
+dd if="$WORKDIR/fat" of="$IMAGE" conv=notrunc,sparse bs=1024 "seek=$FAT_OFFSET_KIB" status=none
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" "$@"
diff --git a/bin/debefivm-ukify b/bin/debefivm-ukify
new file mode 100755
index 0000000..9267158
--- /dev/null
+++ b/bin/debefivm-ukify
@@ -0,0 +1,329 @@
+#!/bin/sh
+# SPDX-FileCopyrightText: 2023-2025 Helmut Grohne <helmut@subdivi.de>
+# SPDX-License-Identifier: MIT
+#
+
+: <<'POD2MAN'
+=head1 NAME
+
+debefivm-ukify - Create Unified Kernel Images for UEFI systems in a similar way to systemd's ukify
+
+=head1 SYNOPSIS
+
+B<debefivm-ukify> B<build> B<--linux>=F<vmlinuz> B<--output>=F<uki.efi> [options]
+
+=head1 DESCRIPTION
+
+B<debefivm-ukify> is a reimplementation of parts of B<systemd-ukify> and mainly meant for backports.
+Please prefer B<systemd>'s implementation.
+It only supports the B<build> subcommand, does not implement any functionality related to secure boot and lacks configuration file support.
+Other than that, it can be used to assemble a linux kernel image, an initrd and an EFI stub image from B<systemd> into an EFI-bootable Unified Kernel Image.
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<--deb-arch>=I<debian_architecture>
+
+Set the architecture of the supplied and produced artifacts using Debian's architecture nomenclature.
+See B<--efi-arch> for another supplying it using the EFI nomenclature instead.
+This option is not available on the B<systemd> implementation.
+
+=item B<--devicetree>=F<devicetree_blob>
+
+Add a device tree file as a dtb section to the generated UKI.
+There can be at most one device tree.
+
+=item B<--cmdline>=I<kernel_cmdline>
+
+Supply extra arguments to be appended to the Linux kernel command line during boot.
+Can be given given at most once.
+If the value starts with an B<@>, it is treated as a filename whose contents will be added.
+
+=item B<--efi-arch>=I<efi_architecture>
+
+Set the architecture of the supplied and produced artifacts using EFI's architecture nomenclature.
+See B<--deb-arch> for another supplying it using the Debian nomenclature instead.
+
+=item B<--initrd>=F<initrd>
+
+Supply an initrd image to be embedded.
+There can be at most one initrd.
+
+=item B<--linux>=F<vmlinuz>
+
+Supply a Linux kernel image to be embedded.
+This option must be given exactly once.
+
+=item B<--os-release>=I<release_information>
+
+Set the os-release description of in the UKI.
+Can be given given at most once.
+If the value starts with an B<@>, it is treated as a filename whose contents will be added.
+
+=item B<--output>=<uki.efi>
+
+Set the name of the resulting UKI image.
+This option must be given exactly once and the target file must not exist already.
+
+=item B<--section>=<name:value>
+
+Add an arbitrary ELF section to the resulting UKI.
+If the value starts with an B<@>, it is treated as a filename whose contents will be added.
+Most image components actually are such ELF sections.
+For instance B<--os-release>, adds a section named B<osrel>.
+
+=item B<--stub>=F<efi_stub>
+
+Supply the location of B<systemd>'s EFI stub image.
+Unlike the B<systemd> implementation, this option accepts a directory containing stubs named by architecture.
+By default, stubs will be looked up in B</usr/lib/systemd/boot/efi>.
+
+=item B<--verbose>
+
+Print diagnostic information during image conversion.
+This option is not available on the B<systemd> implementation.
+
+=back
+
+=head1 SEE ALSO
+
+ ukify(1)
+
+=cut
+POD2MAN
+
+set -eu
+
+die() {
+ echo "$*" >&2
+ exit 1
+}
+usage() {
+ die "usage: $0 [build] <--linux=|--initrd=|--stub=> [--cmdline=|--deb-arch=|--devicetree=|--efi-arch=|--os-release=|--output=|--section=|--verbose]"
+}
+usage_error() {
+ echo "error: $*" >&2
+ usage
+}
+
+DEBARCH=
+BOOTSTUB=/usr/lib/systemd/boot/efi
+OUTPUT=
+VERBOSE=
+
+TDIR=
+cleanup() {
+ if test -n "$TDIR"; then
+ rm -Rf "$TDIR"
+ fi
+}
+cleanup_abort() {
+ cleanup
+ echo aborted >&2
+ exit 2
+}
+trap cleanup EXIT
+trap cleanup_abort HUP INT TERM QUIT
+
+# The TDIR will contain one file per section. It can be a symbolic link or
+# regular file. The section name is the filename with a leading dot.
+TDIR=$(mktemp --directory --tmpdir mini-ukify.XXXXXXXXXX)
+
+add_section_content() {
+ test -e "$TDIR/$1" && die "section '$1' already defined"
+ printf "%s" "$2" > "$TDIR/$1"
+}
+add_section_file() {
+ test -e "$TDIR/$1" && die "section '$1' already defined"
+ test -f "$2" || die "file '$2' does not exist"
+ if test "${2#/}" = "$2"; then
+ ln --symbolic --relative "$2" "$TDIR/$1"
+ else
+ ln --symbolic "$2" "$TDIR/$1"
+ fi
+}
+add_section_either() {
+ if test "${2#@}" = "$2"; then
+ add_section_content "$1" "$2"
+ else
+ add_section_file "$1" "${2#@}"
+ fi
+}
+
+opt_cmdline() {
+ add_section_either cmdline "$1"
+}
+opt_deb_arch() {
+ case "$1" in
+ amd64)
+ EFIARCH=x64
+ GNU_TYPE=x86_64-linux-gnu
+ ;;
+ arm64)
+ EFIARCH=aa64
+ GNU_TYPE=aarch64-linux-gnu
+ ;;
+ armhf)
+ EFIARCH=arm
+ GNU_TYPE=arm-linux-gnueabihf
+ ;;
+ i386)
+ EFIARCH=ia32
+ GNU_TYPE=i686-linux-gnu
+ ;;
+ riscv64)
+ EFIARCH=riscv64
+ GNU_TYPE=riscv64-linux-gnu
+ ;;
+ *)
+ die "unsupported Debian architecture: $1"
+ ;;
+ esac
+ DEBARCH="$1"
+}
+opt_devicetree() {
+ add_section_file dtb "$1"
+}
+opt_efi_arch() {
+ case "$1" in
+ aa64) opt_deb_arch arm64 ;;
+ arm) opt_deb_arch armhf ;;
+ ia32) opt_deb_arch i386 ;;
+ x64) opt_deb_arch amd64 ;;
+ riscv64) opt_deb_arch "$1" ;;
+ *)
+ die "unsupported EFI architecture: $1"
+ ;;
+ esac
+}
+opt_initrd() {
+ add_section_file initrd "$1"
+}
+opt_linux() {
+ add_section_file linux "$1"
+ test -z "$OUTPUT" && OUTPUT="$1.unsigned.efi"
+}
+opt_os_release() {
+ add_section_either osrel "$1"
+}
+opt_output() {
+ OUTPUT="$1"
+}
+opt_section() {
+ case "$1" in
+ *[!a-z]*:*)
+ die "invalid section name '${1%%:*}'"
+ ;;
+ *:*)
+ ;;
+ *)
+ die "missing section name separated by colon in '$1'"
+ ;;
+ esac
+ add_section_either "${1%%:*}" "${1#*:}"
+}
+opt_stub() {
+ BOOTSTUB="$1"
+}
+opt_verbose() {
+ VERBOSE=yes
+}
+positional=1
+positional_1() {
+ test "$1" = "build" || usage_error "command not understood: '$1'"
+}
+positional_2() {
+ usage_error "too many positional arguments"
+}
+
+while test "$#" -gt 0; do
+ case "$1" in
+ --cmdline=*|--deb-arch=*|--devicetree=*|--efi-arch=*|--initrd=*|--linux=*|--os-release=*|--output=*|--section=*|--stub=*)
+ optname="${1%%=*}"
+ optname="${optname#--}"
+ test "${optname#*-}" = "$optname" ||
+ optname="${optname%%-*}_${optname#*-}"
+ "opt_$optname" "${1#*=}"
+ ;;
+ --cmdline|--deb-arch|--devicetree|--efi-arch|--initrd|--linux|--os-release|--output|--section|--stub)
+ test "$#" -ge 2 || usage_error "missing argument for $1"
+ optname="${1#--}"
+ test "${optname#*-}" = "$optname" ||
+ optname="${optname%%-*}_${optname#*-}"
+ "opt_$optname" "$2"
+ shift
+ ;;
+ --verbose)
+ "opt_${1#--}"
+ ;;
+ --*)
+ usage_error "unrecognized option $1"
+ ;;
+ *)
+ "positional_$positional" "$1"
+ positional=$((positional + 1))
+ ;;
+ esac
+ shift
+done
+
+test "$positional" = 2 || usage_error "missing subcommand, only 'build' is supported"
+test -z "$DEBARCH" && opt_deb_arch "$(dpkg --print-architecture)"
+test -e "$TDIR/linux" || usage_error "missing --linux argument"
+test -e "$BOOTSTUB" || die "stub image or directory '$BOOTSTUB' does not exist"
+test -e "$OUTPUT" && die "output '$OUTPUT' already exists"
+
+test -d "$BOOTSTUB" && BOOTSTUB="$BOOTSTUB/linux$EFIARCH.efi.stub"
+test -f "$BOOTSTUB" || die "efi boot stub $BOOTSTUB not found"
+
+GNU_PREFIX="$GNU_TYPE-"
+test "$(dpkg-query --showformat='${db:Status-Status}' --show binutils-multiarch)" = installed &&
+ GNU_PREFIX=
+
+# Compute the next multiple of $2 greater than or equal to $1.
+align_size() {
+ echo "$((($1) + ($2) - 1 - (($1) + ($2) - 1) % ($2)))"
+}
+alignment=$("${GNU_PREFIX}objdump" --private-headers "$BOOTSTUB" | sed 's/^SectionAlignment\s\+\([0-9]\)/0x/;t;d')
+test -z "$alignment" &&
+ die "failed to discover the alignment of the efi stub"
+test -n "$VERBOSE" &&
+ echo "determined efi vma alignment as $alignment"
+
+# Discover the last section in terms of vma + size. We'll append sections
+# beyond.
+lastoffset=0
+# shellcheck disable=SC2034 # unused variables serve documentation
+lastoffset="$("${GNU_PREFIX}objdump" --section-headers "$BOOTSTUB" \
+ | while read -r idx name size vma lma fileoff algn behind; do
+ test -z "$behind" -a "${algn#"2**"}" != "$algn" || continue
+ offset=$((0x$vma + 0x$size))
+ test "$offset" -gt "$lastoffset" || continue
+ lastoffset="$offset"
+ echo "$lastoffset"
+ done | tail -n1)"
+lastoffset=$(align_size "$lastoffset" "$alignment")
+test -n "$VERBOSE" &&
+ echo "determined minimum efi vma offset as $lastoffset"
+
+# Compute the objdump invocation that constructs the UKI. Successively add
+# sections and compute non-overlapping VMAs.
+set -- \
+ "${GNU_PREFIX}objcopy" \
+ --enable-deterministic-archives
+for sectionfile in "$TDIR/"*; do
+ section="${sectionfile##*/}"
+ size=$(stat -Lc%s "$sectionfile")
+ size=$(align_size "$size" "$alignment")
+ set -- "$@" \
+ --add-section ".$section=$sectionfile" \
+ --change-section-vma ".$section=$lastoffset"
+ lastoffset=$((lastoffset + size))
+done
+set -- "$@" "$BOOTSTUB" "$OUTPUT"
+if test -n "$VERBOSE"; then
+ ls -lA "$TDIR"
+ printf "%s\n" "$*"
+fi
+"$@" || die "failed to construct UKI"
diff --git a/debian/clean b/debian/clean
index e3e1d26..2d50bae 100644
--- a/debian/clean
+++ b/debian/clean
@@ -1,3 +1,6 @@
+debefivm-create.1
+debefivm-run.1
+debefivm-ukify.1
debvm-create.1
debvm-run.1
debvm-waitssh.1
diff --git a/debian/debvm.manpages b/debian/debvm.manpages
index e3e1d26..2d50bae 100644
--- a/debian/debvm.manpages
+++ b/debian/debvm.manpages
@@ -1,3 +1,6 @@
+debefivm-create.1
+debefivm-run.1
+debefivm-ukify.1
debvm-create.1
debvm-run.1
debvm-waitssh.1
diff --git a/debian/rules b/debian/rules
index 36a2e9b..2677cce 100755
--- a/debian/rules
+++ b/debian/rules
@@ -3,10 +3,13 @@
%:
dh $@
-override_dh_auto_build:
- pod2man bin/debvm-create debvm-create.1
- pod2man bin/debvm-run debvm-run.1
- pod2man bin/debvm-waitssh debvm-waitssh.1
+%.1:bin/%
+ pod2man $< $@
+
+override_dh_auto_build:debefivm-create.1 debefivm-run.1 debefivm-ukify.1 debvm-create.1 debvm-run.1 debvm-waitssh.1
execute_after_dh_install:
- sed -i -e 's,^\(SHARE_DIR=\).*,\1/usr/share/debvm,' debian/debvm/usr/bin/*
+ sed -i \
+ -e 's,^\(SHARE_DIR=\).*,\1/usr/share/debvm,' \
+ -e 's,^\(BIN_DIR=\).*,\1,' \
+ debian/debvm/usr/bin/*