# Appliance registry-metadata design ## Goal Remove git/branch coupling from the appliance install path. A booted appliance must resolve a **channel** (`stable`/`nightly`) to an **immutable set of registry artifacts** and install from them. No git branch override, no raw GitHub content fetch, no git clone at install time. This replaces the prior design where `setup-engine` read local release metadata and profile values from git and `applyFluxSource` created a Flux `GitRepository` (which also served the helm charts via `chart: ./helm`). ## Artifacts (all in ghcr, published by the Argo pipeline) All under `ghcr.io/nine-minds`. App images already exist; the rest are new. 1. **App images** (unchanged): `ghcr.io/nine-minds/alga-psa-ee:` (+ workflow-worker, email-service, temporal-worker tags as today). 2. **Helm charts as OCI** (new): `oci://ghcr.io/nine-minds/charts/:` for each chart used by the appliance: `sebastian` (alga-core + pgbouncer), `temporal`, `temporal-worker`, `workflow-worker`, `email-service`. Pushed via `helm push`. 3. **Flux base bundle as OCI** (new): `oci://ghcr.io/nine-minds/alga-appliance-config:` — the rendered `ee/appliance/flux/` overlay (namespaces, child Kustomizations, HelmReleases, profile values), with each HelmRelease's chart source rewritten to the OCI chart ref + version. Pushed via `flux push artifact`. Pinned by **digest**. 4. **Control-plane image** (new — published, not just baked): `ghcr.io/nine-minds/alga-appliance-control-plane:`. Today it is only built locally and baked into the ISO (`localhost/...:baked`, `imagePullPolicy: IfNotPresent`, `k3s ctr images import`), so the setup UI / host-service can only be updated by re-burning an ISO. Publishing it to ghcr lets `bootstrap-control-plane.sh` **pull** it (channel/digest-pinned) and roll to it — making setup-UI / host-service updates registry-only. 5. **Release manifest as OCI** (new, the channel pointer): `oci://ghcr.io/nine-minds/alga-appliance-release` tagged `:stable`, `:nightly`, and `:`. Its config blob is JSON: ```json { "schema": "alga.appliance.release/v1", "version": "1.0.3", "channel": "stable", "valuesProfile": "single-node", "images": { "algaCore": "62cdce38", "workflowWorker": "a2cbb43", "emailService": "61e4a00e", "temporalWorker": "a2cbb43" }, "controlPlane": "62cdce38", "config": { "repository": "ghcr.io/nine-minds/alga-appliance-config", "version": "1.0.3", "digest": "sha256:..." }, "charts": { "sebastian": "0.0.1", "temporal": "0.1.0", "temporal-worker": "0.1.0", "workflow-worker": "0.1.0", "email-service": "0.1.0" } } ``` The manifest is the only mutable channel pointer; everything it references is pinned by digest/version, so a resolved release is immutable. ## Control-plane (setup UI / host-service) updates without an ISO burn The setup UI already runs in k8s (the `appliance-control-plane` Deployment, deployed by `bootstrap-control-plane.sh`), but its image is baked + imported, so UI/engine changes still require an ISO. To finish that goal: - Publish the control-plane image to ghcr (artifact #4) and add `controlPlane` to the release manifest. - Keep a **baseline** control-plane image baked in the ISO so first boot can serve the UI with no network dependency. - On boot, `bootstrap-control-plane.sh` resolves the channel's release manifest, and if `controlPlane` differs from the baked baseline, **pulls it from ghcr and rolls the Deployment to it**. Result: setup-UI / host-service updates become a channel repoint — no ISO. The ISO then only needs re-burning for k3s / base OS / Flux controllers / systemd units / the autoinstall seed. ## Consume side (`ee/appliance/host-service/setup-engine.mjs`) - `resolveChannelMetadata`: HTTP to ghcr registry API — token (`GET /token?scope=repository:nine-minds/alga-appliance-release:pull`) → `GET /v2/.../manifests/` → fetch the config blob = the release manifest JSON. No git. - `applyFluxSource`: create a Flux **OCIRepository** (`source.toolkit.fluxcd.io`) at `config.repository` pinned to `config.digest`, plus a Kustomization with `sourceRef: { kind: OCIRepository }`. No GitRepository. - Image tags come from `manifest.images`, injected into the per-release values ConfigMap exactly as today. - The flux-base HelmReleases (inside the config bundle) reference OCI charts pinned to `manifest.charts[name]`. - `validateSetupInputs`: keep `channel`; optional advanced override pins to a specific `version`/digest. ## Publish side (Argo, `~/nm-kube-config/alga-psa/workflows`) The publish stage is owned by the `~/nm-kube-config` Argo workflow and is gated on release/promote parameters (auth via the existing `github-token` secret, user `robertisaacs`): 1. `helm package` + `helm push oci://ghcr.io/nine-minds/charts/` for each chart. 2. Render `ee/appliance/flux/` with chart sources rewritten to OCI refs+versions; `flux push artifact oci://ghcr.io/nine-minds/alga-appliance-config:` (capture digest). 3. Build the release manifest JSON (images from the build, config digest from step 2, chart versions from step 1) and `oras push oci://ghcr.io/nine-minds/alga-appliance-release:` (and `:`). ## Decisions (made, with rationale) - **Full OCI** (charts + flux base + manifest all in the registry) rather than a host-templated base — it keeps base/structure changes out of the control-plane image (a new bundle artifact, not a rebuild) and is Flux-native. - **Channel = OCI tag** on the release manifest; everything else pinned by digest/version for immutability. - **ghcr only** for now (the appliance already requires ghcr egress; the preflight already checks `ghcr.io/v2/`). Harbor mirror optional later. - Public read on these metadata/chart artifacts (no pull secret needed), matching how images are pulled today. ## Bootstrapping / rollout - Publishing requires the Argo additions to run. To validate the consume side before wiring CI, the artifacts can be published once manually from a workstation (`helm push`, `flux push artifact`, `oras push` with a `gh` token). - This engine change ships in the control-plane image, so **one** more control-plane image build + ISO is needed; after that, channel/tag/release changes are registry-only (publish artifacts → appliance picks them up; no control-plane rebuild, no ISO, no branch).