#!/usr/bin/env bash
# Fetch the kernel modules matching the Nucleic Linux base's **external** kernel and pack them into a
# tarball the bootstrap bakes into the rootfs (docs/LINUX_VM.md §base build).
#
#   scripts/fetch-kernel-modules.sh <IMAGE_MANIFEST_URL> <OUT_DIR>
#   # e.g. …/ubuntu-26.04-server-cloudimg-arm64.manifest  /tmp/payload
#
# Why: the base boots an external `vmlinuz` (unwrapped from EFI-zboot) against Ubuntu's **minimal cloud
# rootfs, which ships NO /lib/modules**. On 24.04 the essential virtio drivers (vsock, virtiofs, gpu…)
# were built into the kernel, so this didn't matter. Modern Ubuntu kernels (≥24.10, incl. 26.04's 7.0)
# ship them as MODULES — so without them the in-guest agent can't bind AF_VSOCK and the desktop has no
# virtio_gpu. We fetch the `linux-modules{,-extra}-<ver>-generic` debs whose version the cloud image's
# manifest pins (so it tracks point-release drift), extract `/lib/modules/<ver>`, and emit
# `<OUT_DIR>/modules.tar.gz` (+ `<OUT_DIR>/kernel-release` with the `uname -r`). The bootstrap `/init`
# untars it into the rootfs so the drivers are present from the very first boot.
#
# Only the CORE `linux-modules-<ver>-generic` is needed — it already contains vsock, virtio_gpu and
# virtiofs (verified); `-extra` is the long tail we don't need, so we skip its ~300 MB. The deb ships no
# `modules.dep`/`.alias` (those are generated by depmod in the postinst) and its modules live under
# merged-usr `usr/lib/modules/<ver>`; the guest regenerates the dep/alias at boot (nucleic-modsetup).
#
# Needs: curl, ar, tar, gzip (Ubuntu's linux-modules data member is a plain uncompressed `data.tar`, so
# no zstd is required here — the individual .ko.zst are decompressed by the guest's module loader).
set -euo pipefail

MANIFEST_URL="${1:?usage: fetch-kernel-modules.sh <manifest-url> <out-dir>}"
OUT_DIR="${2:?usage: fetch-kernel-modules.sh <manifest-url> <out-dir>}"
mkdir -p "$OUT_DIR"

work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT

echo "==> reading kernel version from manifest"
manifest="$work/manifest"
curl -fSL "$MANIFEST_URL" -o "$manifest"
# Line: "linux-modules-<ver>-generic\t<pkgver>". Take the first.
line="$(grep -E '^linux-modules-[0-9].*-generic[[:space:]]' "$manifest" | head -1 || true)"
[ -n "$line" ] || { echo "no linux-modules-<ver>-generic in manifest" >&2; exit 2; }
pkgname="$(printf '%s' "$line" | awk '{print $1}')"          # linux-modules-7.0.0-27-generic
pkgver="$(printf '%s' "$line" | awk '{print $2}')"           # 7.0.0-27.27
kver="${pkgname#linux-modules-}"                             # 7.0.0-27-generic
echo "    kernel=$kver pkgver=$pkgver"
printf '%s' "$kver" > "$OUT_DIR/kernel-release"

POOL="http://ports.ubuntu.com/ubuntu-ports/pool/main/l/linux"
deb="linux-modules-${kver}_${pkgver}_arm64.deb"

extract_root="$work/root"
mkdir -p "$extract_root"
echo "==> fetching $deb"
curl -fSL "$POOL/$deb" -o "$work/pkg.deb" || { echo "required modules package $deb not found" >&2; exit 3; }
( cd "$work" && ar x pkg.deb )
echo "==> extracting data payload"
# The data member may be plain `data.tar` (Ubuntu's linux-modules) or compressed. Handle all.
if   [ -f "$work/data.tar" ];     then tar -C "$extract_root" -xf  "$work/data.tar"
elif [ -f "$work/data.tar.zst" ]; then ( cd "$extract_root" && zstd -dc "$work/data.tar.zst" | tar -x )
elif [ -f "$work/data.tar.xz" ];  then tar -C "$extract_root" -xJf "$work/data.tar.xz"
elif [ -f "$work/data.tar.gz" ];  then tar -C "$extract_root" -xzf "$work/data.tar.gz"
else echo "unexpected .deb layout (no data.tar*)" >&2; exit 4
fi

# Ubuntu 26.04 is merged-usr: modules live at usr/lib/modules/<ver>.
moddir=""
for cand in "usr/lib/modules/$kver" "lib/modules/$kver"; do
    [ -d "$extract_root/$cand" ] && { moddir="$cand"; break; }
done
[ -n "$moddir" ] || { echo "no modules dir for $kver in the deb" >&2; exit 5; }

echo "==> packing modules.tar.gz ($moddir)"
# Pack with the merged-usr path preserved so it lands at /usr/lib/modules/<ver> in the rootfs.
# The tarball is unpacked as root over a live rootfs, so keep it clean: no macOS AppleDouble
# (`._*`) sidecars or xattrs (COPYFILE_DISABLE + --no-xattrs on bsdtar), and root-owned entries
# instead of the packing user's uid (bsdtar: --uid/--gid; GNU tar: --owner/--group).
if tar --version 2>/dev/null | grep -q "GNU tar"; then
    tar -C "$extract_root" --owner=0 --group=0 --no-xattrs -czf "$OUT_DIR/modules.tar.gz" "$moddir"
else
    COPYFILE_DISABLE=1 tar -C "$extract_root" --uid 0 --gid 0 --no-xattrs \
        -czf "$OUT_DIR/modules.tar.gz" "$moddir"
fi
echo "    wrote $OUT_DIR/modules.tar.gz ($(wc -c < "$OUT_DIR/modules.tar.gz") bytes) for $kver"
