summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml12
-rw-r--r--README.md14
-rwxr-xr-xbin/debvm-create (renamed from debvm-create)240
-rwxr-xr-xbin/debvm-run (renamed from debvm-run)99
-rwxr-xr-xbin/debvm-waitssh178
-rwxr-xr-xshare/customize-autologin.sh23
-rwxr-xr-xshare/customize-dpkgavailable.sh12
-rwxr-xr-xshare/customize-kernel.sh57
-rwxr-xr-xshare/customize-networkd.sh37
-rwxr-xr-xshare/customize-resolved.sh26
-rwxr-xr-xtests/create-and-run.sh39
-rwxr-xr-xtests/dist-upgrades.sh55
-rw-r--r--tests/test_common.sh7
-rwxr-xr-xuseraddhook/customize.sh34
14 files changed, 641 insertions, 192 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6427330..4604045 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -5,14 +5,14 @@ shellcheck:
- apt-get update
- apt-get dist-upgrade --yes
- apt-get --no-install-recommends --yes install shellcheck
- - shellcheck debvm-*
+ - shellcheck -P tests bin/* share/*.sh tests/*.sh
codespell:
script:
- apt-get update
- apt-get dist-upgrade --yes
- apt-get --no-install-recommends --yes install codespell
- - codespell debvm-*
+ - codespell bin/* share/*.sh tests/*.sh README.md
release_test:
parallel:
@@ -27,8 +27,8 @@ release_test:
script:
- apt-get update
- apt-get dist-upgrade --yes
- - apt-get --no-install-recommends --yes install e2fsprogs genext2fs mmdebstrap openssh-client sleepenh qemu-kvm
- - PATH=.:$PATH ./tests/create-and-run.sh $(dpkg --print-architecture) "$RELEASE"
+ - apt-get --no-install-recommends --yes install e2fsprogs genext2fs mmdebstrap openssh-client qemu-kvm
+ - PATH=$(pwd)/bin:$PATH ./tests/create-and-run.sh $(dpkg --print-architecture) "$RELEASE"
arch_test:
parallel:
@@ -45,5 +45,5 @@ arch_test:
- 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
- - PATH=.:$PATH ./tests/create-and-run.sh "$ARCHITECTURE" sid
+ - apt-get --no-install-recommends --yes install e2fsprogs genext2fs mmdebstrap openssh-client qemu-system binfmt-support arch-test qemu-user-static
+ - PATH=$(pwd)/bin:$PATH ./tests/create-and-run.sh "$ARCHITECTURE" sid
diff --git a/README.md b/README.md
index 8109b2a..0b096a5 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ invocation.
The following two invocations will give you a shell inside a qemu virtual
machine of the native architecture, leaving all the settings at their defaults:
- ./debvm-create && ./debvm-run
+ ./bin/debvm-create && ./bin/debvm-run
What do I need?
===============
@@ -45,10 +45,10 @@ 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` 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.
+ * `(|/boot)/vmlinu[xz]` must be a symbolic link pointing to a regular file
+ containing the kernel.
+ * `initrd.img` must be a symbolic link in the same directory as the kernel
+ image pointing to a regular file containing the initrd image.
Why?
====
@@ -62,6 +62,10 @@ using the one included in qemu.
The other aspect is restricting to Debian-based systems. This allows for a lot
of simplification of the problem space.
+While most similar tools require root privileges at some point, this one works
+with either fakeroot or a subuid allocation for user namespaces, which is often
+available.
+
The implementation is so short that it still is feasible to read and understand
it. Let's see how long that lasts.
diff --git a/debvm-create b/bin/debvm-create
index e8bcf92..c554165 100755
--- a/debvm-create
+++ b/bin/debvm-create
@@ -11,7 +11,7 @@ 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>]
+B<debvm-create> [B<-a> I<architecture>] [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
@@ -37,47 +37,84 @@ A suitable kernel image is automatically selected and installed into the image.
Set the hostname of the virtual machine.
By default, the hostname is B<testvm>.
-=item B<-k> I<sshkey>, B<--sshkey>=I<sshkey>
+=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<debvm-run> with the B<-s> option.
-=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>
+=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 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.
+By default, it is written to F<rootfs.ext4>.
=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>
+=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>
-Specify the minimum image size in giga bytes.
+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<debvm> 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<-z> I<size>, B<--size>=I<size>
+
+Specify the minimum image size as an integer and optional unit (example: 10K is 10*1024).
+Units are K,M,G,T,P,E,Z,Y (powers of 1024) or KB,MB,... (powers of 1000).
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.
+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>.
=back
+=head1 EXAMPLES
+
+In order to create images for Debian ports architectures, you can pass two options to mmdebstrap:
+
+ debvm-create ... -- http://deb.debian.org/debian-ports --keyring=/usr/share/keyrings/debian-ports-archive-keyring.gpg
+
=head1 SEE ALSO
debvm-run(1) mmdebstrap(1)
@@ -89,13 +126,14 @@ set -u
ARCHITECTURE=$(dpkg --print-architecture)
IMAGE=rootfs.ext4
-INCLUDE_PACKAGES=init
-MIRROR="http://deb.debian.org/debian"
-SIZE=$((1024*1024*1024))
+SIZE=1G
+SKIP=,
SSHKEY=
SUITE=unstable
VMNAME=testvm
+SHARE_DIR="${0%/*}/../share"
+
nth_arg() {
shift "$1"
printf "%s" "$1"
@@ -106,7 +144,7 @@ die() {
exit 1
}
usage() {
- die "usage: $0 [-a architecture] [-h hostname] [-k sshkey] [-m mirror] [-o output] [-p packages] [-r release] [-z size_in_GB] [-- mmdebstrap options]"
+ die "usage: $0 [-a architecture] [-h hostname] [-k sshkey] [-o output] [-r release] [-s task] [-z size] [-- mmdebstrap options]"
}
usage_error() {
echo "error: $*" 1>&2
@@ -119,46 +157,43 @@ opt_architecture() {
opt_hostname() {
VMNAME=$1
}
-opt_mirror() {
- MIRROR=$1
+opt_skip() {
+ SKIP="$SKIP$1,"
}
opt_sshkey() {
SSHKEY=$1
}
opt_output() {
IMAGE=$1
-}
-opt_package() {
- INCLUDE_PACKAGES="$INCLUDE_PACKAGES,$1"
+ test "${IMAGE#-}" = "$IMAGE" || IMAGE="./$IMAGE"
}
opt_release() {
SUITE=$1
}
opt_size() {
- SIZE=$(($1*1024*1024*1024))
+ SIZE=$1
}
-while getopts :a:h:k:m:o:p:r:z:-: OPTCHAR; do
+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" ;;
- m) opt_mirror "$OPTARG" ;;
o) opt_output "$OPTARG" ;;
- p) opt_package "$OPTARG" ;;
r) opt_release "$OPTARG" ;;
+ s) opt_skip "$OPTARG" ;;
z) opt_size "$OPTARG" ;;
-)
case "$OPTARG" in
help)
usage
;;
- architecture|hostname|mirror|output|package|release|size|sshkey)
+ architecture|hostname|output|release|size|skip|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=*)
+ architecture=*|hostname=*|output=*|release=*|size=*|skip=*|sshkey=*)
"opt_${OPTARG%%=*}" "${OPTARG#*=}"
;;
*)
@@ -183,84 +218,15 @@ if test -n "$SSHKEY" && ! test -f "$SSHKEY"; then
die "error: ssh keyfile '$SSHKEY' not found"
fi
-case "$SUITE" in
- jessie)
- DEBVER=8
- ;;
- stretch)
- DEBVER=9
- ;;
- buster)
- DEBVER=10
- ;;
- bullseye|stable)
- DEBVER=11
- ;;
- bookworm|testing)
- DEBVER=12
- ;;
- trixie)
- DEBVER=13
- ;;
- forky)
- DEBVER=14
- ;;
- sid|unstable)
- DEBVER=999
- ;;
- *)
- die "unrecognized Debian release: $SUITE"
- ;;
-esac
-
-KERNEL_SUFFIX=-$ARCHITECTURE
-case "$ARCHITECTURE" in
- amd64|arm64)
- KERNEL_SUFFIX="-cloud-$ARCHITECTURE"
- if test "$DEBVER" -le 9; then
- KERNEL_SUFFIX="-$ARCHITECTURE"
- fi
- ;;
- armhf)
- KERNEL_SUFFIX=-armmp
- ;;
- i386)
- KERNEL_SUFFIX=-686-pae
- ;;
- mips64el)
- KERNEL_SUFFIX=-5kc-malta
- ;;
- mipsel)
- KERNEL_SUFFIX=-4kc-malta
- ;;
- ppc64el)
- KERNEL_SUFFIX=-powerpc64le
- ;;
-esac
-
-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"
-fi
+check_skip() {
+ case "$SKIP" in
+ *",$1,"*) return 0 ;;
+ *) return 1 ;;
+ esac
+}
-# add a DNS resolver
-if test "$DEBVER" -ge 9; then
- INCLUDE_PACKAGES="$INCLUDE_PACKAGES,libnss-resolve"
-fi
-if test "$DEBVER" -le 11; then
- set -- '--customize-hook=chroot "$1" systemctl enable systemd-resolved.service' "$@"
-fi
-if test "$DEBVER" -le 9; then
- set -- '--customize-hook=ln -fs ../run/systemd/resolve/resolv.conf "$1/etc/resolv.conf"' "$@"
-elif test "$DEBVER" -le 11; then
- set -- '--customize-hook=ln -fs ../run/systemd/resolve/stub-resolv.conf "$1/etc/resolv.conf"' "$@"
+if ! check_skip kernel; then
+ set -- "--customize-hook=$SHARE_DIR/customize-kernel.sh" "$@"
fi
# construct mmdebstrap options as $@:
@@ -268,12 +234,11 @@ set -- \
--verbose \
--variant=apt \
--format=ext2 \
+ --include=init \
"--architecture=$ARCHITECTURE" \
- "--include=$INCLUDE_PACKAGES" \
'--customize-hook=echo "LABEL=debvm / ext4 defaults 0 1" >"$1/etc/fstab"' \
"$@"
-
# set up a hostname
set -- \
"--customize-hook=echo $VMNAME >"'"$1/etc/hostname"' \
@@ -283,54 +248,53 @@ set -- \
# allow password-less root login
set -- '--customize-hook=chroot "$1" passwd --delete root' "$@"
-# dhcp on all network interfaces
-SYSD_NET_MATCH='Name=en*\n'
-test "$DEBVER" -le 8 && SYSD_NET_MATCH="${SYSD_NET_MATCH}Name=eth*\\n"
-SYSD_NET_NET='DHCP=yes\n'
-# This anchor is included by default since bullseye. Fails DNSSEC validation when missing.
-test "$DEBVER" -le 11 && SYSD_NET_NET="${SYSD_NET_NET}DNSSECNegativeTrustAnchors=home.arpa\\n"
-set -- \
- '--customize-hook=chroot "$1" systemctl enable systemd-networkd.service' \
- "--customize-hook=printf \"[Match]\\n$SYSD_NET_MATCH\\n[Network]\\n$SYSD_NET_NET"'\n[DHCP]\nUseDomains=yes\n" > "$1/etc/systemd/network/20-wired.network"' \
- "$@"
+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 -- \
- '--customize-hook=mkdir -p "$1/root/.ssh"' \
+ --include=openssh-server \
+ '--customize-hook=mkdir -m700 -p "$1/root/.ssh"' \
"--customize-hook=upload $SSHKEY /root/.ssh/authorized_keys" \
"$@"
fi
-set -- --skip=cleanup/apt/lists "$@"
-
-# Make dpkg --set-selections to work.
-set -- '--customize-hook=chroot "$1" apt-cache dumpavail | chroot "$1" dpkg --update-avail' "$@"
+if ! check_skip packagelists; then
+ set -- --skip=cleanup/apt/lists "$@"
+ set -- "--customize-hook=$SHARE_DIR/customize-dpkgavailable.sh" "$@"
+fi
-if test "$DEBVER" -le 8; then
+if test "$SUITE" = jessie; 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"' "$@"
set -- --hook-dir=/usr/share/mmdebstrap/hooks/jessie-or-older "$@"
fi
-if test "$DEBVER" -ge 12; then
+if ! check_skip usrmerge; then
# Avoid the usrmerge package
- set -- --hook-dir=/usr/share/mmdebstrap/hooks/merged-usr "$@"
+ set -- --hook-dir=/usr/share/mmdebstrap/hooks/maybe-merged-usr "$@"
fi
-# suite target mirror
-set -- "$@" "$SUITE" "$IMAGE" "deb $MIRROR $SUITE main"
+if ! check_skip autologin; then
+ set -- "--customize-hook=$SHARE_DIR/customize-autologin.sh" "$@"
+fi
+
+set -- "$SUITE" "$IMAGE" "$@"
set -ex
mmdebstrap "$@"
-IMAGESIZE=$(stat -c %s "$IMAGE")
-if test "$IMAGESIZE" -lt "$SIZE"; then
- truncate -s "$SIZE" "$IMAGE"
- /sbin/resize2fs "$IMAGE"
-fi
+truncate -s ">$SIZE" "$IMAGE"
+/sbin/resize2fs "$IMAGE"
/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 "$IMAGE"
diff --git a/debvm-run b/bin/debvm-run
index d6a5422..0ef8740 100755
--- a/debvm-run
+++ b/bin/debvm-run
@@ -9,14 +9,14 @@ 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>]
+B<debvm-run> [B<-g>] [B<-i> F<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>.
+The architecture of the machine is detected from the contained F</bin/true>.
+It must contain a symbolic link pointing to a kernel image at one of F<(|/boot)/vmlinu[xz]> a symbolic link pointing to an initrd image at F<initrd.img> in the same directory as the kernel image.
Both are extracted and passed to B<qemu>.
A net interface configured for user mode is added automatically.
@@ -29,14 +29,17 @@ A net interface configured for user mode is added automatically.
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>
+=item B<-i> F<image>, B<--image>=F<image>
This option specifies the location of the virtual machine image file.
-By default B<rootfs.ext4> in the working directory is used.
+By default F<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.
+You can connect to your virtual machine without updating your known hosts like this:
+
+ ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $sshport root@127.0.0.1
=item B<--> I<qemu options>
@@ -46,6 +49,32 @@ One possible use of this method is passing B<-snapshot> to avoid modifying the v
=back
+=head1 EXAMPLES
+
+Run a virtual machine stored in the image F<rootfs.ext4> (the default) with
+local port 8022 routed to port 22 of the virtual machine. The B<-snapshot>
+argument is passed to QEMU and prevents any permanent changes to
+F<rootfs.ext4>, resulting in an ephemeral run.
+
+ debvm-run -s 8022 -i rootfs.ext4 -- -snapshot
+
+=head1 FAQ
+
+=over 8
+
+=item The debvm-run console renders wrong.
+
+Make sure C<$TERM> is set to a value known inside the VM.
+You may need to install B<ncurses-term> for more definitions.
+It also helps to run C<setterm --resize> after boot and when resizing the terminal emulator.
+
+=item How can I kill debvm-run?
+
+The wrapped B<qemu> can be terminated by pressing Ctrl-a x.
+Refer to the B<qemu> manual page for more escape sequences.
+
+=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.
@@ -120,7 +149,7 @@ while getopts :gi:s:-: OPTCHAR; do
usage_error "missing argument for -$OPTARG"
;;
'?')
- usage_erro "unrecognized option -$OPTARG"
+ usage_error "unrecognized option -$OPTARG"
;;
*)
die "internal error while parsing command options, please report a bug"
@@ -157,20 +186,22 @@ 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
- mips*|ppc64el)
- KERNELLINK=vmlinux
- ;;
- *)
- KERNELLINK=vmlinuz
- ;;
-esac
-
-KERNELNAME=$(/sbin/debugfs "$IMAGE" -R "stat $KERNELLINK" | sed 's/Fast link dest: "\(.*\)"/\1/;t;d')
-INITRDNAME=$(/sbin/debugfs "$IMAGE" -R "stat initrd.img" | sed 's/Fast link dest: "\(.*\)"/\1/;t;d')
+for KERNELLINK in vmlinuz vmlinux boot/vmlinuz boot/vmlinux; do
+ KERNELNAME=$(/sbin/debugfs "$IMAGE" -R "stat $KERNELLINK" | sed 's/Fast link dest: "\(.*\)"/\1/;t;d')
+ test -n "$KERNELNAME" && break
+done
+if test "${KERNELLINK%/*}" = "$KERNELLINK"; then
+ BOOTDIR=
+else
+ BOOTDIR="${KERNELLINK%/*}/"
+fi
test -n "$KERNELNAME" || die "failed to discover kernel image"
+test "${KERNELNAME#/}" = "$KERNELNAME" && KERNELNAME="$BOOTDIR$KERNELNAME"
+
+INITRDNAME=$(/sbin/debugfs "$IMAGE" -R "stat ${BOOTDIR}initrd.img" | sed 's/Fast link dest: "\(.*\)"/\1/;t;d')
test -n "$INITRDNAME" || die "failed to discover initrd image"
+test "${INITRDNAME#/}" = "$INITRDNAME" && INITRDNAME="$BOOTDIR$INITRDNAME"
KERNEL_CMDLINE="root=LABEL=debvm rw"
NETDEV="user,id=net0"
@@ -200,7 +231,11 @@ if test "$ARCHITECTURE" = "$VMARCH"; then
;;
esac
else
+ QEMU="qemu-system-$VMARCH"
case "$VMARCH" in
+ amd64)
+ QEMU=qemu-system-x86_64
+ ;;
arm64)
QEMU=qemu-system-aarch64
set -- -machine virt -cpu max "$@"
@@ -209,23 +244,36 @@ else
QEMU=qemu-system-arm
set -- -machine virt -cpu max "$@"
;;
+ powerpc)
+ QEMU=qemu-system-ppc
+ MAX_SMP=1
+ ;;
ppc64el)
QEMU=qemu-system-ppc64
;;
+ m68k)
+ MAX_SMP=1
+ ;;
mips64el)
- QEMU="qemu-system-$VMARCH"
MAX_SMP=1
set -- -cpu 5KEc "$@"
;;
mipsel)
- QEMU="qemu-system-$VMARCH"
MAX_SMP=1
;;
- *)
- QEMU="qemu-system-$VMARCH"
+ riscv64)
+ set -- -machine virt "$@"
+ ;;
+ sparc64)
+ MAX_SMP=1
;;
esac
fi
+case "$VMARCH" in
+ amd64)
+ set -- -machine q35 "$@"
+ ;;
+esac
if test -z "$MAX_SMP" || test "$MAX_SMP" -gt 1; then
NPROC=$(nproc)
test -n "$MAX_SMP" && test "$NPROC" -gt "$MAX_SMP" && NPROC=$MAX_SMP
@@ -239,6 +287,15 @@ if test -z "$GRAPHICAL"; then
KERNEL_CMDLINE="$KERNEL_CMDLINE console=ttyS0"
;;
esac
+ if test -t 0 && test -t 1 && test -n "$TERM"; then
+ KERNEL_CMDLINE="$KERNEL_CMDLINE TERM=$TERM"
+ fi
+else
+ case "$VMARCH" in
+ amd64|i386)
+ set -- -vga virtio "$@"
+ ;;
+ esac
fi
if test -n "$SSHPORT"; then
diff --git a/bin/debvm-waitssh b/bin/debvm-waitssh
new file mode 100755
index 0000000..82eb14d
--- /dev/null
+++ b/bin/debvm-waitssh
@@ -0,0 +1,178 @@
+#!/bin/sh
+# Copyright 2023 Helmut Grohne <helmut@subdivi.de>
+# SPDX-License-Identifier: MIT
+
+: <<'POD2MAN'
+=head1 NAME
+
+debvm-waitssh - Wait for a ssh server to be reachable
+
+=head1 SYNOPSIS
+
+B<debvm-waitssh> [B<-q>] [B<-t> I<timeout>] [I<hostname>:]I<port>
+
+=head1 DESCRIPTION
+
+B<debvm-waitssh> can be used to wait for a virtual machine with exposed ssh port to be reachable on that port.
+If no hostname is given, B<127.0.0.1> is assumed. No authentication is attempted by B<debvm-waitssh>, so neither
+a username nor a key have to be supplied.
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<-t> I<timeout>, B<--timeout>=I<timeout>
+
+Set the maximum duration for waiting in seconds.
+Defaults to one minute.
+
+=item B<-q>, B<--quiet>
+
+Be quiet.
+Do not output a message when the timeout has been reached without success.
+
+=back
+
+=head1 EXIT VALUES
+
+=over 8
+
+=item B<0>
+
+The server is reachable.
+
+=item B<1>
+
+A timeout was reached before the server answered.
+
+=item B<2>
+
+Usage error.
+
+=back
+
+=head1 SEE ALSO
+
+ debvm-run(1)
+
+=cut
+POD2MAN
+
+set -u
+
+TOTALTIMEOUT=60
+SCANTIMEOUT=10
+SCANDELAY=1
+VERBOSITY=1
+
+nth_arg() {
+ shift "$1"
+ printf "%s" "$1"
+}
+
+die() {
+ echo "$*" >&2
+ exit 2
+}
+usage() {
+ die "usage: $0 [-q] [-t <timeout>] [<host>:]<port>"
+}
+usage_error() {
+ echo "error: $*" >&2
+ usage
+}
+
+opt_help() {
+ # shellcheck disable=SC2317 # not dead, called as "opt_$OPTARG"
+ usage
+}
+opt_quiet() {
+ VERBOSITY=0
+}
+opt_timeout() {
+ TOTALTIMEOUT=$1
+}
+
+while getopts :qt:-: OPTCHAR; do
+ case "$OPTCHAR" in
+ q) opt_quiet ;;
+ t) opt_timeout "$OPTARG" ;;
+ -)
+ case "$OPTARG" in
+ help|quiet)
+ "opt_$OPTARG"
+ ;;
+ timeout)
+ test "$OPTIND" -gt "$#" && usage_error "missing argument for --$OPTARG"
+ "opt_$OPTARG" "$(nth_arg "$OPTIND" "$@")"
+ OPTIND=$((OPTIND+1))
+ ;;
+ timeout=)
+ "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))"
+
+test "$#" = 1 || usage
+
+case "$1" in
+ "")
+ usage
+ ;;
+ *:*)
+ HOST=${1%:*}
+ PORT=${1##*:}
+ ;;
+ *)
+ HOST=127.0.0.1
+ PORT=$1
+ ;;
+esac
+
+case "$HOST" in *@*)
+ die "$0: hostname '$HOST' must not contain the '@' character. No username is required."
+;; esac
+
+# Guard against strings containing anything but digits, strings starting with
+# zero and empty strings as the port number.
+#
+# We cannot use [!0-9] because that matches on any character (or possibly
+# multi-character collation element) that sorts in between 0 and 9.
+case "$PORT" in *[!0123456789]*|0?*|""|??????*)
+ die "$0: port '$PORT' is not an integer between 1 and 65535"
+;; esac
+if test "$PORT" -lt 1 -o "$PORT" -gt 65535; then
+ die "$0: port '$PORT' is not an integer between 1 and 65535"
+fi
+
+now=$(date +%s)
+deadline=$((now + TOTALTIMEOUT))
+while test "$now" -lt "$deadline"; do
+ start=$now
+ ssh-keyscan -t rsa -T "$SCANTIMEOUT" -p "$PORT" "$HOST" >/dev/null 2>&1 && exit 0
+ now=$(date +%s)
+ if test "$((now - start))" -lt "$SCANTIMEOUT"; then
+ sleep "$SCANDELAY"
+ now=$(date +%s)
+ fi
+done
+if [ "$VERBOSITY" -ge 1 ]; then
+ echo "$0: timeout reached trying to contact $HOST:$PORT after waiting $TOTALTIMEOUT seconds." >&2
+fi
+exit 1
+
diff --git a/share/customize-autologin.sh b/share/customize-autologin.sh
new file mode 100755
index 0000000..4340650
--- /dev/null
+++ b/share/customize-autologin.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+# Copyright 2022 Helmut Grohne <helmut@subdivi.de>
+# SPDX-License-Identifier: MIT
+#
+# This is a mmdebstrap customize hook that configures automatic root login on a
+# serial console. It also parses the TERM kernel cmdline and passes it as
+# TERM to agetty.
+
+set -eu
+
+TARGET=$1
+
+UNIT=serial-getty@.service
+
+mkdir "$TARGET/etc/systemd/system/$UNIT.d"
+
+(
+ echo '[Service]'
+ printf '%s\n' 'ExecStartPre=/bin/sed -n -e "s/^\\(.* \\)\\?\\(TERM=[^ ]*\\).*/\\2/w/run/debvmterm" /proc/cmdline'
+ echo 'EnvironmentFile=-/run/debvmterm'
+ echo 'ExecStart='
+ sed -n 's,^ExecStart=-/sbin/agetty ,&-a root ,p' "$TARGET/lib/systemd/system/$UNIT"
+) > "$TARGET/etc/systemd/system/$UNIT.d/autologin.conf"
diff --git a/share/customize-dpkgavailable.sh b/share/customize-dpkgavailable.sh
new file mode 100755
index 0000000..f35b81f
--- /dev/null
+++ b/share/customize-dpkgavailable.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+# Copyright 2022 Helmut Grohne <helmut@subdivi.de>
+# SPDX-License-Identifier: MIT
+#
+# This is a mmdebstrap customize hook that initializes dpkg's available
+# database from an updated apt package list cache.
+#
+# Without the available database, dpkg --set-selections won't work.
+
+set -eu
+
+APT_CONFIG=$MMDEBSTRAP_APT_CONFIG apt-cache dumpavail | dpkg --root "$1" --update-avail
diff --git a/share/customize-kernel.sh b/share/customize-kernel.sh
new file mode 100755
index 0000000..79a7449
--- /dev/null
+++ b/share/customize-kernel.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+#
+# Copyright 2022 Helmut Grohne <helmut@subdivi.de>
+# SPDX-License-Identifier: MIT
+#
+# This is a mmdebstrap customize hook that installs a kernel image. The name
+# of the kernel image depends on the architecture, derivative and release.
+
+set -eu
+
+TARGET="$1"
+
+if dpkg-query --root="$TARGET" --showformat='${db:Status-Status}\n' --show 'linux-image-*' 2>/dev/null | grep -q '^installed$'; then
+ exit 0
+fi
+
+ARCHITECTURE=$(cat "$TARGET/var/lib/dpkg/arch")
+
+KERNEL_ARCH="$ARCHITECTURE"
+case "$ARCHITECTURE" in
+ armhf)
+ KERNEL_ARCH=armmp
+ ;;
+ hppa)
+ KERNEL_ARCH=parisc
+ ;;
+ i386)
+ KERNEL_ARCH=686-pae
+ ;;
+ mips64el)
+ KERNEL_ARCH=5kc-malta
+ ;;
+ mipsel)
+ KERNEL_ARCH=4kc-malta
+ ;;
+ ppc64)
+ KERNEL_ARCH=powerpc64
+ ;;
+ ppc64el)
+ KERNEL_ARCH=powerpc64le
+ ;;
+esac
+
+export APT_CONFIG="$MMDEBSTRAP_APT_CONFIG"
+
+if test "${MMDEBSTRAP_MODE:-}" = chrootless; then
+ set -- \
+ -oDPkg::Options::=--force-not-root \
+ -oDPkg::Options::=--force-script-chrootless \
+ -oDPkg::Options::=--root="$TARGET" \
+ -oDPkg::Options::=--log="$TARGET/var/log/dpkg.log"
+else
+ set -- -oDPkg::Chroot-Directory="$TARGET"
+fi
+
+# On some derivatives such as Ubuntu, linux image does not depend on an initramfs.
+apt-get --yes satisfy "$@" "linux-image-cloud-$KERNEL_ARCH | linux-image-$KERNEL_ARCH | linux-image-generic" "initramfs-tools | linux-initramfs-tool"
diff --git a/share/customize-networkd.sh b/share/customize-networkd.sh
new file mode 100755
index 0000000..c89aae2
--- /dev/null
+++ b/share/customize-networkd.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+# Copyright 2022 Helmut Grohne <helmut@subdivi.de>
+# SPDX-License-Identifier: MIT
+#
+# This is a mmdebstrap customize hook that enables and configures
+# systemd-networkd on various Debian releases.
+
+set -eu
+
+TARGET=$1
+
+SYSTEMD_VERSION=$(dpkg-query --root "$TARGET" -f '${Version}' -W systemd)
+
+if test "${MMDEBSTRAP_MODE:-}" = chrootless; then
+ systemctl --root "$TARGET" enable systemd-networkd.service
+else
+ chroot "$TARGET" systemctl enable systemd-networkd.service
+fi
+
+{
+ echo '[Match]'
+ echo 'Name=en*'
+ if dpkg --compare-versions "$SYSTEMD_VERSION" lt 220-7~; then
+ echo 'Name=eth*'
+ fi
+
+ echo '[Network]'
+ echo 'DHCP=yes'
+
+ if dpkg --compare-versions "$SYSTEMD_VERSION" lt 249; then
+ # This anchor is included by default since bullseye. Fails DNSSEC
+ # validation when missing.
+ echo 'DNSSECNegativeTrustAnchors=home.arpa'
+ fi
+ echo '[DHCP]'
+ echo 'UseDomains=yes'
+} >"$TARGET/etc/systemd/network/20-wired.network"
diff --git a/share/customize-resolved.sh b/share/customize-resolved.sh
new file mode 100755
index 0000000..e8fe248
--- /dev/null
+++ b/share/customize-resolved.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+# Copyright 2022 Helmut Grohne <helmut@subdivi.de>
+# SPDX-License-Identifier: MIT
+#
+# This is a mmdebstrap customize hook that enables systemd-resolved on various
+# Debian releases.
+
+set -eu
+
+TARGET=$1
+
+LIBNSS_RESOLVE_VERSION=$(dpkg-query --root "$TARGET" -f '${Version}' -W libnss-resolve 2>/dev/null) || :
+
+if dpkg --compare-versions "$LIBNSS_RESOLVE_VERSION" lt 251.3-2~exp1; then
+ if test "${MMDEBSTRAP_MODE:-}" = chrootless; then
+ systemctl --root "$TARGET" enable systemd-resolved.service
+ else
+ chroot "$TARGET" systemctl enable systemd-resolved.service
+ fi
+
+ if test -z "$LIBNSS_RESOLVE_VERSION"; then
+ ln -fs ../run/systemd/resolve/resolv.conf "$TARGET/etc/resolv.conf"
+ else
+ ln -fs ../run/systemd/resolve/stub-resolv.conf "$TARGET/etc/resolv.conf"
+ fi
+fi
diff --git a/tests/create-and-run.sh b/tests/create-and-run.sh
index aafd41d..f978052 100755
--- a/tests/create-and-run.sh
+++ b/tests/create-and-run.sh
@@ -1,36 +1,31 @@
#!/bin/sh
-# shellcheck disable=SC2086
-
if test "$#" -ne 2; then
- echo "$(basename $0) takes two positional arguments. architecture and release" 1>&2
+ echo "$(basename "$0") takes two positional arguments: architecture and release" 1>&2
exit 1
fi
+ARCHITECTURE=$1
+RELEASE=$2
+SSH_KEYPATH=ssh_id
+IMAGE=test.ext4
+
+set -eux
-set -ex
+. "$(dirname "$0")/test_common.sh"
cleanup() {
- rm -f ssh_id ssh_id.pub test.ext4
+ rm -f "$SSH_KEYPATH" "$SSH_KEYPATH.pub" "$IMAGE"
}
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"
+ssh-keygen -f "$SSH_KEYPATH" -N ''
+debvm-create -k "$SSH_KEYPATH.pub" -o "$IMAGE" -a "$ARCHITECTURE" -r "$RELEASE"
-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
+SSH_PORT=2222
+timeout 240s debvm-run -s "$SSH_PORT" -i "$IMAGE" &
+set -- localhost
+test "$RELEASE" = jessie && set -- -o PubkeyAcceptedKeyTypes=+ssh-rsa "$@"
+debvm-waitssh -t 150 "$SSH_PORT"
+run_ssh "$@" poweroff
wait
diff --git a/tests/dist-upgrades.sh b/tests/dist-upgrades.sh
new file mode 100755
index 0000000..b90f17b
--- /dev/null
+++ b/tests/dist-upgrades.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+# Copyright 2022 Jochen Sprickerhof <debvm@jochen.sprickerhof.de>
+# SPDX-License-Identifier: MIT
+#
+# apt install e2fsprogs genext2fs mmdebstrap openssh-client qemu-kvm
+
+set -x
+
+. "$(dirname "$0")/test_common.sh"
+
+SSH_KEYPATH=ssh_id
+
+cleanup() {
+ rm -f "$SSH_KEYPATH" "$SSH_KEYPATH.pub" upgrade
+}
+
+trap cleanup EXIT INT TERM QUIT
+
+cat > upgrade << "EOF"
+#!/bin/sh
+
+set -ex
+
+export DEBIAN_FRONTEND=noninteractive
+
+sed -i "s/\([^ ]*\) \([^ ]*\) [^ ]* \(.*\)/\1 \2 $1 \3/" /etc/apt/sources.list
+apt update
+apt dist-upgrade -y
+
+test "$1" = stretch && apt install libnss-resolve
+
+apt autoremove --purge -y
+apt clean
+poweroff
+EOF
+
+chmod +x upgrade
+ssh-keygen -f "$SSH_KEYPATH" -N ''
+
+debvm-create --sshkey="$SSH_KEYPATH.pub" -r jessie --size=3G -- --customize-hook="copy-in upgrade /usr/local/bin"
+
+SSH_PORT=2222
+for RELEASE in stretch buster bullseye bookworm sid; do
+ timeout 15m debvm-run -s "$SSH_PORT" &
+ set -- localhost
+ test "$RELEASE" = stretch && set -- -o PubkeyAcceptedKeyTypes=+ssh-rsa "$@"
+ debvm-waitssh -t 150 "$SSH_PORT"
+ run_ssh "$@" "upgrade $RELEASE"
+ wait
+done
+
+timeout 5m debvm-run -s "$SSH_PORT" &
+debvm-waitssh -t 150 "$SSH_PORT"
+run_ssh localhost poweroff
+wait
diff --git a/tests/test_common.sh b/tests/test_common.sh
new file mode 100644
index 0000000..cba9693
--- /dev/null
+++ b/tests/test_common.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+run_ssh() {
+ test -n "${SSH_KEYPATH:-}" && set -- -i "$SSH_KEYPATH" "$@"
+ test -n "${SSH_PORT:-}" && set -- -p "$SSH_PORT" "$@"
+ ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -l root "$@"
+}
diff --git a/useraddhook/customize.sh b/useraddhook/customize.sh
new file mode 100755
index 0000000..3bba263
--- /dev/null
+++ b/useraddhook/customize.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# Copyright 2023 Johannes Schauer Marin Rodrigues <josch@debian.org>
+# SPDX-License-Identifier: MIT
+#
+# Add a non-root user, add them to the sudo group and use the same authorized
+# ssh keys as the root user.
+#
+# - the new user is called "user"
+# - no password required for login
+# - requires the passwd and coreutils packages installed inside the chroot
+# - adds the new user to the sudo group if it exists
+# - ~/.ssh/authorized_keys files is copied from root user if it exists
+#
+# Example usage:
+#
+# $ debvm-create -k ~/.ssh/id_rsa.pub -- --hook-dir=.../useraddhook --include sudo
+# $ debvm-run -s 8022
+# $ ssh -l user -p 8022 127.0.0.1 whoami
+# user
+# $ ssh -l user -p 8022 127.0.0.1 sudo whoami
+# root
+#
+
+set -eu
+
+chroot "$1" useradd --home-dir /home/user --create-home --shell /bin/bash user
+chroot "$1" passwd --delete user
+if chroot "$1" getent group sudo >/dev/null; then
+ chroot "$1" usermod --append --groups sudo user
+fi
+if [ -e "$1"/root/.ssh/authorized_keys ]; then
+ chroot "$1" install -o user -g user -m 700 -d /home/user/.ssh
+ chroot "$1" install -o user -g user -t /home/user/.ssh /root/.ssh/authorized_keys
+fi