#!/bin/sh # Copyright 2022 Helmut Grohne # SPDX-License-Identifier: MIT # shellcheck disable=SC2016 # Intentional quoting technique set -u ARCHITECTURE=$(dpkg --print-architecture) IMAGE=rootfs.ext2 INCLUDE_PACKAGES=init MIRROR="http://deb.debian.org/debian" SIZE=$((1024*1024*1024)) 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]" } 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 getopts :a:h:k:m:o:p:r:s:-: 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_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 ;; :) 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 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 # 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"' "$@" fi # construct mmdebstrap options as $@: set -- \ --verbose \ --variant=apt \ --format=ext2 \ "--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"' \ "--customize-hook=echo 127.0.0.1 localhost $VMNAME >"'"$1/etc/hosts"' \ "$@" # 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"' \ "$@" # add ssh key for root if test -n "$SSHKEY"; then set -- \ '--customize-hook=mkdir -p "$1/root/.ssh"' \ "--customize-hook=upload $SSHKEY /root/.ssh/authorized_keys" \ "$@" fi set -- --skip=cleanup/apt "$@" # We need /var/lib/dpkg/available for dpkg --set-selections to work. set -- '--customize-hook=chroot "$1" apt-cache dumpavail | chroot "$1" dpkg --update-avail' "$@" 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"' "$@" set -- --hook-dir=/usr/share/mmdebstrap/hooks/jessie-or-older "$@" fi if test "$DEBVER" -ge 12; then # Avoid the usrmerge package set -- --hook-dir=/usr/share/mmdebstrap/hooks/merged-usr "$@" fi # suite target mirror set -- "$@" "$SUITE" "$IMAGE" "deb $MIRROR $SUITE main" set -ex mmdebstrap "$@" 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 "$IMAGE"