Walkthrough: image catalog¶
The image catalog is bty-web’s only source of truth for “what can the
fleet flash”. Each entry is a URL (oras:// or https://) plus
optional metadata: a sha256 (when published alongside the image), a
human display name, a format, a size hint, a one-line description.
v0.40+: bty-web doesn’t own image bytes. The live env fetches the
catalog entry’s URL at flash time – through
withcache when the cache is warm,
direct from the upstream origin when it isn’t. There is no
BTY_IMAGE_ROOT, no dir-scan, no on-disk cache under bty-web’s state
dir. One rule: bty has catalogs; withcache has bytes.
Catalog identity: ref vs sha256¶
Two distinct identifiers on every catalog entry:
ref=sha256(canonicalise_src(src)). A 64-hex digest of the canonical URL. Always present (it’s pure math on the URL). This is what machine bindings target – a rolling oras tag’s ref stays stable across re-pushes, so binding to the tag survives the next rebuild upstream.sha256= the observed content hash of the image bytes. Optional. Rolling-tag entries (oras://...:latest,releases/latest/download/...) have no stable content sha at catalog-publish time and carrysha256 = None. Pinned entries carry the digest the publisher computed.
When an entry has a sha256 (or the source is an oras:// ref, whose
layer digest is resolved at flash time), bty verifies the streamed
bytes against it during the flash and aborts with an error on
mismatch, so a corrupted or tampered download never silently lands on a
disk. The hash is computed in the pipe (curl | tee | sha256sum | dd),
adding no measurable overhead. Entries with no known sha flash without
verification. For PXE flashes the server passes the content sha to the
live env as disk_image_sha in the boot plan, so verification holds
even when the image is served from a cache or direct origin whose URL
doesn’t carry the digest.
The merge collapses entries by ref: two manifest entries with the
same canonical src are one row. Same content under multiple refs
(operator catalogs the same image as both oras://a and
https://b) renders as two rows – different provenance, even if
the bytes match.
Three ways to add an entry¶
All paths write rows to the catalog_entries table in state.db.
None of them puts bytes anywhere on bty-web’s filesystem.
Upload a
catalog.toml–POST /ui/catalog/upload. Use this when you have a curated multi-entry manifest. bty-web parses the TOML, imports every row, then replacesBTY_PATHS_CATALOG_FILEso the manifest survives restarts.Fetch from release –
POST /ui/catalog/fetch-release. Pullsreleases/latest/download/catalog.tomlfrom the configured upstream catalog repo (defaultsafl/nosi; override via/ui/settings/upstream> “Catalog repo”) and imports it. This is the “load the default nosi catalog” button.Add by URL –
POST /catalog/entries. Body:{"image_url": "...", "sha_url": "..." | null}. For an ad-hoc image not in a curated catalog: host it on whatever HTTP server you have (nginx / GHCR via oras / S3) and add the URL. If the upstream publishes a.sha256sidecar, pointsha_urlat it; bty-web fetches + parses + stores the digest.
There is no upload form for image bytes.
Manifest schema¶
version = 1
[[images]]
name = "nosi-debian-sysdev-x86_64.img.gz"
src = "oras://ghcr.io/safl/nosi/debian-sysdev:latest"
sha256 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
format = "img.gz" # optional: auto-detected from name
size_bytes = 1234567890 # optional
description = "Debian sysdev image, rolling" # optional
Required fields per entry: name, src. Optional: sha256,
format, size_bytes, description. name must be unique within
the manifest. sha256 (when set) must be a 64-char lower-case hex
string.
Validate before deploying:
python3 -c 'import sys; from bty import catalog; catalog.load_source(sys.argv[1])' /path/to/catalog.toml
bty-web also parses server-side: an upload with a parse error bounces back with the error message and does NOT replace the running manifest.
Browser UI¶
/ui/images renders the catalog as a flat table: name, content sha
(or “unset”), format, source (with an icon for oras vs https), and a
per-row entry-delete button. Header controls: Fetch latest catalog
Upload catalog. In-page sub-nav jumps to the List + the recent Activity card.
There is no Fetch / Hash / Cache-delete column. Those buttons were the DownloadManager + HashManager affordances; v0.40 took them out.
CLI¶
The bty console script (the wizard) is the operator-facing flash
surface:
bty # interactive wizard, local image-root only
bty --catalog <URL> # interactive wizard with the catalog pre-loaded
bty --server <X> --mac <Y> # server-driven via GET <X>/pxe/<Y>/plan
--catalog accepts a local TOML path, an HTTP URL, an oras://
reference, or a bty-web instance’s /catalog.toml. Local-only mode
(no overlay) scans BTY_IMAGE_ROOT (or /var/lib/bty/images) on
the host running bty – typically the USB-stick BTY_IMAGES
partition or a developer’s directory – and shows whatever flashable
files are there. This is the bty CLI’s image root, distinct from
the deleted bty-web image-store.
HTTP API (catalog surface)¶
Endpoint |
Method |
Purpose |
|---|---|---|
|
GET |
unified catalog listing (one row per |
|
GET |
the unified catalog as a TOML manifest (what |
|
GET |
list operator-curated catalog entries |
|
POST |
add an entry: |
|
DELETE |
delete an entry by its |
|
POST |
import entries from a |
|
POST |
(form) upload a |
|
POST |
(form) pull the default catalog from the configured upstream repo (Settings > Upstream > Catalog repo; default |
All endpoints are auth-gated (the same session cookie as the browser UI).
The byte-handling endpoints (/catalog/downloads, /catalog/hashes,
/catalog/cache/{name}, PUT /images/{name}) are gone in v0.40.
Image bytes are withcache’s domain; the live env flashes from
whatever URL the plan endpoint hands it.
Environment variables¶
Var |
Default |
Purpose |
|---|---|---|
|
|
state directory (state.db, catalogs, session-secret) |
|
|
catalog manifest path |
|
|
GitHub repo the “Fetch netboot artifacts” button pulls kernel/initrd/squashfs from (was previously the catalog source too; the catalog source moved to a separate setting in v0.46 – |
Where the bytes actually live¶
File / dir |
Owner |
Purpose |
|---|---|---|
|
bty-web |
the catalog (rows) |
|
bty-web |
the operator-uploaded manifest (re-importable) |
withcache’s data dir |
withcache |
cached image blobs, keyed by origin URL |
upstream origin |
publisher |
the source of truth when withcache is cold |
bty-web stores no image bytes; withcache holds the cache; the upstream origin is the canonical source. See walkthrough-image-store.md for the full picture.