feat(indeedhub): manifest-driven 7-member stack, orchestrator-first (#20 phase 3)

Author the IndeedHub stack as 7 manifests (postgres/redis/minio/relay/api/
ffmpeg + frontend) and route install_indeedhub_stack through the
orchestrator first (immich pattern), falling back to the legacy installer
only when the manifests aren't deployed.

Data-preserving by construction — the manifests reproduce the live install
exactly so an existing node ADOPTS rather than recreates:
- container_name = the live hyphenated names the runtime already references
  (health_monitor tiers/deps, crash_recovery).
- named volumes indeedhub-{postgres,redis,minio,relay}-data (not bind mounts).
- dedicated indeedhub-net + network_aliases [postgres|redis|minio|relay|api]
  so the api/ffmpeg env hostnames and the frontend nginx upstreams resolve
  unchanged.
- generated_secrets (indeedhub-db-password/-minio-password owned by their
  backends, indeedhub-jwt by the api) reuse the live /var/lib/archipelago/
  secrets values (ensure_one no-ops on existing files; postgres pw is fixed
  at PGDATA init). minio user "indeeadmin" + AES_MASTER_SECRET literal kept.

The frontend carries the post_install hook (#20) that replaces the hardcoded
patch_indeedhub_nostr_provider: strip X-Frame-Options, refresh
nostr-provider.js from /opt/archipelago/web-ui, inject the <script> if
absent, reload nginx — defensive/idempotent since indeedhub:1.0.0 already
bakes these. Frontend manifest also corrected off its dead Next.js shape
(health check now nginx :7777, tmpfs /run + /var/cache/nginx).

Builds + unit-tested; live adoption/lifecycle verification on .228 next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-06-21 15:46:26 -04:00
co-authored by Claude Opus 4.8
parent b94b61f640
commit b1eea8c053
8 changed files with 404 additions and 22 deletions
@@ -696,6 +696,23 @@ fn immich_stack_app_ids() -> &'static [&'static str] {
&["immich-postgres", "immich-redis", "immich"]
}
fn indeedhub_stack_app_ids() -> &'static [&'static str] {
// Dependency order: backends + their generated secrets first, then the api
// (owns indeedhub-jwt; reads the db/minio secrets the backends materialised),
// then the ffmpeg worker, then the user-facing frontend ("indeedhub", which
// carries the post_install nginx hook). The frontend's nginx reaches the
// backends by their short network_aliases (api/minio/relay) on indeedhub-net.
&[
"indeedhub-postgres",
"indeedhub-redis",
"indeedhub-minio",
"indeedhub-relay",
"indeedhub-api",
"indeedhub-ffmpeg",
"indeedhub",
]
}
const REGISTRY: &str = "146.59.87.168:3000/lfg2025";
const NETBIRD_DASHBOARD_IMAGE: &str = "docker.io/netbirdio/dashboard:v2.38.0";
@@ -1422,6 +1439,20 @@ impl RpcHandler {
/// Install the IndeedHub multi-container stack.
pub(super) async fn install_indeedhub_stack(&self) -> Result<serde_json::Value> {
// Manifest-driven path (#20 phase 3): render the 7-member stack from
// apps/indeedhub-*/manifest.yml via the orchestrator (dedicated
// indeedhub-net + network_aliases, generated_secrets, the frontend's
// post_install nginx hook, reboot-survivable). The manifests use the exact
// live container names / named volumes, so on an existing node this ADOPTS
// the running stack rather than recreating it (data preserved). Falls back
// to the legacy installer below only when the orchestrator doesn't know
// these app_ids (manifests not yet deployed). See PRODUCTION-MASTER-PLAN.md.
if let Some(orchestrated) =
install_stack_via_orchestrator(self, "indeedhub", indeedhub_stack_app_ids()).await?
{
return Ok(orchestrated);
}
let registry = crate::container::registry::load_registries(&self.config.data_dir)
.await
.unwrap_or_default()