#!/usr/bin/env bash
set -euo pipefail

repo=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
work=$(dirname "$repo")/kernel
src=$work/linux
fwdir=$work/firmware
initdir=$work/initramfs
cpiolist=$work/initramfs.list

upstream=https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git
esp=/boot
subdir=EFI/cabletest
loader='\EFI\cabletest\cabletest.efi'
label=cabletest

# Releases are signed by one of these two and nothing else is accepted.
keys=(
	647F28654894E3BD457199BE38DBBDC86092693E
	ABAF11C65A2970B130ABE3C479BE3E4300411886
)

# Nothing here can be loaded from disk at runtime, so every blob the drivers
# ask for is linked into the image. The EDID is ours; the rest come from the
# distro's linux-firmware.
edid=edid/waveshare-1024x600.bin
distrofw=(
	intel/ice/ddp/ice.pkg
	i915/adlp_dmc.bin
	i915/adlp_guc_70.bin
	i915/tgl_huc.bin
)

cmdline="drm.edid_firmware=HDMI-A-1:$edid video=HDMI-A-1:e"

# Every setting the appliance depends on, re-asserted after the stored config
# is loaded so a renamed or newly defaulted symbol cannot quietly drop one.
wanty=(
	EFI_STUB BLK_DEV_INITRD INITRAMFS_COMPRESSION_NONE CMDLINE_BOOL
	PACKET INET DEVTMPFS PROC_FS SYSFS TMPFS
	ICE ICE_HWTS I40E PTP_1588_CLOCK
	DRM_I915 DRM_FBDEV_EMULATION DRM_LOAD_EDID_FIRMWARE DRM_PANIC
	FB_DEVICE FRAMEBUFFER_CONSOLE VT_CONSOLE
	USB_XHCI_PCI USB_HID HID_MULTITOUCH INPUT_EVDEV
	CPU_FREQ_GOV_PERFORMANCE X86_INTEL_PSTATE
)
wantn=(MODULES BLOCK IGC)

say() { printf '\n\033[36m== %s\033[0m\n' "$*"; }
die() { printf '\033[31merror: %s\033[0m\n' "$*" >&2; exit 1; }

say "sudo is needed at the end, asking now so the build runs unattended"
sudo -v

say "kernel source"
if [ ! -d "$src/.git" ]; then
	echo "cloning $upstream (one time, several GB)"
	git clone "$upstream" "$src"
fi
git -C "$src" fetch --tags --prune origin
# sed rather than head: head closes the pipe early, which under pipefail turns
# the upstream SIGPIPE into a failed pipeline.
tag=$(git -C "$src" tag -l 'v[0-9]*' --sort=-v:refname | grep -vE -- '-(rc|tree)' | sed -n 1p)
[ -n "$tag" ] || die "no release tag found"

status=$(git -C "$src" verify-tag --raw "$tag" 2>&1) || die "$tag is not signed"
signer=""
for key in "${keys[@]}"; do
	case "$status" in *"VALIDSIG $key"*) signer=$key ;; esac
done
[ -n "$signer" ] || die "$tag is not signed by a pinned kernel.org key"
echo "$tag verified against $signer"

git -C "$src" checkout -q --detach "$tag"

say "go binary"
mkdir -p "$initdir"
( cd "$repo" && CGO_ENABLED=0 go build -trimpath -o "$initdir/init" . )
go version -m "$initdir/init" | grep -E 'vcs\.(revision|modified)' ||
	die "no VCS stamp; build from a git checkout so the image is traceable"

say "firmware"
mkdir -p "$fwdir/$(dirname "$edid")"
cp "$repo/kernel/$edid" "$fwdir/$edid"
for f in "${distrofw[@]}"; do
	mkdir -p "$fwdir/$(dirname "$f")"
	if [ -e "/lib/firmware/$f" ]; then
		cp "/lib/firmware/$f" "$fwdir/$f"
	elif [ -e "/lib/firmware/$f.zst" ]; then
		zstd -qdf -o "$fwdir/$f" "/lib/firmware/$f.zst"
	else
		die "/lib/firmware/$f is missing; install linux-firmware"
	fi
done

# The binary is embedded by the kernel link, so it has to exist before make.
# /dev/console must be in the cpio: the kernel opens it for init's stdio before
# anything has had a chance to mount devtmpfs over /dev.
say "initramfs"
cat > "$cpiolist" <<EOF
dir /dev 0755 0 0
nod /dev/console 0600 0 0 c 5 1
dir /proc 0755 0 0
dir /sys 0755 0 0
file /init $initdir/init 0755 0 0
EOF
cat "$cpiolist"

say "configure"
cp "$repo/kernel/config" "$src/.config"
( cd "$src" && scripts/config \
	--disable MODULES \
	--enable EXPERT --disable BLOCK \
	--enable I40E --disable IGC \
	--enable DRM_LOAD_EDID_FIRMWARE \
	--set-str EXTRA_FIRMWARE "$edid ${distrofw[*]}" \
	--set-str EXTRA_FIRMWARE_DIR "$fwdir" \
	--set-str INITRAMFS_SOURCE "$cpiolist" \
	--disable INITRAMFS_COMPRESSION_GZIP --enable INITRAMFS_COMPRESSION_NONE \
	--enable CMDLINE_BOOL --set-str CMDLINE "$cmdline" \
	--disable DEBUG_INFO_DWARF5 --enable DEBUG_INFO_NONE \
	--disable LOCALVERSION_AUTO --set-str LOCALVERSION "-cabletest" )
make -C "$src" olddefconfig

for s in "${wanty[@]}"; do
	grep -qx "CONFIG_$s=y" "$src/.config" || die "CONFIG_$s is not y after olddefconfig"
done
for s in "${wantn[@]}"; do
	grep -qx "# CONFIG_$s is not set" "$src/.config" || die "CONFIG_$s survived olddefconfig"
done
echo "${#wanty[@]} required and ${#wantn[@]} forbidden symbols check out"

say "build $tag"
make -C "$src" -j"$(nproc)"

say "package"
image=$src/arch/x86/boot/bzImage
cpio -itv < "$src/usr/initramfs_data.cpio"
[ -z "$(find "$src" -name '*.ko' -print -quit)" ] || die "modules were built"
ls -l "$image"

say "install"
[ "$(findmnt -no FSTYPE "$esp")" = vfat ] || die "$esp is not a mounted ESP"
espdev=$(findmnt -no SOURCE "$esp")
disk=/dev/$(lsblk -no PKNAME "$espdev")
part=$(cat "/sys/class/block/$(basename "$espdev")/partition")

sudo mkdir -p "$esp/$subdir"
sudo cp "$image" "$esp/$subdir/cabletest.efi"
sync
sudo cmp "$image" "$esp/$subdir/cabletest.efi"

bootnum=$(efibootmgr | awk -v l="$label" '$1 ~ /^Boot[0-9A-Fa-f]{4}/ && $2 == l {print substr($1, 5, 4)}')
if [ -z "$bootnum" ]; then
	# A new entry is prepended to BootOrder, which would silently take over the
	# default boot, so the previous order is put back with ours on the end.
	order=$(efibootmgr | sed -n 's/^BootOrder: //p')
	sudo efibootmgr --create --disk "$disk" --part "$part" \
		--loader "$loader" --label "$label" > /dev/null
	bootnum=$(efibootmgr | awk -v l="$label" '$1 ~ /^Boot[0-9A-Fa-f]{4}/ && $2 == l {print substr($1, 5, 4)}')
	sudo efibootmgr -o "${order:+$order,}$bootnum" > /dev/null
fi
sudo efibootmgr -n "$bootnum" > /dev/null
efibootmgr

say "$tag installed as Boot$bootnum, armed for the next reboot only"
