# Kernel catalog

The kernel catalog ships pre-built Firecracker-ready kernel bundles so users
don't have to compile anything. The catalog is embedded into the banger
binary and updated each release.

End-user flow:

```bash
banger kernel list --available       # browse the catalog
banger kernel pull generic-6.12      # download a bundle (no sudo, no make)
banger image register --name myimg --rootfs … --kernel-ref generic-6.12
```

## Architecture

Two parts:

1. **`internal/kernelcat/catalog.json`** — a JSON manifest embedded into the
   banger binary via `go:embed`. Each entry carries a name, distro, arch,
   kernel version, tarball URL, and tarball SHA256. Updating the catalog
   means editing this file in the repo and rebuilding banger.

2. **Tarballs at `https://kernels.thaloco.com/`** — Cloudflare R2 bucket
   `banger-kernels`, fronted by a public custom domain. Each tarball is
   `<name>-<arch>.tar.zst` and contains `vmlinux`, optional `initrd.img`,
   and an optional `modules/` tree at the archive root.

The `banger kernel pull` flow streams the tarball, verifies its SHA256
against the embedded catalog entry, decompresses it (zstd), extracts it
into `~/.local/state/banger/kernels/<name>/`, and writes a manifest. Path
traversal entries and unsafe symlinks are rejected.

## Kernel types

**`generic-<version>`** — built from upstream kernel.org sources with
Firecracker's official config. All essential drivers (virtio_blk,
virtio_net, ext4, vsock) compiled in — no modules, no initramfs. This
is the kernel the golden image pairs with and the recommended kernel
for OCI-pulled images. Build with `scripts/make-generic-kernel.sh`.

## Adding or updating an entry

The repo has no CI for kernel publishing yet. Catalog updates are manual
and infrequent (kernel version bumps every few weeks at most).

```bash
# 1. Build the kernel locally.
scripts/make-generic-kernel.sh

# 2. Import it into the local catalog so the canonical layout exists.
banger kernel import generic-6.12 \
  --from build/manual/generic-kernel \
  --distro generic \
  --arch x86_64

# 3. Package, upload, patch catalog.json.
scripts/publish-kernel.sh generic-6.12 \
  --description "Generic Firecracker kernel 6.12 (all drivers built-in, no initrd)"

# 4. Review and commit the catalog change.
git diff -- internal/kernelcat/catalog.json
git add internal/kernelcat/catalog.json
git commit -m 'kernel catalog: add/update generic-6.12'

# 5. Rebuild so the new catalog is embedded.
make build
```

`scripts/publish-kernel.sh` reads the locally-imported entry under
`~/.local/state/banger/kernels/<name>/`, builds a tar+zstd archive, uploads
it to R2 via `rclone`, HEAD-checks the public URL, and patches
`internal/kernelcat/catalog.json` with the new URL, SHA256, and size.

Environment overrides if the defaults need to change:
`RCLONE_REMOTE`, `RCLONE_BUCKET`, `BASE_URL`, `BANGER_KERNELS_DIR`.

## Removing an entry

1. Delete the line from `internal/kernelcat/catalog.json` and commit.
2. Delete the tarball from R2: `rclone delete r2:banger-kernels/<name>-<arch>.tar.zst`.
3. Rebuild banger.

Already-pulled local copies on user machines are not invalidated — they
keep working until the user runs `banger kernel rm <name>`. That's
intentional: pulling is idempotent, removing should not break anyone in
the middle of a workflow.

## Versioning conventions

- **Entry names**: `<family>-<major.minor>` (e.g. `generic-6.12`).
  The major.minor is the kernel line. Patch-level bumps reuse the
  entry name and replace the tarball; minor bumps create a new entry
  (`generic-6.13`).
- **Architecture**: only `x86_64` is published today. The `arch` field in
  the catalog schema is additive — adding `arm64` later is a config
  change, not a schema change.
- **Tarball layout**: contents at the archive root (no top-level
  versioned directory). `vmlinux` is required; `initrd.img` and
  `modules/` are optional. Symlinks inside `modules/` are allowed but
  must resolve within the archive.

## Trust model

The embedded `catalog.json` carries the SHA256 of each tarball. `banger
kernel pull` rejects any download whose hash doesn't match. This protects
against transport corruption and against an attacker swapping a tarball
on R2 without also pushing a banger release.

It does **not** protect against a compromise of the banger source repo
itself — an attacker who can land a commit can change both the catalog
SHA256 and the tarball. GPG/sigstore signing of the published catalog
tarballs is deferred until banger is public and the threat model
justifies the operational overhead.

Upstream kernel sources *are* verified: `scripts/make-generic-kernel.sh`
fetches the detached PGP signature alongside the tarball from
kernel.org and rejects the build if gpg can't verify it against one
of the three known release signing keys (Greg KH / Linus / Sasha
Levin). So a compromised kernel.org mirror can't slip a backdoored
tarball past a maintainer rebuilding the kernel locally.

## Hosting

Tarballs live in Cloudflare R2 (bucket `banger-kernels`), served at the
custom domain `kernels.thaloco.com`. The bucket is publicly readable;
writes require the `banger-kernels-publish` API token (kept locally,
never committed). R2's free tier covers the expected traffic comfortably
(zero egress fees, generous storage).

If hosting ever moves, catalog entries can be migrated by reuploading the
tarballs and editing the URLs in `catalog.json` — no other code changes
required.

## Tech debt

- Kernel publishing is manual; there is no CI yet. `scripts/make-generic-kernel.sh`
  plus `scripts/publish-kernel.sh` is fine while refreshes are
  infrequent and maintainer-only. CI becomes relevant once banger
  goes public.
- `make lint-shell` runs at `--severity=error` only. Tightening to
  `--severity=warning` is a nice-to-have but low priority.
