feat(registry): deliver app manifests via the signed catalog (embed by default)

Turn on registry-distributed manifests for all apps: generate-app-catalog.sh now
embeds each apps/<id>/manifest.yml by default (EMBED_MANIFESTS opt-out), so nodes
install from the signed catalog (origin-wins overlay, disk = fallback) with no
OTA-shipped disk manifest. main.rs awaits a bounded (25s) refresh_catalog before
load_manifests so a fresh boot overlays the latest embedded catalog instead of a
restart later; offline/ISO boot falls through to disk and never hangs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-06-23 13:39:54 -04:00
parent a8b9b0f5e8
commit 9670af62b6
2 changed files with 25 additions and 7 deletions

View File

@ -198,6 +198,24 @@ async fn main() -> Result<()> {
(Some(trait_obj), Some(dev))
} else {
let prod = Arc::new(ProdContainerOrchestrator::new(config.clone()).await?);
// Pull the freshest signed app-catalog BEFORE loading manifests, so any
// registry-embedded manifest (the origin-wins overlay in load_manifests)
// is in place on THIS boot — not a restart later. Without this the boot
// would overlay the previous run's cached catalog and a newly-published
// app (e.g. a registry-only install) wouldn't appear until the next
// restart. Bounded + best-effort: on timeout/unreachable origin the
// last-cached catalog (or the disk manifests) still load — registry is
// an overlay on top of disk, never a hard dependency.
match tokio::time::timeout(
std::time::Duration::from_secs(25),
crate::container::app_catalog::refresh_catalog(&config.data_dir),
)
.await
{
Ok(Ok(n)) => info!("🛰️ app-catalog refreshed before manifest load ({n} apps)"),
Ok(Err(e)) => tracing::debug!("app-catalog pre-load refresh failed (using cache): {e}"),
Err(_) => tracing::debug!("app-catalog pre-load refresh timed out (using cache)"),
}
// Best-effort manifest load; a missing /opt/archipelago/apps is
// logged inside load_manifests and not fatal.
match prod.load_manifests().await {

View File

@ -14,16 +14,16 @@
#
# Usage:
# scripts/generate-app-catalog.sh [output-path]
# EMBED_MANIFESTS=1 scripts/generate-app-catalog.sh # also embed full manifests
# EMBED_MANIFESTS=0 scripts/generate-app-catalog.sh # version/image only (legacy)
# # then publish: push releases/app-catalog.json to the OVH gitea (raw URL).
#
# EMBED_MANIFESTS (opt-in, default off): also embed each app's full
# apps/<id>/manifest.yml into its catalog entry's `manifest` field, so nodes can
# EMBED_MANIFESTS (default ON, 2026-06-23): embed each app's full
# apps/<id>/manifest.yml into its catalog entry's `manifest` field, so nodes
# install from the signed registry alone (no OTA-shipped disk manifest). Consumed
# by container::app_catalog + the orchestrator's load_manifests overlay
# (origin-wins, disk = fallback). See docs/registry-manifest-design.md. Kept
# opt-in during the migration window so a routine catalog regen never changes
# what phase-1 nodes install until we deliberately turn it on.
# (origin-wins, disk = fallback). See docs/registry-manifest-design.md. The
# migration window is over — every regen now embeds; set EMBED_MANIFESTS=0 only
# to reproduce the old version/image-only catalog.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
@ -36,7 +36,7 @@ source "$ROOT/scripts/image-versions.sh"
set +a
UPDATED="$(date -u +%Y-%m-%d)" OUT="$OUT" APPS_DIR="$ROOT/apps" \
EMBED_MANIFESTS="${EMBED_MANIFESTS:-}" python3 - <<'PY'
EMBED_MANIFESTS="${EMBED_MANIFESTS:-1}" python3 - <<'PY'
import glob
import json, os