# Image catalog

The image catalog ships pre-built banger rootfs bundles so users don't
have to register or build anything. It's the fast path behind
`banger vm run` (auto-pull) and `banger image pull <name>`. The
catalog is embedded into the banger binary and updated each release.

End-user flow:

```bash
banger image pull debian-bookworm             # explicit
banger vm run --name sandbox                  # implicit (auto-pulls)
```

## Architecture

Two parts — the same shape as the kernel catalog:

1. **`internal/imagecat/catalog.json`** — JSON manifest embedded into
   the banger binary via `go:embed`. Each entry: name, distro, arch,
   kernel_ref (a `kernelcat` entry name), tarball URL, tarball
   sha256, size.

2. **Tarballs at `https://images.thaloco.com/`** — Cloudflare R2
   bucket `banger-images`, fronted by a public custom domain. Each
   tarball is `<name>-<arch>-<sha256-prefix>.tar.zst` (content-
   addressed filename so CDN edge cache can never serve stale bytes
   for the URL the catalog points at). Contents at the archive root:
   `rootfs.ext4` (finalized: flattened + ownership-fixed + agent-
   injected at build time) and `manifest.json`.

The `banger image pull` bundle path streams the tarball, verifies
sha256 against the catalog entry, extracts both files into a staging
dir, resolves the kernel via `kernel_ref` (auto-pulling from
`kernelcat` if the user hasn't pulled it yet), stages boot artifacts
alongside, and registers the result as a managed image.

The same `image pull` command transparently falls through to the
existing OCI-pull path when `<name>` doesn't match a catalog entry —
see [`docs/oci-import.md`](oci-import.md).

## Adding or updating an entry

The repo has no CI for bundle publishing yet. Catalog updates are
manual.

```bash
# 1. Build the bundle + upload + patch catalog.json in one shot.
scripts/publish-golden-image.sh

# 2. Review and commit the catalog change.
git diff -- internal/imagecat/catalog.json
git add internal/imagecat/catalog.json
git commit -m 'imagecat: publish debian-bookworm'

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

`scripts/publish-golden-image.sh` wraps `scripts/make-golden-bundle.sh`
(which runs `docker build` on `images/golden/Dockerfile` then pipes
`docker export` into `banger internal make-bundle`), computes the
bundle's sha256, uses the first 12 hex chars as a cache-busting
filename suffix, uploads via `rclone` to R2, HEAD-checks the public
URL, and patches `internal/imagecat/catalog.json`.

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

`--skip-upload` builds the bundle into `dist/` and stops — useful for
local testing without touching R2 or the catalog.

## Bundle format

A bundle is a tar+zstd archive with exactly two entries at the root:

```
rootfs.ext4      # finalized banger rootfs
manifest.json    # {name, distro, arch, kernel_ref, description}
```

`rootfs.ext4` is fully prepared at build time: ownership fixed via
`debugfs sif`, banger guest agents (vsock agent, network bootstrap,
first-boot unit) already injected and enabled in
`multi-user.target.wants`. The pull path only has to place the file
and register the image — no mkfs, no ownership pass, no injection on
the daemon host.

## Removing an entry

1. Remove the entry from `internal/imagecat/catalog.json` and commit.
2. Delete the tarball from R2:
   `rclone delete banger-images:banger-images/<name>-<arch>-<hash>.tar.zst`.
3. Rebuild banger.

Already-pulled local images are not invalidated — users keep using
them until they run `banger image delete <name>`.

## Versioning conventions

- **Entry names**: `<distro>-<release>` (e.g. `debian-bookworm`).
  Per-release names make it trivial to publish `debian-trixie`
  alongside without collisions.
- **Content-addressed filenames**: the `-<sha256-prefix>` suffix is
  mandatory (set by `publish-golden-image.sh`). Never reuse a URL for
  different bytes.
- **Architecture**: `x86_64` only today. The `arch` field is additive
  — adding `arm64` is a config change, not a schema change.

## Trust model

Same as the kernel catalog: the embedded `catalog.json` carries each
bundle's sha256, and `imagecat.Fetch` rejects any download whose hash
doesn't match. This protects against transport corruption and against
an attacker swapping an R2 object without landing a commit in the
banger repo. GPG/sigstore signing is deferred until banger is public
and the threat model justifies the operational overhead.

## Hosting

Tarballs live in Cloudflare R2 (bucket `banger-images`), served at
`images.thaloco.com`. The bucket is publicly readable; writes require
the R2 API token configured on the `banger-images` rclone remote.
