81 lines
4.6 KiB
Bash
Executable File
81 lines
4.6 KiB
Bash
Executable File
#!/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"
|