#!/bin/sh
# arctic-mkinitramfs - build /boot/initramfs-<kernel>.img
#
#   arctic-mkinitramfs             the newest kernel under /lib/modules
#   arctic-mkinitramfs <kver>      a specific one
#
# Arctic's kernel builds its storage, filesystem and USB/PCI controller
# drivers directly in, not as modules (see build/build-kernel.sh) - the
# point of that was so the live ISO's own initramfs never needs to load a
# module to find the medium, but it also means a plain single-disk
# ext4/btrfs/xfs install can boot with *no* initramfs at all. This exists
# for the case that isn't true: an encrypted root, which needs a passphrase
# prompt before anything else can happen, and needs dm-crypt - one of the
# few things Arctic's kernel does build as a module rather than in.
#
# alpm calls this after every kernel install and after installing a driver
# that touches the boot path (see alpm's tx handling and
# ports/nonfree/nvidia-drivers/install); arctic-strap calls it once at the
# end of a fresh install. All of those call sites already guard with
# `have arctic-mkinitramfs`, so before this existed they silently did
# nothing - which is why a from-scratch encrypted install could not
# actually boot.
# shellcheck shell=sh disable=SC2039

set -u

KVER=${1:-}
if [ -z "$KVER" ]; then
	# The call sites never pass one: they run right after installing (or
	# reinstalling) a kernel package, sometimes from inside a chroot where
	# `uname -r` would report the *host's* running kernel, not the target's.
	# The most-recently-modified /lib/modules entry is the one that was just
	# installed, which is what every caller actually means.
	KVER=$(ls -t /lib/modules 2>/dev/null | head -1)
fi
MODDIR=/lib/modules/$KVER
[ -n "$KVER" ] && [ -d "$MODDIR" ] || {
	echo "arctic-mkinitramfs: no /lib/modules/<version> to build for" >&2
	exit 1
}

OUT=/boot/initramfs-$KVER.img
WORK=$(mktemp -d /tmp/arctic-mkinitramfs.XXXXXX) || exit 1
trap 'rm -rf "$WORK"' EXIT INT TERM

mkdir -p "$WORK/bin" "$WORK/lib" "$WORK/dev" "$WORK/proc" "$WORK/sys" \
	"$WORK/run" "$WORK/newroot" "$WORK/lib/modules/$KVER"

# Copy a dynamically linked binary's shared libraries in alongside it, using
# Arctic's own ldd (a wrapper around the real loader's --list, since there is
# no bash here for the GNU ldd script). A statically linked binary just
# prints "not a dynamic executable" and this loop does nothing extra, which
# is the correct outcome either way.
copylibs() {
	ldd "$1" 2>/dev/null | sed 's/^[[:space:]]*//' | while read -r a b c _; do
		if [ "$b" = "=>" ]; then
			p=$c
		elif [ "${a#/}" != "$a" ]; then
			# No "=>" on this line: it IS the path (the dynamic linker
			# itself, printed on its own line by --list).
			p=$a
		else
			continue
		fi
		[ -n "$p" ] && [ -f "$p" ] || continue
		mkdir -p "$WORK$(dirname "$p")"
		cp -f "$p" "$WORK$p" 2>/dev/null || :
	done
}

bb=$(command -v busybox) || { echo "arctic-mkinitramfs: no busybox found" >&2; exit 1; }
cp -f "$bb" "$WORK/bin/busybox"
copylibs "$bb"
for a in sh mount umount switch_root mkdir sleep echo cat ls blkid \
	mknod chroot printf test grep sed cut sync modprobe insmod depmod \
	readlink basename dirname; do
	ln -sf busybox "$WORK/bin/$a"
done

# Only bundle cryptsetup (and dm-crypt/essiv - the two pieces of LUKS
# support Arctic's kernel config leaves as modules instead of building in;
# everything else it needs - device-mapper itself, AES, CBC, XTS, SHA256 -
# is already compiled directly into the kernel) if something actually asked
# for it. crypttab with a real (non-comment) line is arctic-strap's own
# signal that the target has an encrypted volume.
NEED_LUKS=0
if [ -s /etc/crypttab ] && grep -qv '^[[:space:]]*#\|^[[:space:]]*$' /etc/crypttab 2>/dev/null; then
	if cs=$(command -v cryptsetup); then
		NEED_LUKS=1
		cp -f "$cs" "$WORK/bin/cryptsetup"
		copylibs "$cs"
		# Small (~5 MiB): every device-mapper and crypto module, rather than
		# guessing which exact IV/mode template a given LUKS header wants.
		find "$MODDIR" \( -path '*/kernel/crypto/*.ko' -o -path '*/kernel/drivers/md/dm-*.ko' \) 2>/dev/null \
		| while read -r ko; do
			rel=${ko#"$MODDIR/"}
			mkdir -p "$WORK/lib/modules/$KVER/$(dirname "$rel")"
			cp -f "$ko" "$WORK/lib/modules/$KVER/$rel"
		done
	else
		echo "arctic-mkinitramfs: warning: /etc/crypttab exists but cryptsetup is not installed" >&2
	fi
fi
cp -f "$MODDIR/modules.order" "$MODDIR/modules.builtin" \
	"$WORK/lib/modules/$KVER/" 2>/dev/null || :
depmod -b "$WORK" "$KVER" >/dev/null 2>&1 || :

cat >"$WORK/init" <<INIT
#!/bin/busybox sh
# Arctic Linux - installed-system early userspace. Built by arctic-mkinitramfs.
PATH=/bin
export PATH
/bin/busybox --install -s /bin 2>/dev/null

mount -t proc proc /proc
mount -t sysfs sys /sys
mount -t devtmpfs dev /dev 2>/dev/null || mount -t tmpfs dev /dev

R=\$(printf '\033[0m'); TEAL=\$(printf '\033[38;5;44m'); RED=\$(printf '\033[38;5;203m')
say()  { printf '%s::%s %s\n' "\$TEAL" "\$R" "\$*"; }
oops() {
	printf '\n%s!!%s %s\n\n' "\$RED" "\$R" "\$*"
	printf 'Dropping to a shell. cat /proc/cmdline, ls /dev, mount by hand.\n\n'
	exec sh
}

ROOT="" ROOTFSTYPE="" ROOTFLAGS="ro" LUKSSPEC=""
for a in \$(cat /proc/cmdline); do
	case "\$a" in
	root=*)       ROOT=\${a#root=} ;;
	rootfstype=*) ROOTFSTYPE=\${a#rootfstype=} ;;
	rootflags=*)  ROOTFLAGS=\${a#rootflags=} ;;
	rd.luks.uuid=*|cryptdevice=*) LUKSSPEC=\$a ;;
	esac
done
[ -n "\$ROOT" ] || oops "no root= on the kernel command line"

if [ -n "\$LUKSSPEC" ]; then
	say "encrypted root - loading dm-crypt"
	modprobe dm-crypt 2>/dev/null || :
	modprobe essiv 2>/dev/null || :
	case "\$LUKSSPEC" in
	rd.luks.uuid=*) uuid=\${LUKSSPEC#rd.luks.uuid=} ;;
	cryptdevice=*)  uuid=\$(printf '%s' "\${LUKSSPEC#cryptdevice=}" | cut -d: -f1 | sed 's/^UUID=//') ;;
	esac
	src=/dev/disk/by-uuid/\$uuid
	n=0
	while [ "\$n" -lt 30 ] && [ ! -e "\$src" ]; do sleep 1; n=\$((n+1)); done
	[ -e "\$src" ] || oops "cannot find the encrypted device (uuid \$uuid)"
	tries=0
	while [ "\$tries" -lt 5 ] && [ ! -e /dev/mapper/cryptroot ]; do
		say "passphrase for the encrypted root:"
		cryptsetup open "\$src" cryptroot && break
		tries=\$((tries+1))
	done
	[ -e /dev/mapper/cryptroot ] || oops "could not unlock the encrypted root"
	ROOT=/dev/mapper/cryptroot
fi

say "mounting root (\$ROOT)"
n=0
while [ "\$n" -lt 20 ] && [ ! -e "\$ROOT" ]; do sleep 1; n=\$((n+1)); done
[ -e "\$ROOT" ] || oops "root device \$ROOT never appeared"
mount \${ROOTFSTYPE:+-t "\$ROOTFSTYPE"} -o "\$ROOTFLAGS" "\$ROOT" /newroot \\
	|| oops "could not mount \$ROOT"

mkdir -p /newroot/dev
mount --move /dev /newroot/dev 2>/dev/null || :
umount /sys 2>/dev/null || :
umount /proc 2>/dev/null || :
exec switch_root /newroot /sbin/init
INIT
chmod 755 "$WORK/init"

mknod -m 600 "$WORK/dev/console" c 5 1 2>/dev/null || :
mknod -m 666 "$WORK/dev/null" c 1 3 2>/dev/null || :

mkdir -p "$(dirname "$OUT")"
( cd "$WORK" && find . -print0 | cpio --null -o -H newc 2>/dev/null | xz -T0 --check=crc32 -9 ) >"$OUT" \
	|| { echo "arctic-mkinitramfs: failed to write $OUT" >&2; exit 1; }
printf 'arctic-mkinitramfs: wrote %s (%s)%s\n' "$OUT" "$(du -h "$OUT" | cut -f1)" \
	"$([ "$NEED_LUKS" = 1 ] && printf ', with LUKS unlock support' || :)"
