diff --git a/apps/indeedhub-api/manifest.yml b/apps/indeedhub-api/manifest.yml new file mode 100644 index 00000000..fb557280 --- /dev/null +++ b/apps/indeedhub-api/manifest.yml @@ -0,0 +1,77 @@ +app: + id: indeedhub-api + name: IndeedHub API + version: "1.0.0" + description: IndeedHub backend API (Nostr auth, media, payments). + category: community + + # Hyphen name matches runtime references + the live container (adoption); + # alias `api` is the short hostname the frontend nginx proxies to + # (http://api:4000). Reaches its backends by their short aliases + # (postgres/redis/minio) on indeedhub-net — unchanged from the legacy installer. + container_name: indeedhub-api + + container: + image: 146.59.87.168:3000/lfg2025/indeedhub-api:1.0.0 + pull_policy: if-not-present + network: indeedhub-net + network_aliases: [api] + # The JWT signing secret is owned here (no backend container owns it); the + # db + minio passwords are owned by indeedhub-postgres / indeedhub-minio and + # only consumed here. ensure_generated_secrets no-ops when a file already + # exists, so live values on .228 are preserved (postgres pw is fixed at + # PGDATA init — regenerating would lock the API out). + generated_secrets: + - name: indeedhub-jwt + kind: hex32 + secret_env: + - key: DATABASE_PASSWORD + secret_file: indeedhub-db-password + - key: AWS_SECRET_KEY + secret_file: indeedhub-minio-password + - key: NOSTR_JWT_SECRET + secret_file: indeedhub-jwt + + dependencies: + - app_id: indeedhub-postgres + - app_id: indeedhub-redis + - app_id: indeedhub-minio + + resources: + memory_limit: 2Gi + + security: + capabilities: [] + readonly_root: false + network_policy: isolated + + ports: [] + + volumes: [] + + environment: + - PORT=4000 + - DATABASE_HOST=postgres + - DATABASE_PORT=5432 + - DATABASE_USER=indeedhub + - DATABASE_NAME=indeedhub + - QUEUE_HOST=redis + - QUEUE_PORT=6379 + - S3_ENDPOINT=http://minio:9000 + - AWS_REGION=us-east-1 + - AWS_ACCESS_KEY=indeeadmin + - S3_PUBLIC_BUCKET_NAME=indeedhub-public + - S3_PRIVATE_BUCKET_NAME=indeedhub-private + - S3_PUBLIC_BUCKET_URL=/storage + - NOSTR_JWT_EXPIRES_IN=7d + # Fixed across the fleet (envelope-encryption master key baked by the legacy + # installer); not node-specific, so a plain env literal, not a secret. + - AES_MASTER_SECRET=0123456789abcdef0123456789abcdef + - ENVIRONMENT=production + + health_check: + type: tcp + endpoint: localhost:4000 + interval: 30s + timeout: 5s + retries: 10 diff --git a/apps/indeedhub-ffmpeg/manifest.yml b/apps/indeedhub-ffmpeg/manifest.yml new file mode 100644 index 00000000..2f93f148 --- /dev/null +++ b/apps/indeedhub-ffmpeg/manifest.yml @@ -0,0 +1,51 @@ +app: + id: indeedhub-ffmpeg + name: IndeedHub FFmpeg Worker + version: "1.0.0" + description: IndeedHub background media transcoding worker. + category: community + + # Hyphen name matches runtime references + the live container (adoption). No + # network_alias: nothing connects TO the worker — it only dials out to + # postgres/redis/minio (resolved by their aliases on indeedhub-net). + container_name: indeedhub-ffmpeg + + container: + image: 146.59.87.168:3000/lfg2025/indeedhub-ffmpeg:1.0.0 + pull_policy: if-not-present + network: indeedhub-net + secret_env: + - key: DATABASE_PASSWORD + secret_file: indeedhub-db-password + - key: AWS_SECRET_KEY + secret_file: indeedhub-minio-password + + dependencies: + - app_id: indeedhub-api + + resources: + memory_limit: 4Gi + + security: + capabilities: [] + readonly_root: false + network_policy: isolated + + ports: [] + + volumes: [] + + environment: + - DATABASE_HOST=postgres + - DATABASE_PORT=5432 + - DATABASE_USER=indeedhub + - DATABASE_NAME=indeedhub + - QUEUE_HOST=redis + - QUEUE_PORT=6379 + - S3_ENDPOINT=http://minio:9000 + - AWS_REGION=us-east-1 + - AWS_ACCESS_KEY=indeeadmin + - S3_PUBLIC_BUCKET_NAME=indeedhub-public + - S3_PRIVATE_BUCKET_NAME=indeedhub-private + - ENVIRONMENT=production + - AES_MASTER_SECRET=0123456789abcdef0123456789abcdef diff --git a/apps/indeedhub-minio/manifest.yml b/apps/indeedhub-minio/manifest.yml new file mode 100644 index 00000000..79e0d267 --- /dev/null +++ b/apps/indeedhub-minio/manifest.yml @@ -0,0 +1,60 @@ +app: + id: indeedhub-minio + name: IndeedHub MinIO + version: "RELEASE.2024-11-07T00-52-20Z" + description: MinIO S3-compatible object storage for IndeedHub media. + category: community + + # Hyphen name matches runtime references + the live container (adoption); + # alias `minio` is the short hostname the api/ffmpeg use (S3_ENDPOINT= + # http://minio:9000) AND the frontend nginx proxies to (http://minio:9000). + container_name: indeedhub-minio + + container: + image: 146.59.87.168:3000/lfg2025/minio:RELEASE.2024-11-07T00-52-20Z + pull_policy: if-not-present + network: indeedhub-net + network_aliases: [minio] + # `server /data` — the minio entrypoint args from the legacy installer. + custom_args: [server, /data] + generated_secrets: + - name: indeedhub-minio-password + kind: hex32 + secret_env: + - key: MINIO_ROOT_PASSWORD + secret_file: indeedhub-minio-password + + dependencies: + - storage: 50Gi + + resources: + memory_limit: 1Gi + disk_limit: 50Gi + + security: + capabilities: [] + readonly_root: false + network_policy: isolated + + ports: [] + + # Named volume matches the live indeedhub-minio-data volume on .228. + volumes: + - type: volume + source: indeedhub-minio-data + target: /data + options: [rw] + + # MINIO_ROOT_USER "indeeadmin" is the fixed admin identity baked by the legacy + # installer (api/ffmpeg use it as AWS_ACCESS_KEY); the password is the + # generated secret above. Not secret, so it stays a plain env value. + environment: + - MINIO_ROOT_USER=indeeadmin + + health_check: + type: http + endpoint: http://localhost:9000 + path: /minio/health/live + interval: 30s + timeout: 5s + retries: 5 diff --git a/apps/indeedhub-postgres/manifest.yml b/apps/indeedhub-postgres/manifest.yml new file mode 100644 index 00000000..8d09211f --- /dev/null +++ b/apps/indeedhub-postgres/manifest.yml @@ -0,0 +1,59 @@ +app: + id: indeedhub-postgres + name: IndeedHub Postgres + version: "16.13-alpine" + description: Postgres database backend for IndeedHub. + category: community + + # Container named indeedhub-postgres (hyphen) to match the runtime's existing + # per-app references (health_monitor tiers/deps, crash_recovery) and the live + # .228 install, so the orchestrator ADOPTS the running container instead of + # recreating it. `network_aliases: [postgres]` keeps the short hostname the + # api/ffmpeg/relay reach by (DATABASE_HOST=postgres) resolvable on + # indeedhub-net, reproducing the legacy `--network-alias postgres`. + container_name: indeedhub-postgres + + container: + image: 146.59.87.168:3000/lfg2025/postgres:16.13-alpine + pull_policy: if-not-present + network: indeedhub-net + network_aliases: [postgres] + generated_secrets: + - name: indeedhub-db-password + kind: hex32 + secret_env: + - key: POSTGRES_PASSWORD + secret_file: indeedhub-db-password + + dependencies: + - storage: 10Gi + + resources: + memory_limit: 1Gi + disk_limit: 10Gi + + security: + capabilities: [CHOWN, DAC_OVERRIDE, FOWNER, SETGID, SETUID] + readonly_root: false + network_policy: isolated + + ports: [] + + # Named podman volume (matches the live indeedhub-postgres-data volume on .228); + # preserves all existing database content across the migration. + volumes: + - type: volume + source: indeedhub-postgres-data + target: /var/lib/postgresql/data + options: [rw] + + environment: + - POSTGRES_USER=indeedhub + - POSTGRES_DB=indeedhub + + health_check: + type: tcp + endpoint: localhost:5432 + interval: 30s + timeout: 5s + retries: 3 diff --git a/apps/indeedhub-redis/manifest.yml b/apps/indeedhub-redis/manifest.yml new file mode 100644 index 00000000..c9997b0a --- /dev/null +++ b/apps/indeedhub-redis/manifest.yml @@ -0,0 +1,45 @@ +app: + id: indeedhub-redis + name: IndeedHub Redis + version: "7.4.8-alpine" + description: Redis queue/cache backend for IndeedHub. + category: community + + # Hyphen name matches runtime references + the live container (adoption); + # alias `redis` is the short hostname the api/ffmpeg reach (QUEUE_HOST=redis). + container_name: indeedhub-redis + + container: + image: 146.59.87.168:3000/lfg2025/redis:7.4.8-alpine + pull_policy: if-not-present + network: indeedhub-net + network_aliases: [redis] + + dependencies: + - storage: 1Gi + + resources: + memory_limit: 256Mi + + security: + capabilities: [SETGID, SETUID] + readonly_root: false + network_policy: isolated + + ports: [] + + # Named volume matches the live indeedhub-redis-data volume on .228. + volumes: + - type: volume + source: indeedhub-redis-data + target: /data + options: [rw] + + environment: [] + + health_check: + type: tcp + endpoint: localhost:6379 + interval: 30s + timeout: 5s + retries: 3 diff --git a/apps/indeedhub-relay/manifest.yml b/apps/indeedhub-relay/manifest.yml new file mode 100644 index 00000000..842fe0c4 --- /dev/null +++ b/apps/indeedhub-relay/manifest.yml @@ -0,0 +1,47 @@ +app: + id: indeedhub-relay + name: IndeedHub Nostr Relay + version: "0.9.0" + description: nostr-rs-relay backing IndeedHub's Nostr identity + comments. + category: community + + # Hyphen name matches runtime references + the live container (adoption); + # alias `relay` is the short hostname the frontend nginx proxies to + # (http://relay:8080 for the /relay websocket). + container_name: indeedhub-relay + + container: + image: 146.59.87.168:3000/lfg2025/nostr-rs-relay:0.9.0 + pull_policy: if-not-present + network: indeedhub-net + network_aliases: [relay] + + dependencies: + - storage: 2Gi + + resources: + memory_limit: 256Mi + disk_limit: 2Gi + + security: + capabilities: [] + readonly_root: false + network_policy: isolated + + ports: [] + + # Named volume matches the live indeedhub-relay-data volume on .228. + volumes: + - type: volume + source: indeedhub-relay-data + target: /usr/src/app/db + options: [rw] + + environment: [] + + health_check: + type: tcp + endpoint: localhost:8080 + interval: 30s + timeout: 5s + retries: 3 diff --git a/apps/indeedhub/manifest.yml b/apps/indeedhub/manifest.yml index cf625470..45c8ad3c 100644 --- a/apps/indeedhub/manifest.yml +++ b/apps/indeedhub/manifest.yml @@ -1,58 +1,70 @@ app: id: indeedhub name: IndeeHub - version: 1.0.0 + version: "1.0.0" description: Bitcoin documentary streaming platform featuring God Bless Bitcoin and other educational content about Bitcoin, sovereignty, and decentralized technology. Sign in with your Nostr identity. category: community + # The user-facing launcher (app_id "indeedhub"). Container is named "indeedhub" + # (matches the runtime's per-app references + the live container, so the + # orchestrator adopts it). Its nginx (listen 7777) proxies to the backends by + # their short aliases on indeedhub-net: api:4000, minio:9000, relay:8080. + container_name: indeedhub + container: image: 146.59.87.168:3000/lfg2025/indeedhub:1.0.0 - pull_policy: always # Pull from registry; falls back to local build + pull_policy: if-not-present network: indeedhub-net dependencies: + - app_id: indeedhub-api - storage: 1Gi resources: - cpu_limit: 2 memory_limit: 512Mi disk_limit: 1Gi security: capabilities: [] - readonly_root: true - no_new_privileges: true - user: 1001 - seccomp_profile: default - network_policy: bridge - apparmor_profile: default + readonly_root: false + network_policy: isolated ports: - host: 7778 container: 7777 - protocol: tcp # Web UI. Port 7777 on the host is reserved for Nostr relay. + protocol: tcp # Web UI. Port 7777 on the host is reserved for the Nostr relay. + # Writable scratch the baked nginx needs; matches the legacy installer's + # --tmpfs /run + /var/cache/nginx. volumes: - - type: tmpfs - target: /tmp - options: [rw,noexec,nosuid,size=64m] - - type: tmpfs - target: /app/.next/cache - options: [rw,noexec,nosuid,size=128m] - type: tmpfs target: /run - options: [rw,nosuid,nodev,size=16m] + options: [rw, nosuid, nodev, size=16m] - type: tmpfs target: /var/cache/nginx - options: [rw,nosuid,nodev,size=32m] + options: [rw, nosuid, nodev, size=32m] - environment: - - NODE_ENV=production - - NEXT_TELEMETRY_DISABLED=1 + environment: [] + + # Defensive + idempotent. The current indeedhub:1.0.0 image already bakes the + # iframe-friendly nginx (X-Frame-Options omitted, nostr-provider.js present + + # #' /etc/nginx/conf.d/default.conf"] + - exec: ["nginx", "-s", "reload"] health_check: type: http - endpoint: http://localhost:3000 + endpoint: http://localhost:7777 path: / interval: 30s timeout: 10s diff --git a/core/archipelago/src/api/rpc/package/stacks.rs b/core/archipelago/src/api/rpc/package/stacks.rs index 2d410c88..ed088964 100644 --- a/core/archipelago/src/api/rpc/package/stacks.rs +++ b/core/archipelago/src/api/rpc/package/stacks.rs @@ -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 { + // 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()