From e382e679aee1e38ffa2b34cc56492ae1617a7986 Mon Sep 17 00:00:00 2001 From: archipelago Date: Tue, 1 Sep 2026 10:29:05 -0400 Subject: [PATCH] fix(apps): NPM needs /etc/letsencrypt mounted and NET_BIND_SERVICE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Converting Nginx Proxy Manager to a platform manifest (fc68c5b6) dropped two things its image hard-requires, and the result was an endless start/die loop — shorty-s watched it restart 3,176 times: 1. /etc/letsencrypt mount: NPM's s6 'prepare' service refuses to boot without it ('ERROR: /etc/letsencrypt is not mounted!'). Mounted from the same persistent app directory as before (/var/lib/archipelago/nginx-proxy-manager/letsencrypt), so existing certificates are preserved — no data moves, no migration. 2. NET_BIND_SERVICE: NPM's internal nginx listens on 80, 443 AND 81, and the orchestrator runs --cap-drop=ALL. The legacy podman-run path defaulted to the full capability set (and the legacy repair path in package/config.rs always listed it), which is why this only broke once the manifest became the source of truth. The signed catalog embeds manifests with origin-wins semantics, so the catalog carries the fix for every catalog-covered node — regenerate it here (plus the generated store/launcher-port artifacts, which also pick up drift from bf6ef964's retired apps). Catalog re-signing follows the usual ceremony. --- app-catalog/catalog.json | 12 ++++++------ apps/nginx-proxy-manager/manifest.yml | 16 +++++++++++++++- core/archipelago/src/fips/app_ports.rs | 6 +++--- neode-ui/public/catalog.json | 12 ++++++------ .../appSession/generatedAppSessionConfig.ts | 12 +++++++----- 5 files changed, 37 insertions(+), 21 deletions(-) diff --git a/app-catalog/catalog.json b/app-catalog/catalog.json index 0c3d269d..463eb113 100644 --- a/app-catalog/catalog.json +++ b/app-catalog/catalog.json @@ -26,7 +26,7 @@ "id": "alby-hub", "title": "Alby Hub", "version": "1.23.0", - "description": "Self-custodial Lightning wallet hub. Runs its own Lightning node on your Archipelago and connects your apps to it over Nostr Wallet Connect \u2014 one hub, every app pays through it.", + "description": "Self-custodial Lightning wallet hub. Runs its own Lightning node on your Archipelago and connects your apps to it over Nostr Wallet Connect — one hub, every app pays through it.", "icon": "/assets/img/app-icons/alby-hub.svg", "author": "Alby", "category": "money", @@ -370,7 +370,7 @@ "id": "netbird", "title": "NetBird", "version": "2.38.0", - "description": "Self-hosted WireGuard mesh VPN control plane with dashboard, embedded identity provider, management API, signal, relay, and STUN. The user-facing entry point \u2014 a TLS proxy in front of the dashboard + server.", + "description": "Self-hosted WireGuard mesh VPN control plane with dashboard, embedded identity provider, management API, signal, relay, and STUN. The user-facing entry point — a TLS proxy in front of the dashboard + server.", "icon": "/assets/img/app-icons/netbird.svg", "author": "NetBird", "category": "networking", @@ -412,7 +412,7 @@ "id": "nginx-proxy-manager", "title": "Nginx Proxy Manager", "version": "2.12.1", - "description": "Reverse proxy with SSL. Beautiful web interface for managing proxies. On a node, this manages its admin UI and upstream configuration \u2014 the proxy's own :80/:443 listeners are not published (the node's web server owns those ports).", + "description": "Reverse proxy with SSL. Beautiful web interface for managing proxies. On a node, this manages its admin UI and upstream configuration — the proxy's own :80/:443 listeners are not published (the node's web server owns those ports).", "icon": "/assets/img/app-icons/nginx.svg", "author": "Nginx Proxy Manager", "category": "networking", @@ -448,7 +448,7 @@ "id": "ollama", "title": "Ollama", "version": "0.5.4", - "description": "Run large language models locally. Download and run AI models like Llama, Mistral on your own hardware \u2014 served on the node's loopback for the AI assistant (Settings \u2192 Claude Auth \u2192 model backend), never exposed to the network.", + "description": "Run large language models locally. Download and run AI models like Llama, Mistral on your own hardware — served on the node's loopback for the AI assistant (Settings → Claude Auth → model backend), never exposed to the network.", "icon": "/assets/img/app-icons/ollama.png", "author": "Ollama", "category": "community", @@ -460,7 +460,7 @@ "id": "phoenixd", "title": "phoenixd", "version": "0.9.0", - "description": "Headless Lightning daemon by ACINQ (the Phoenix wallet team). No screen of its own \u2014 it exposes a small local API that other apps and tools use to send and receive Lightning payments. Channel liquidity is managed automatically for a fee.", + "description": "Headless Lightning daemon by ACINQ (the Phoenix wallet team). No screen of its own — it exposes a small local API that other apps and tools use to send and receive Lightning payments. Channel liquidity is managed automatically for a fee.", "icon": "/assets/img/app-icons/phoenixd.svg", "author": "ACINQ", "category": "money", @@ -495,7 +495,7 @@ "id": "pine", "title": "Pine", "version": "1.3.0", - "description": "A private voice assistant for your home. Pine runs speech-to-text (Whisper), text-to-speech (Piper) and wake-word detection (openWakeWord) on your own node and pairs with a PineVoice satellite speaker, so Home Assistant Assist works locally with nothing sent to the cloud. Ask it about your node \u2014 block height, sync, peers, Lightning balance \u2014 and, when a Claude API key is set, anything else.", + "description": "A private voice assistant for your home. Pine runs speech-to-text (Whisper), text-to-speech (Piper) and wake-word detection (openWakeWord) on your own node and pairs with a PineVoice satellite speaker, so Home Assistant Assist works locally with nothing sent to the cloud. Ask it about your node — block height, sync, peers, Lightning balance — and, when a Claude API key is set, anything else.", "icon": "/assets/img/app-icons/pine.svg", "author": "Archipelago", "category": "home", diff --git a/apps/nginx-proxy-manager/manifest.yml b/apps/nginx-proxy-manager/manifest.yml index 47fece1e..42f18e97 100644 --- a/apps/nginx-proxy-manager/manifest.yml +++ b/apps/nginx-proxy-manager/manifest.yml @@ -24,7 +24,14 @@ app: disk_limit: 1Gi security: - capabilities: [CHOWN, SETUID, SETGID, DAC_OVERRIDE] + # NET_BIND_SERVICE is load-bearing, not decoration: NPM's internal nginx + # listens on 80, 443 AND 81, and the orchestrator runs --cap-drop=ALL — + # without this cap every start dies with "bind() to 0.0.0.0:80 failed + # (13: Permission denied)" and s6 restart-loops forever (shorty-s, + # 2026-09-01, restart counter 3176 within hours of the manifest + # conversion). The legacy podman-run path defaulted to the full cap set, + # which is why it never showed there. + capabilities: [CHOWN, SETUID, SETGID, DAC_OVERRIDE, NET_BIND_SERVICE] readonly_root: false no_new_privileges: true network_policy: isolated @@ -47,6 +54,13 @@ app: source: /var/lib/archipelago/nginx-proxy-manager target: /data options: [rw] + # Current NPM images refuse to start unless /etc/letsencrypt is a mount in + # its own right. Keeping the files below the same persistent app directory + # preserves existing certificates while satisfying that startup contract. + - type: bind + source: /var/lib/archipelago/nginx-proxy-manager/letsencrypt + target: /etc/letsencrypt + options: [rw] environment: [] diff --git a/core/archipelago/src/fips/app_ports.rs b/core/archipelago/src/fips/app_ports.rs index b85eed8c..acd45e1a 100644 --- a/core/archipelago/src/fips/app_ports.rs +++ b/core/archipelago/src/fips/app_ports.rs @@ -6,7 +6,7 @@ //! no listener, so allowing them is inert. pub const APP_LAUNCH_PORTS: &[u16] = &[ - 2283, 2342, 3000, 3001, 3002, 4080, 5180, 7778, 8080, 8081, 8082, 8083, 8084, 8085, 8087, 8088, - 8089, 8090, 8096, 8123, 8175, 8176, 8187, 8240, 8334, 8336, 8888, 8999, 9000, 9100, 10380, - 11434, 18081, 18083, 23000, 32838, 50002, + 2283, 2342, 3000, 3001, 3002, 3030, 4080, 5180, 7778, 8080, 8081, 8082, 8083, 8084, 8085, 8087, + 8090, 8096, 8123, 8175, 8176, 8187, 8240, 8334, 8336, 8888, 8999, 9000, 9100, 10380, 11434, + 18081, 18083, 23000, 32838, 50002, ]; diff --git a/neode-ui/public/catalog.json b/neode-ui/public/catalog.json index 0c3d269d..463eb113 100644 --- a/neode-ui/public/catalog.json +++ b/neode-ui/public/catalog.json @@ -26,7 +26,7 @@ "id": "alby-hub", "title": "Alby Hub", "version": "1.23.0", - "description": "Self-custodial Lightning wallet hub. Runs its own Lightning node on your Archipelago and connects your apps to it over Nostr Wallet Connect \u2014 one hub, every app pays through it.", + "description": "Self-custodial Lightning wallet hub. Runs its own Lightning node on your Archipelago and connects your apps to it over Nostr Wallet Connect — one hub, every app pays through it.", "icon": "/assets/img/app-icons/alby-hub.svg", "author": "Alby", "category": "money", @@ -370,7 +370,7 @@ "id": "netbird", "title": "NetBird", "version": "2.38.0", - "description": "Self-hosted WireGuard mesh VPN control plane with dashboard, embedded identity provider, management API, signal, relay, and STUN. The user-facing entry point \u2014 a TLS proxy in front of the dashboard + server.", + "description": "Self-hosted WireGuard mesh VPN control plane with dashboard, embedded identity provider, management API, signal, relay, and STUN. The user-facing entry point — a TLS proxy in front of the dashboard + server.", "icon": "/assets/img/app-icons/netbird.svg", "author": "NetBird", "category": "networking", @@ -412,7 +412,7 @@ "id": "nginx-proxy-manager", "title": "Nginx Proxy Manager", "version": "2.12.1", - "description": "Reverse proxy with SSL. Beautiful web interface for managing proxies. On a node, this manages its admin UI and upstream configuration \u2014 the proxy's own :80/:443 listeners are not published (the node's web server owns those ports).", + "description": "Reverse proxy with SSL. Beautiful web interface for managing proxies. On a node, this manages its admin UI and upstream configuration — the proxy's own :80/:443 listeners are not published (the node's web server owns those ports).", "icon": "/assets/img/app-icons/nginx.svg", "author": "Nginx Proxy Manager", "category": "networking", @@ -448,7 +448,7 @@ "id": "ollama", "title": "Ollama", "version": "0.5.4", - "description": "Run large language models locally. Download and run AI models like Llama, Mistral on your own hardware \u2014 served on the node's loopback for the AI assistant (Settings \u2192 Claude Auth \u2192 model backend), never exposed to the network.", + "description": "Run large language models locally. Download and run AI models like Llama, Mistral on your own hardware — served on the node's loopback for the AI assistant (Settings → Claude Auth → model backend), never exposed to the network.", "icon": "/assets/img/app-icons/ollama.png", "author": "Ollama", "category": "community", @@ -460,7 +460,7 @@ "id": "phoenixd", "title": "phoenixd", "version": "0.9.0", - "description": "Headless Lightning daemon by ACINQ (the Phoenix wallet team). No screen of its own \u2014 it exposes a small local API that other apps and tools use to send and receive Lightning payments. Channel liquidity is managed automatically for a fee.", + "description": "Headless Lightning daemon by ACINQ (the Phoenix wallet team). No screen of its own — it exposes a small local API that other apps and tools use to send and receive Lightning payments. Channel liquidity is managed automatically for a fee.", "icon": "/assets/img/app-icons/phoenixd.svg", "author": "ACINQ", "category": "money", @@ -495,7 +495,7 @@ "id": "pine", "title": "Pine", "version": "1.3.0", - "description": "A private voice assistant for your home. Pine runs speech-to-text (Whisper), text-to-speech (Piper) and wake-word detection (openWakeWord) on your own node and pairs with a PineVoice satellite speaker, so Home Assistant Assist works locally with nothing sent to the cloud. Ask it about your node \u2014 block height, sync, peers, Lightning balance \u2014 and, when a Claude API key is set, anything else.", + "description": "A private voice assistant for your home. Pine runs speech-to-text (Whisper), text-to-speech (Piper) and wake-word detection (openWakeWord) on your own node and pairs with a PineVoice satellite speaker, so Home Assistant Assist works locally with nothing sent to the cloud. Ask it about your node — block height, sync, peers, Lightning balance — and, when a Claude API key is set, anything else.", "icon": "/assets/img/app-icons/pine.svg", "author": "Archipelago", "category": "home", diff --git a/neode-ui/src/views/appSession/generatedAppSessionConfig.ts b/neode-ui/src/views/appSession/generatedAppSessionConfig.ts index 0cf0ba3d..4d555b91 100644 --- a/neode-ui/src/views/appSession/generatedAppSessionConfig.ts +++ b/neode-ui/src/views/appSession/generatedAppSessionConfig.ts @@ -1,6 +1,7 @@ /** Generated by scripts/generate-app-catalog.py. Do not edit manually. */ export const GENERATED_APP_PORTS: Record = { + "adguardhome": 3030, "aiui": 5180, "alby-hub": 8187, "archy-mempool-web": 4080, @@ -8,7 +9,6 @@ export const GENERATED_APP_PORTS: Record = { "bitcoin-ui": 8334, "botfights": 9100, "btcpay-server": 23000, - "did-wallet": 8088, "electrs-ui": 50002, "electrumx": 50002, "fedimint": 8175, @@ -23,9 +23,9 @@ export const GENERATED_APP_PORTS: Record = { "lnd-ui": 18083, "mempool": 4080, "mempool-api": 8999, - "morphos-server": 8089, "netbird": 8087, "nextcloud": 8085, + "nginx-proxy-manager": 8081, "nostr-rs-relay": 18081, "photoprism": 2342, "pine": 10380, @@ -33,11 +33,13 @@ export const GENERATED_APP_PORTS: Record = { "router": 8084, "searxng": 8888, "strfry": 8090, + "tailscale": 8240, "uptime-kuma": 3002, "vaultwarden": 8082, } export const GENERATED_APP_TITLES: Record = { + "adguardhome": "AdGuard Home", "aiui": "AI Assistant", "alby-hub": "Alby Hub", "archy-btcpay-db": "BTCPay Postgres", @@ -52,7 +54,6 @@ export const GENERATED_APP_TITLES: Record = { "btcpay-server": "BTCPay Server", "core-lightning": "Core Lightning (CLN)", "cuprate": "Cuprate", - "did-wallet": "Web5 DID Wallet", "electrs-ui": "Electrs UI", "electrumx": "ElectrumX", "fedimint": "Fedimint Guardian", @@ -74,17 +75,17 @@ export const GENERATED_APP_TITLES: Record = { "indeedhub-redis": "IndeedHub Redis", "indeedhub-relay": "IndeedHub Nostr Relay", "jellyfin": "Jellyfin", - "lightning-stack": "Lightning Stack", "lnd": "LND", "lnd-ui": "LND UI", "mempool": "Mempool Explorer", "mempool-api": "Mempool API", - "morphos-server": "MorphOS Server", "netbird": "NetBird", "netbird-dashboard": "NetBird Dashboard", "netbird-server": "NetBird Server", "nextcloud": "Nextcloud", + "nginx-proxy-manager": "Nginx Proxy Manager", "nostr-rs-relay": "Nostr Relay (Rust)", + "ollama": "Ollama", "phoenixd": "phoenixd", "photoprism": "PhotoPrism", "pine": "Pine", @@ -95,6 +96,7 @@ export const GENERATED_APP_TITLES: Record = { "router": "Mesh Router", "searxng": "SearXNG", "strfry": "Strfry Nostr Relay", + "tailscale": "Tailscale", "uptime-kuma": "Uptime Kuma", "vaultwarden": "Vaultwarden", }