#!/bin/sh
# Copyright 2022 Helmut Grohne <helmut@subdivi.de>
# 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] [-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 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
		;;
		:)
			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/lists "$@"

# 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"