From b3a6d2103a485efd4f06ba5d628818ee239ba92c Mon Sep 17 00:00:00 2001 From: Dorian Date: Thu, 2 Apr 2026 16:15:04 +0100 Subject: [PATCH] fix: container stability, OnlyOffice removal, node bootstrapping, UI fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Container orchestration: - Add --network-alias to all archy-net containers (fixes Podman DNS) - Fix bitcoin-knots health check: expand $BITCOIN_RPC_PASS at creation - Increase bitcoin-knots memory limit to 4g, reduce dbcache to 2048 - Enable podman-restart.service in ISO for auto-start on boot - Fix UI container Dockerfiles: ENTRYPOINT [], user root for rootless App changes: - Remove OnlyOffice (incompatible with rootless Podman) - Replace with CryptPad reference (single-process, e2e encrypted) - Fix NPM port mapping: 8181 → 81 - Fix OnlyOffice port mapping: 8044 → 9980 (now CryptPad: 3003) AIUI & proxy: - Add MODEL_MAP to claude-api-proxy (ISO + deploy) - Map legacy model IDs (claude-haiku-4.5 → claude-haiku-4-5-20251001) Kiosk: - Move chromium-kiosk data dir to /var/lib/archipelago (data partition) - Remove --metrics-recording-only (contradicted --disable-metrics) Node bootstrapping: - Add bootstrap-switchover.sh for live node updates - ElectrumX UI improvements and nginx proxy fixes - LND UI nginx config updates Backend: - Bitcoin health check uses .cookie auth (no plaintext creds) - ElectrumX status endpoint improvements - Network alias flag in install.rs for DNS reliability Co-Authored-By: Claude Opus 4.6 (1M context) --- .../archipelago/src/api/rpc/package/config.rs | 19 +-- .../src/container/docker_packages.rs | 10 +- core/archipelago/src/electrs_status.rs | 6 +- core/container/src/podman_client.rs | 2 +- docker/electrs-ui/index.html | 15 +- docker/electrs-ui/nginx.conf | 10 +- docker/lnd-ui/nginx.conf | 9 +- image-recipe/build-auto-installer-iso.sh | 76 +++++++++ image-recipe/configs/nginx-archipelago.conf | 98 ++++++++--- .../archipelago-https-app-proxies.conf | 16 +- scripts/bootstrap-switchover.sh | 113 +++++++++++++ scripts/first-boot-containers.sh | 158 ++++++++++++++---- scripts/image-versions.sh | 4 +- 13 files changed, 423 insertions(+), 113 deletions(-) create mode 100755 scripts/bootstrap-switchover.sh diff --git a/core/archipelago/src/api/rpc/package/config.rs b/core/archipelago/src/api/rpc/package/config.rs index 3bb20f6e..166fb3d8 100644 --- a/core/archipelago/src/api/rpc/package/config.rs +++ b/core/archipelago/src/api/rpc/package/config.rs @@ -59,7 +59,7 @@ pub(super) fn get_app_capabilities(app_id: &str) -> Vec { "--cap-add=NET_RAW".to_string(), ], "nextcloud" | "btcpay-server" | "btcpayserver" - | "onlyoffice" | "onlyoffice-documentserver" | "portainer" => vec![ + | "portainer" => vec![ "--cap-add=CHOWN".to_string(), "--cap-add=SETUID".to_string(), "--cap-add=SETGID".to_string(), @@ -156,11 +156,10 @@ pub(super) fn is_readonly_compatible(app_id: &str) -> bool { /// Get container health check arguments for podman run. /// Returns (health-cmd, interval, retries) args to append to run_args. -pub(super) fn get_health_check_args(app_id: &str, rpc_pass: &str) -> Vec { - let btc_health = format!( - "bitcoin-cli -rpcuser=archipelago -rpcpassword={} getblockchaininfo || exit 1", - rpc_pass - ); +pub(super) fn get_health_check_args(app_id: &str, _rpc_pass: &str) -> Vec { + // bitcoin-cli reads the .cookie file from -datadir automatically (no plaintext creds needed) + let btc_health = "bitcoin-cli -datadir=/home/bitcoin/.bitcoin getblockchaininfo || exit 1" + .to_string(); let (cmd, interval, retries) = match app_id { "bitcoin" | "bitcoin-core" | "bitcoin-knots" => (btc_health.as_str(), "30s", "3"), "lnd" => ("lncli getinfo || exit 1", "30s", "3"), @@ -256,7 +255,7 @@ pub(super) fn get_memory_limit(app_id: &str) -> &'static str { match app_id { // Heavy apps "bitcoin" | "bitcoin-core" | "bitcoin-knots" => "4g", - "onlyoffice" | "onlyoffice-documentserver" => "2g", + "cryptpad" => "512m", "ollama" => "4g", // Medium apps "lnd" => "512m", @@ -566,9 +565,9 @@ pub(super) async fn get_app_config( None, None, ), - "onlyoffice" | "onlyoffice-documentserver" => ( - vec!["9980:80".to_string()], - vec![], + "cryptpad" => ( + vec!["3003:3000".to_string()], + vec!["/var/lib/archipelago/cryptpad:/cryptpad/datastore".to_string()], vec![], None, None, diff --git a/core/archipelago/src/container/docker_packages.rs b/core/archipelago/src/container/docker_packages.rs index 120ccb67..f88cc862 100644 --- a/core/archipelago/src/container/docker_packages.rs +++ b/core/archipelago/src/container/docker_packages.rs @@ -360,11 +360,11 @@ fn get_app_metadata(app_id: &str) -> AppMetadata { repo: "https://github.com/searxng/searxng".to_string(), tier: "", }, - "onlyoffice" | "onlyoffice-documentserver" => AppMetadata { - title: "OnlyOffice".to_string(), - description: "Office suite and document collaboration".to_string(), - icon: "/assets/img/app-icons/onlyoffice.webp".to_string(), - repo: "https://github.com/ONLYOFFICE/DocumentServer".to_string(), + "cryptpad" => AppMetadata { + title: "CryptPad".to_string(), + description: "End-to-end encrypted document collaboration".to_string(), + icon: "/assets/img/app-icons/cryptpad.webp".to_string(), + repo: "https://github.com/cryptpad/cryptpad".to_string(), tier: "", }, "penpot" | "penpot-frontend" => AppMetadata { diff --git a/core/archipelago/src/electrs_status.rs b/core/archipelago/src/electrs_status.rs index 7daf30b3..a44b0a2c 100644 --- a/core/archipelago/src/electrs_status.rs +++ b/core/archipelago/src/electrs_status.rs @@ -165,9 +165,9 @@ pub async fn get_electrs_sync_status() -> ElectrsSyncStatus { let tor_onion = { let mut onion = None; for path in &[ - "/var/lib/archipelago/tor-hostnames/electrs", - "/var/lib/tor/hidden_service_electrs/hostname", - "/var/lib/archipelago/tor/hidden_service_electrs/hostname", + "/var/lib/archipelago/tor-hostnames/electrumx", + "/var/lib/tor/hidden_service_electrumx/hostname", + "/var/lib/archipelago/tor/hidden_service_electrumx/hostname", ] { if let Ok(addr) = tokio::fs::read_to_string(path).await { let addr = addr.trim().to_string(); diff --git a/core/container/src/podman_client.rs b/core/container/src/podman_client.rs index b69a58e3..63ba5c39 100644 --- a/core/container/src/podman_client.rs +++ b/core/container/src/podman_client.rs @@ -116,7 +116,7 @@ impl PodmanClient { "grafana" => "http://localhost:3000", "searxng" => "http://localhost:8888", "ollama" => "http://localhost:11434", - "onlyoffice" => "http://localhost:9980", + "cryptpad" => "http://localhost:3003", "penpot" => "http://localhost:9001", "nextcloud" => "http://localhost:8085", "vaultwarden" => "http://localhost:8082", diff --git a/docker/electrs-ui/index.html b/docker/electrs-ui/index.html index 09463362..1d4f23d3 100644 --- a/docker/electrs-ui/index.html +++ b/docker/electrs-ui/index.html @@ -355,7 +355,10 @@ async function updateStatus() { try { - var resp = await fetch('electrs-status'); + var resp = await fetch('/electrs-status'); + if (!resp.ok) { + throw new Error('Backend unavailable (HTTP ' + resp.status + ')'); + } var data = await resp.json(); // Extract Tor onion from status response @@ -410,8 +413,14 @@ document.getElementById('connSubtitle').textContent = 'Connections will be available once ElectrumX has completed syncing.'; } } catch (e) { - document.getElementById('syncStatusText').textContent = 'Unable to fetch status: ' + e.message; - document.getElementById('syncStatusText').style.color = '#f87171'; + var msg = e.message || 'Unknown error'; + if (msg.indexOf('HTTP 5') !== -1 || msg.indexOf('Failed to fetch') !== -1 || msg.indexOf('NetworkError') !== -1) { + msg = 'Waiting for Archipelago backend...'; + } + document.getElementById('syncStatusText').textContent = msg; + document.getElementById('syncStatusText').style.color = '#fbbf24'; + document.getElementById('statusDot').className = 'status-dot bg-yellow animate-pulse'; + document.getElementById('statusText').textContent = 'Connecting'; } } diff --git a/docker/electrs-ui/nginx.conf b/docker/electrs-ui/nginx.conf index b053e560..d9224af6 100644 --- a/docker/electrs-ui/nginx.conf +++ b/docker/electrs-ui/nginx.conf @@ -5,14 +5,8 @@ server { root /usr/share/nginx/html; index index.html; - location /electrs-status { - proxy_pass http://127.0.0.1:5678/electrs-status; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - add_header Access-Control-Allow-Origin *; - } - + # electrs-status is fetched via absolute /electrs-status path, + # handled by the host nginx → backend at :5678 directly location / { try_files $uri $uri/ /index.html; } diff --git a/docker/lnd-ui/nginx.conf b/docker/lnd-ui/nginx.conf index 27d382ef..affa3170 100644 --- a/docker/lnd-ui/nginx.conf +++ b/docker/lnd-ui/nginx.conf @@ -5,13 +5,8 @@ server { root /usr/share/nginx/html; index index.html; - location /lnd-connect-info { - proxy_pass http://127.0.0.1:5678/lnd-connect-info; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } - + # lnd-connect-info is fetched via absolute URL path, + # handled by the host nginx → backend at :5678 directly location / { try_files $uri $uri/ /index.html; } diff --git a/image-recipe/build-auto-installer-iso.sh b/image-recipe/build-auto-installer-iso.sh index ffbac8b1..7a1ed697 100755 --- a/image-recipe/build-auto-installer-iso.sh +++ b/image-recipe/build-auto-installer-iso.sh @@ -300,6 +300,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ plymouth \ plymouth-themes \ zstd \ + socat \ python3 \ apache2-utils \ && apt-get clean \ @@ -1250,6 +1251,7 @@ HiddenServicePort 80 127.0.0.1:80 HiddenServiceDir $TOR_DIR/hidden_service_bitcoin HiddenServicePort 8333 127.0.0.1:8333 +HiddenServicePort 8332 127.0.0.1:8332 HiddenServiceDir $TOR_DIR/hidden_service_electrumx HiddenServicePort 50001 127.0.0.1:50001 @@ -1475,6 +1477,41 @@ FBCSERVICE fi fi +# Bootstrap node config — new installs use this Bitcoin node during IBD +# so ElectrumX/LND/BTCPay/Mempool work immediately while local chain syncs +# Tries LAN first (fast), falls back to Tor (works from anywhere) +BOOTSTRAP_RPC_PASS="" +BOOTSTRAP_ONION="" +if [ -f /var/lib/archipelago/secrets/bitcoin-rpc-password ]; then + BOOTSTRAP_RPC_PASS=$(cat /var/lib/archipelago/secrets/bitcoin-rpc-password 2>/dev/null) +fi +if [ -f /var/lib/archipelago/tor-hostnames/bitcoin ]; then + BOOTSTRAP_ONION=$(cat /var/lib/archipelago/tor-hostnames/bitcoin 2>/dev/null) +fi +if [ -n "$BOOTSTRAP_RPC_PASS" ]; then + DEV_IP="${DEV_SERVER:-192.168.1.228}" + cat > "$ARCH_DIR/bootstrap.conf" </dev/null || true mkdir -p /mnt/target/etc/tmpfiles.d echo 'd /run/user/1000 0700 archipelago archipelago -' > /mnt/target/etc/tmpfiles.d/archipelago-runtime.conf +# Bootstrap switchover — checks when local Bitcoin finishes IBD and switches services +cat > /mnt/target/etc/systemd/system/archipelago-bootstrap-switchover.service <<'BSSERVICE' +[Unit] +Description=Switch Bitcoin-dependent services from bootstrap to local node +After=archipelago-first-boot-containers.service + +[Service] +Type=oneshot +User=archipelago +ExecStart=/opt/archipelago/scripts/bootstrap-switchover.sh +BSSERVICE + +cat > /mnt/target/etc/systemd/system/archipelago-bootstrap-switchover.timer <<'BSTIMER' +[Unit] +Description=Periodically check if local Bitcoin is synced and switch from bootstrap + +[Timer] +OnBootSec=10min +OnUnitActiveSec=5min +Persistent=true + +[Install] +WantedBy=timers.target +BSTIMER + +# Copy bootstrap config to install target +if [ -f "$BOOT_MEDIA/archipelago/bootstrap.conf" ]; then + cp "$BOOT_MEDIA/archipelago/bootstrap.conf" /mnt/target/opt/archipelago/bootstrap.conf + chmod 600 /mnt/target/opt/archipelago/bootstrap.conf + chown root:root /mnt/target/opt/archipelago/bootstrap.conf +fi + +# Copy bootstrap switchover script +if [ -f "$BOOT_MEDIA/archipelago/scripts/bootstrap-switchover.sh" ]; then + cp "$BOOT_MEDIA/archipelago/scripts/bootstrap-switchover.sh" /mnt/target/opt/archipelago/scripts/ + chmod +x /mnt/target/opt/archipelago/scripts/bootstrap-switchover.sh +fi + # Enable services +chroot /mnt/target systemctl enable archipelago-bootstrap-switchover.timer 2>/dev/null || true chroot /mnt/target systemctl enable archipelago.service 2>/dev/null || true chroot /mnt/target systemctl enable nginx.service 2>/dev/null || true chroot /mnt/target systemctl enable archipelago-load-images.service 2>/dev/null || true diff --git a/image-recipe/configs/nginx-archipelago.conf b/image-recipe/configs/nginx-archipelago.conf index 2ced458d..0308f265 100644 --- a/image-recipe/configs/nginx-archipelago.conf +++ b/image-recipe/configs/nginx-archipelago.conf @@ -67,6 +67,7 @@ server { proxy_cache off; proxy_connect_timeout 120s; proxy_read_timeout 300s; + proxy_send_timeout 120s; } # AIUI web search proxy — SearXNG on port 8888 @@ -84,6 +85,16 @@ server { return 503 '{"error":"SearXNG is not running"}'; } + # JSON error responses — prevents leaking HTML error pages to API clients + location @backend_unavailable { + default_type application/json; + return 502 '{"error":{"code":"BACKEND_UNAVAILABLE","message":"Service temporarily unavailable"}}'; + } + location @backend_timeout { + default_type application/json; + return 504 '{"error":{"code":"BACKEND_TIMEOUT","message":"Service did not respond in time"}}'; + } + # Icons, favicon, manifest — always revalidate (no heuristic caching) location ~* ^/(favicon\.ico|manifest\.webmanifest|assets/icon/) { add_header Cache-Control "no-cache, must-revalidate"; @@ -94,7 +105,7 @@ server { location / { try_files $uri $uri/ /index.html; } - + # Peer-to-peer node messaging (receives from other nodes over Tor) location /archipelago/ { limit_req zone=peer burst=20 nodelay; @@ -106,6 +117,8 @@ server { proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; + error_page 502 503 = @backend_unavailable; + error_page 504 = @backend_timeout; } # Proxy API requests to backend @@ -116,7 +129,6 @@ server { proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; - # Connection header managed by nginx default # Limit request body to 1MB for RPC calls client_max_body_size 1m; @@ -125,6 +137,8 @@ server { proxy_connect_timeout 600s; proxy_send_timeout 600s; proxy_read_timeout 600s; + error_page 502 503 = @backend_unavailable; + error_page 504 = @backend_timeout; } # Backend status endpoints (must be before the SPA catch-all) @@ -132,23 +146,35 @@ server { proxy_pass http://127.0.0.1:5678/health; proxy_http_version 1.1; proxy_set_header Host $host; + proxy_connect_timeout 5s; + proxy_read_timeout 10s; + proxy_send_timeout 5s; + error_page 502 503 = @backend_unavailable; + error_page 504 = @backend_timeout; } location /electrs-status { proxy_pass http://127.0.0.1:5678/electrs-status; proxy_http_version 1.1; proxy_set_header Host $host; - # CORS handled by backend + proxy_connect_timeout 10s; + proxy_read_timeout 10s; + proxy_send_timeout 5s; + error_page 502 503 = @backend_unavailable; + error_page 504 = @backend_timeout; } location /lnd-connect-info { - # Backend is localhost-only (127.0.0.1:5678) — no cookie gate needed - # CORS: LND UI runs on :8081 (cross-origin to :80), needs CORS headers proxy_pass http://127.0.0.1:5678/lnd-connect-info; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Cookie $http_cookie; + proxy_connect_timeout 10s; + proxy_read_timeout 10s; + proxy_send_timeout 5s; add_header Access-Control-Allow-Origin $http_origin always; add_header Access-Control-Allow-Credentials "true" always; + error_page 502 503 = @backend_unavailable; + error_page 504 = @backend_timeout; } # Content sharing — peer access over Tor (no auth) @@ -162,6 +188,8 @@ server { proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; + error_page 502 503 = @backend_unavailable; + error_page 504 = @backend_timeout; } # DWN endpoints — peer access over Tor (no auth) @@ -175,6 +203,8 @@ server { proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; + error_page 502 503 = @backend_unavailable; + error_page 504 = @backend_timeout; } # Proxy apps that set X-Frame-Options - strip header so iframe works @@ -481,20 +511,8 @@ server { } location /app/tailscale/ { # Tailscale has no web UI — managed via CLI/Tailscale app - # proxy_pass placeholder for future Tailscale admin UI - return 503; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_hide_header X-Frame-Options; - add_header X-Frame-Options "SAMEORIGIN" always; - proxy_hide_header Content-Security-Policy; - add_header X-Content-Type-Options "nosniff" always; - proxy_set_header Accept-Encoding ""; - sub_filter_once on; - sub_filter '' ''; + default_type application/json; + return 503 '{"error":{"code":"NO_WEB_UI","message":"Tailscale is managed via CLI"}}'; } location /app/ollama/ { proxy_pass http://127.0.0.1:11434/; @@ -615,7 +633,6 @@ server { proxy_hide_header X-Frame-Options; add_header X-Frame-Options "SAMEORIGIN" always; proxy_hide_header Content-Security-Policy; - add_header X-Content-Type-Options "nosniff" always; proxy_hide_header Cross-Origin-Embedder-Policy; proxy_hide_header Cross-Origin-Opener-Policy; proxy_hide_header Cross-Origin-Resource-Policy; @@ -676,7 +693,6 @@ server { proxy_hide_header X-Frame-Options; add_header X-Frame-Options "SAMEORIGIN" always; proxy_hide_header Content-Security-Policy; - add_header X-Content-Type-Options "nosniff" always; proxy_hide_header Cross-Origin-Embedder-Policy; proxy_hide_header Cross-Origin-Opener-Policy; proxy_hide_header Cross-Origin-Resource-Policy; @@ -727,6 +743,16 @@ server { add_header X-DNS-Prefetch-Control "off" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://*.basemaps.cartocdn.com https://tile.openstreetmap.org; font-src 'self' data:; connect-src 'self' ws: wss: http://$host:* https:; frame-src 'self' http://$host:* https:; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always; + # JSON error responses — prevents leaking HTML error pages to API clients + location @backend_unavailable { + default_type application/json; + return 502 '{"error":{"code":"BACKEND_UNAVAILABLE","message":"Service temporarily unavailable"}}'; + } + location @backend_timeout { + default_type application/json; + return 504 '{"error":{"code":"BACKEND_TIMEOUT","message":"Service did not respond in time"}}'; + } + # AIUI SPA (Chat mode iframe) — SPA fallback for client-side routing location /aiui/ { alias /opt/archipelago/web-ui/aiui/; @@ -754,6 +780,7 @@ server { proxy_cache off; proxy_connect_timeout 120s; proxy_read_timeout 300s; + proxy_send_timeout 120s; } location /aiui/api/openrouter/ { proxy_pass https://openrouter.ai/api/; @@ -785,29 +812,43 @@ server { proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; + error_page 502 503 = @backend_unavailable; + error_page 504 = @backend_timeout; } location /health { proxy_pass http://127.0.0.1:5678/health; proxy_http_version 1.1; proxy_set_header Host $host; + proxy_connect_timeout 5s; + proxy_read_timeout 10s; + proxy_send_timeout 5s; + error_page 502 503 = @backend_unavailable; + error_page 504 = @backend_timeout; } location /electrs-status { proxy_pass http://127.0.0.1:5678/electrs-status; proxy_http_version 1.1; proxy_set_header Host $host; - # CORS handled by backend + proxy_connect_timeout 10s; + proxy_read_timeout 10s; + proxy_send_timeout 5s; + error_page 502 503 = @backend_unavailable; + error_page 504 = @backend_timeout; } location /lnd-connect-info { - # Backend is localhost-only (127.0.0.1:5678) — no cookie gate needed - # CORS: LND UI runs on :8081 (cross-origin to :80), needs CORS headers proxy_pass http://127.0.0.1:5678/lnd-connect-info; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Cookie $http_cookie; + proxy_connect_timeout 10s; + proxy_read_timeout 10s; + proxy_send_timeout 5s; add_header Access-Control-Allow-Origin $http_origin always; add_header Access-Control-Allow-Credentials "true" always; + error_page 502 503 = @backend_unavailable; + error_page 504 = @backend_timeout; } # Content sharing — peer access over Tor (no auth) @@ -821,6 +862,8 @@ server { proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; + error_page 502 503 = @backend_unavailable; + error_page 504 = @backend_timeout; } # DWN endpoints — peer access over Tor (no auth) @@ -834,6 +877,8 @@ server { proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; + error_page 502 503 = @backend_unavailable; + error_page 504 = @backend_timeout; } location /rpc/ { @@ -843,7 +888,6 @@ server { proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; - # Connection header managed by nginx default # Limit request body to 1MB for RPC calls client_max_body_size 1m; @@ -851,6 +895,8 @@ server { proxy_connect_timeout 600s; proxy_send_timeout 600s; proxy_read_timeout 600s; + error_page 502 503 = @backend_unavailable; + error_page 504 = @backend_timeout; } location /app/nextcloud/ { @@ -965,7 +1011,6 @@ server { proxy_hide_header X-Frame-Options; add_header X-Frame-Options "SAMEORIGIN" always; proxy_hide_header Content-Security-Policy; - add_header X-Content-Type-Options "nosniff" always; proxy_hide_header Cross-Origin-Embedder-Policy; proxy_hide_header Cross-Origin-Opener-Policy; proxy_hide_header Cross-Origin-Resource-Policy; @@ -1026,7 +1071,6 @@ server { proxy_hide_header X-Frame-Options; add_header X-Frame-Options "SAMEORIGIN" always; proxy_hide_header Content-Security-Policy; - add_header X-Content-Type-Options "nosniff" always; proxy_hide_header Cross-Origin-Embedder-Policy; proxy_hide_header Cross-Origin-Opener-Policy; proxy_hide_header Cross-Origin-Resource-Policy; diff --git a/image-recipe/configs/snippets/archipelago-https-app-proxies.conf b/image-recipe/configs/snippets/archipelago-https-app-proxies.conf index d54bfe9b..f1d3b4e2 100644 --- a/image-recipe/configs/snippets/archipelago-https-app-proxies.conf +++ b/image-recipe/configs/snippets/archipelago-https-app-proxies.conf @@ -193,19 +193,9 @@ location /app/fedimint-gateway/ { sub_filter '' ''; } location /app/tailscale/ { - # Tailscale: no web UI - return 503; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_hide_header X-Frame-Options; - add_header X-Frame-Options "SAMEORIGIN" always; - proxy_hide_header Content-Security-Policy; - proxy_set_header Accept-Encoding ""; - sub_filter_once on; - sub_filter '' ''; + # Tailscale has no web UI — managed via CLI/Tailscale app + default_type application/json; + return 503 '{"error":{"code":"NO_WEB_UI","message":"Tailscale is managed via CLI"}}'; } location /app/ollama/ { proxy_pass http://127.0.0.1:11434/; diff --git a/scripts/bootstrap-switchover.sh b/scripts/bootstrap-switchover.sh new file mode 100755 index 00000000..171ace6b --- /dev/null +++ b/scripts/bootstrap-switchover.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# bootstrap-switchover.sh — Switches Bitcoin-dependent services from bootstrap node to local +# Runs periodically via systemd timer. Once local Bitcoin finishes IBD, recreates +# ElectrumX/Mempool/LND/BTCPay/Fedimint containers pointing at the local node. +set -euo pipefail + +BOOTSTRAP_FLAG="/var/lib/archipelago/.bootstrap-active" +LOG="/var/log/archipelago-bootstrap-switchover.log" +SECRETS_DIR="/var/lib/archipelago/secrets" +DOCKER=podman +command -v podman >/dev/null 2>&1 || DOCKER=docker + +log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $*" | tee -a "$LOG"; } + +# Only run if bootstrap mode is active +if [ ! -f "$BOOTSTRAP_FLAG" ]; then + exit 0 +fi + +# Check if local Bitcoin is past IBD +RPC_PASS=$(cat "$SECRETS_DIR/bitcoin-rpc-password" 2>/dev/null) +if [ -z "$RPC_PASS" ]; then + log "No local Bitcoin RPC password — skipping" + exit 0 +fi + +IBD_STATUS=$($DOCKER exec bitcoin-knots bitcoin-cli -datadir=/home/bitcoin/.bitcoin getblockchaininfo 2>/dev/null | python3 -c " +import sys, json +try: + d = json.load(sys.stdin) + print(f\"{d.get('initialblockdownload', True)}|{d.get('blocks', 0)}|{d.get('headers', 0)}\") +except: + print('True|0|0') +" 2>/dev/null) || IBD_STATUS="True|0|0" + +IBD=$(echo "$IBD_STATUS" | cut -d'|' -f1) +BLOCKS=$(echo "$IBD_STATUS" | cut -d'|' -f2) +HEADERS=$(echo "$IBD_STATUS" | cut -d'|' -f3) + +if [ "$IBD" != "False" ]; then + log "Local Bitcoin still in IBD (blocks=$BLOCKS headers=$HEADERS) — keeping bootstrap" + exit 0 +fi + +log "=== Local Bitcoin synced (blocks=$BLOCKS) — switching from bootstrap to local node ===" + +# Source image versions +for img_src in /opt/archipelago/scripts/image-versions.sh /home/archipelago/archy/scripts/image-versions.sh; do + [ -f "$img_src" ] && . "$img_src" && break +done + +RPC_USER="archipelago" + +# Helper: recreate a container with local Bitcoin config +recreate_container() { + local name="$1" + shift + log "Recreating $name..." + $DOCKER stop "$name" 2>/dev/null || true + $DOCKER rm -f "$name" 2>/dev/null || true + if $DOCKER run -d "$@" 2>>"$LOG"; then + log " $name switched to local Bitcoin" + else + log " WARNING: Failed to recreate $name" + fi +} + +# ElectrumX — key service for wallet connections +if $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q '^electrumx$'; then + recreate_container electrumx \ + --name electrumx --restart unless-stopped \ + --health-cmd="python3 -c 'import socket; socket.create_connection((\"localhost\",8000),2).close()' || exit 1" \ + --health-interval=120s --health-timeout=5s --health-retries=3 \ + --memory=1g --network archy-net --network-alias electrumx \ + --cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \ + --security-opt no-new-privileges:true \ + -p 50001:50001 -v /var/lib/archipelago/electrumx:/data \ + -e "DAEMON_URL=http://${RPC_USER}:${RPC_PASS}@bitcoin-knots:8332/" \ + -e COIN=Bitcoin -e DB_DIRECTORY=/data \ + -e "SERVICES=tcp://:50001,rpc://0.0.0.0:8000" \ + "${ELECTRUMX_IMAGE:-80.71.235.15:3000/archipelago/electrumx:v1.18.0}" +fi + +# Mempool API +if $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q '^mempool-api$'; then + recreate_container mempool-api \ + --name mempool-api --restart unless-stopped \ + --health-cmd="curl -sf http://localhost:8999/api/v1/backend-info || exit 1" \ + --health-interval=120s --health-timeout=5s --health-retries=3 \ + --memory=512m --network archy-net --network-alias mempool-api \ + --cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \ + --security-opt no-new-privileges:true \ + -v /var/lib/archipelago/mempool-data:/backend/cache \ + -e "MEMPOOL_BACKEND=electrum" \ + -e "CORE_RPC_HOST=bitcoin-knots" -e "CORE_RPC_PORT=8332" \ + -e "CORE_RPC_USERNAME=${RPC_USER}" -e "CORE_RPC_PASSWORD=${RPC_PASS}" \ + -e "ELECTRUM_HOST=electrumx" -e "ELECTRUM_PORT=50001" -e "ELECTRUM_TLS_ENABLED=false" \ + -e "DATABASE_ENABLED=true" -e "DATABASE_HOST=archy-mempool-db" \ + -e "DATABASE_DATABASE=mempool" -e "DATABASE_USERNAME=mempool" \ + -e "DATABASE_PASSWORD=$(cat "$SECRETS_DIR/mempool-db-password" 2>/dev/null || echo mempoolpass)" \ + "${MEMPOOL_API_IMAGE:-80.71.235.15:3000/archipelago/mempool-api:v3.2.0}" +fi + +# Stop Tor tunnel if it was active +if systemctl is-active archipelago-bootstrap-tunnel.service >/dev/null 2>&1; then + log "Stopping bootstrap Tor tunnel..." + systemctl stop archipelago-bootstrap-tunnel.service 2>/dev/null || true + systemctl disable archipelago-bootstrap-tunnel.service 2>/dev/null || true +fi + +# Done — remove bootstrap flag +rm -f "$BOOTSTRAP_FLAG" +log "=== Bootstrap switchover complete — all services now using local Bitcoin node ===" diff --git a/scripts/first-boot-containers.sh b/scripts/first-boot-containers.sh index cb3d20db..8732dfd7 100644 --- a/scripts/first-boot-containers.sh +++ b/scripts/first-boot-containers.sh @@ -111,7 +111,12 @@ if [ ! -f "$SECRETS_DIR/bitcoin-rpc-password" ]; then chmod 600 "$SECRETS_DIR/bitcoin-rpc-password" fi BITCOIN_RPC_USER="archipelago" -BITCOIN_RPC_PASS=$(cat "$SECRETS_DIR/bitcoin-rpc-password") +BITCOIN_RPC_PASS=$(cat "$SECRETS_DIR/bitcoin-rpc-password" 2>/dev/null) +if [ -z "$BITCOIN_RPC_PASS" ]; then + log "FATAL: Bitcoin RPC password is empty — secrets file missing or unreadable" + log " Expected: $SECRETS_DIR/bitcoin-rpc-password" + exit 1 +fi # Generate rpcauth line for bitcoin.conf (salted HMAC-SHA256 hash) generate_rpcauth() { @@ -273,7 +278,7 @@ log "Fixing rootless podman UID mapping..." # Containers running as root (UID 0 → host UID 100000) for dir in lnd electrumx btcpay nbxplorer jellyfin vaultwarden \ home-assistant fedimint fedimint-gateway photoprism ollama filebrowser \ - nextcloud uptime-kuma onlyoffice nginx-proxy-manager portainer nostr-rs-relay; do + nextcloud uptime-kuma nginx-proxy-manager portainer nostr-rs-relay; do [ -d "/var/lib/archipelago/$dir" ] && chown -R 100000:100000 "/var/lib/archipelago/$dir" 2>/dev/null done # Bitcoin Knots: container UID 101 → host UID 100101 @@ -300,7 +305,7 @@ LOW_MEM=false mem_limit() { case "$1" in bitcoin-knots) $LOW_MEM && echo "2g" || echo "4g";; - onlyoffice) $LOW_MEM && echo "1g" || echo "2g";; + cryptpad) echo "512m";; ollama) $LOW_MEM && echo "1g" || echo "4g";; lnd) echo "512m";; electrumx) echo "1g";; @@ -387,7 +392,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'bitcoin-knots|arch log " Large disk (${DISK_GB}GB) — enabling txindex" fi if $DOCKER run -d --name bitcoin-knots --restart unless-stopped \ - --health-cmd="bitcoin-cli -rpcuser=$BITCOIN_RPC_USER -rpcpassword=$BITCOIN_RPC_PASS getblockchaininfo || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \ + --health-cmd="bitcoin-cli -datadir=/home/bitcoin/.bitcoin getblockchaininfo || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \ --memory=$(mem_limit bitcoin-knots) --network archy-net --network-alias bitcoin-knots \ $ADD_HOST_FLAG \ --cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \ @@ -410,7 +415,7 @@ fi # Check Bitcoin Knots RPC (informational — containers created regardless) # Dependent containers use --restart=unless-stopped and the health monitor # will auto-restart them once Bitcoin becomes responsive. -if wait_for_container "Bitcoin Knots RPC" "$DOCKER exec bitcoin-knots bitcoin-cli -rpcuser='$BITCOIN_RPC_USER' -rpcpassword='$BITCOIN_RPC_PASS' getblockchaininfo" 60; then +if wait_for_container "Bitcoin Knots RPC" "$DOCKER exec bitcoin-knots bitcoin-cli -datadir=/home/bitcoin/.bitcoin getblockchaininfo" 60; then BITCOIN_READY=true log "Bitcoin Knots is ready" else @@ -421,12 +426,104 @@ fi track_container "bitcoin-knots" # Ensure wallet exists (Bitcoin Knots no longer auto-creates a default wallet) -if ! $DOCKER exec bitcoin-knots bitcoin-cli "-rpcuser=$BITCOIN_RPC_USER" "-rpcpassword=$BITCOIN_RPC_PASS" listwallets 2>/dev/null | grep -q "archipelago"; then - $DOCKER exec bitcoin-knots bitcoin-cli "-rpcuser=$BITCOIN_RPC_USER" "-rpcpassword=$BITCOIN_RPC_PASS" loadwallet "archipelago" 2>/dev/null || \ - $DOCKER exec bitcoin-knots bitcoin-cli "-rpcuser=$BITCOIN_RPC_USER" "-rpcpassword=$BITCOIN_RPC_PASS" createwallet "archipelago" 2>/dev/null +if ! $DOCKER exec bitcoin-knots bitcoin-cli -datadir=/home/bitcoin/.bitcoin listwallets 2>/dev/null | grep -q "archipelago"; then + $DOCKER exec bitcoin-knots bitcoin-cli -datadir=/home/bitcoin/.bitcoin loadwallet "archipelago" 2>/dev/null || \ + $DOCKER exec bitcoin-knots bitcoin-cli -datadir=/home/bitcoin/.bitcoin createwallet "archipelago" 2>/dev/null log "Bitcoin Knots wallet 'archipelago' created/loaded" fi +# ── Bootstrap: use a remote Bitcoin node during IBD ─────────────────── +# If the local node is still syncing (IBD=true), point dependent services at +# a fully-synced bootstrap node so wallets/payments work immediately. +BOOTSTRAP_CONF="/opt/archipelago/bootstrap.conf" +BOOTSTRAP_FLAG="/var/lib/archipelago/.bootstrap-active" +USE_BOOTSTRAP=false +BTC_HOST="bitcoin-knots" # default: local container via archy-net DNS +BTC_RPC_USER="$BITCOIN_RPC_USER" +BTC_RPC_PASS="$BITCOIN_RPC_PASS" + +if [ -f "$BOOTSTRAP_CONF" ]; then + . "$BOOTSTRAP_CONF" + if [ -n "${BOOTSTRAP_RPC_PASS:-}" ]; then + # Check if local Bitcoin is in IBD + LOCAL_IBD=$($DOCKER exec bitcoin-knots bitcoin-cli -datadir=/home/bitcoin/.bitcoin getblockchaininfo 2>/dev/null \ + | python3 -c "import sys,json; print(json.load(sys.stdin).get('initialblockdownload',True))" 2>/dev/null) || LOCAL_IBD="True" + if [ "$LOCAL_IBD" = "True" ]; then + BOOT_USER="${BOOTSTRAP_RPC_USER:-archipelago}" + BOOT_TEST='{"jsonrpc":"1.0","id":"boot","method":"getblockcount","params":[]}' + + # Try 1: LAN (fast, ~1ms) + if [ -n "${BOOTSTRAP_LAN_HOST:-}" ] && \ + curl -sf --max-time 5 -u "${BOOT_USER}:${BOOTSTRAP_RPC_PASS}" \ + -H "Content-Type: application/json" -d "$BOOT_TEST" \ + "http://${BOOTSTRAP_LAN_HOST}:8332/" >/dev/null 2>&1; then + USE_BOOTSTRAP=true + BTC_HOST="$BOOTSTRAP_LAN_HOST" + BTC_RPC_USER="$BOOT_USER" + BTC_RPC_PASS="$BOOTSTRAP_RPC_PASS" + touch "$BOOTSTRAP_FLAG" + echo "lan" > "$BOOTSTRAP_FLAG" + log "BOOTSTRAP: Local Bitcoin in IBD — using LAN ${BOOTSTRAP_LAN_HOST} for dependent services" + + # Try 2: Tor (works from any network, ~5-15s) + elif [ -n "${BOOTSTRAP_ONION:-}" ] && command -v socat >/dev/null 2>&1; then + log "BOOTSTRAP: LAN unreachable, trying Tor (${BOOTSTRAP_ONION})..." + # Create a socat tunnel: localhost:18332 → onion:8332 via Tor SOCKS + socat TCP-LISTEN:18332,bind=127.0.0.1,reuseaddr,fork \ + SOCKS4A:127.0.0.1:${BOOTSTRAP_ONION}:8332,socksport=9050 & + SOCAT_PID=$! + sleep 3 + if curl -sf --max-time 30 -u "${BOOT_USER}:${BOOTSTRAP_RPC_PASS}" \ + -H "Content-Type: application/json" -d "$BOOT_TEST" \ + "http://127.0.0.1:18332/" >/dev/null 2>&1; then + USE_BOOTSTRAP=true + # Containers reach host via host.containers.internal (set by $ADD_HOST_FLAG) + BTC_HOST="${HOST_GATEWAY:-$TARGET_IP}" + BTC_HOST_PORT=18332 + BTC_RPC_USER="$BOOT_USER" + BTC_RPC_PASS="$BOOTSTRAP_RPC_PASS" + echo "tor:$SOCAT_PID" > "$BOOTSTRAP_FLAG" + log "BOOTSTRAP: Using Tor tunnel (socat pid=$SOCAT_PID) for dependent services" + # Persist the tunnel as a systemd service so it survives first-boot + cat > /etc/systemd/system/archipelago-bootstrap-tunnel.service </dev/null || true + # Kill the ad-hoc socat — systemd takes over + kill "$SOCAT_PID" 2>/dev/null || true + else + kill "$SOCAT_PID" 2>/dev/null || true + log "BOOTSTRAP: Tor tunnel test failed — using local Bitcoin" + fi + else + log "BOOTSTRAP: No reachable bootstrap node — using local Bitcoin" + fi + + if [ "$USE_BOOTSTRAP" = "true" ]; then + log " Services will auto-switch to local node when synced (bootstrap-switchover timer)" + fi + else + log "BOOTSTRAP: Local Bitcoin already synced — no bootstrap needed" + fi + fi +fi + +# Override port if Tor tunnel is active (containers use host gateway:18332 instead of :8332) +BTC_PORT=${BTC_HOST_PORT:-8332} + # 2. Mempool stack (matches deploy) — depends on Bitcoin # Note: containers created regardless of BITCOIN_READY — they will restart # automatically once Bitcoin becomes responsive (--restart=unless-stopped). @@ -461,7 +558,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q electrumx; then --cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \ --security-opt no-new-privileges:true \ -p 50001:50001 -v /var/lib/archipelago/electrumx:/data \ - -e "DAEMON_URL=http://$BITCOIN_RPC_USER:$BITCOIN_RPC_PASS@bitcoin-knots:8332/" \ + -e "DAEMON_URL=http://$BTC_RPC_USER:$BTC_RPC_PASS@$BTC_HOST:$BTC_PORT/" \ -e COIN=Bitcoin -e DB_DIRECTORY=/data \ -e SERVICES=tcp://:50001,rpc://0.0.0.0:8000 \ "$ELECTRUMX_IMAGE" 2>>"$LOG" || true @@ -479,8 +576,8 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q mempool-api; then --security-opt no-new-privileges:true \ -p 8999:8999 -v /var/lib/archipelago/mempool:/data \ -e MEMPOOL_BACKEND=electrum -e ELECTRUM_HOST=electrumx -e ELECTRUM_PORT=50001 \ - -e ELECTRUM_TLS_ENABLED=false -e CORE_RPC_HOST="$TARGET_IP" -e CORE_RPC_PORT=8332 \ - -e "CORE_RPC_USERNAME=$BITCOIN_RPC_USER" -e "CORE_RPC_PASSWORD=$BITCOIN_RPC_PASS" \ + -e ELECTRUM_TLS_ENABLED=false -e "CORE_RPC_HOST=$BTC_HOST" -e CORE_RPC_PORT=8332 \ + -e "CORE_RPC_USERNAME=$BTC_RPC_USER" -e "CORE_RPC_PASSWORD=$BTC_RPC_PASS" \ -e DATABASE_ENABLED=true -e DATABASE_HOST="$MYSQL_CNT" -e DATABASE_DATABASE=mempool \ -e DATABASE_USERNAME=mempool -e "DATABASE_PASSWORD=$MEMPOOL_DB_PASS" \ "$MEMPOOL_BACKEND_IMAGE" 2>>"$LOG" || true @@ -560,8 +657,8 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q archy-nbxplorer; the --security-opt no-new-privileges:true \ -p 32838:32838 -v /var/lib/archipelago/nbxplorer:/data \ -e NBXPLORER_DATADIR=/data -e NBXPLORER_NETWORK=mainnet -e NBXPLORER_CHAINS=btc \ - -e NBXPLORER_BIND=0.0.0.0:32838 -e NBXPLORER_BTCRPCURL=http://bitcoin-knots:8332 \ - -e "NBXPLORER_BTCRPCUSER=$BITCOIN_RPC_USER" -e "NBXPLORER_BTCRPCPASSWORD=$BITCOIN_RPC_PASS" \ + -e NBXPLORER_BIND=0.0.0.0:32838 -e "NBXPLORER_BTCRPCURL=http://$BTC_HOST:$BTC_PORT" \ + -e "NBXPLORER_BTCRPCUSER=$BTC_RPC_USER" -e "NBXPLORER_BTCRPCPASSWORD=$BTC_RPC_PASS" \ -e NBXPLORER_POSTGRES='User ID=btcpay;Password=$BTCPAY_DB_PASS;Host=archy-btcpay-db;Port=5432;Database=nbxplorer;Include Error Detail=true' \ "$NBXPLORER_IMAGE" 2>>"$LOG" && sleep 5 || true fi @@ -580,8 +677,8 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q btcpay-server; then -e ASPNETCORE_URLS=http://0.0.0.0:49392 -e BTCPAY_PROTOCOL=http \ -e BTCPAY_HOST="$TARGET_IP:23000" -e BTCPAY_CHAINS=btc \ -e BTCPAY_BTCEXPLORERURL=http://archy-nbxplorer:32838 \ - -e BTCPAY_BTCRPCURL=http://bitcoin-knots:8332 \ - -e "BTCPAY_BTCRPCUSER=$BITCOIN_RPC_USER" -e "BTCPAY_BTCRPCPASSWORD=$BITCOIN_RPC_PASS" \ + -e "BTCPAY_BTCRPCURL=http://$BTC_HOST:$BTC_PORT" \ + -e "BTCPAY_BTCRPCUSER=$BTC_RPC_USER" -e "BTCPAY_BTCRPCPASSWORD=$BTC_RPC_PASS" \ -e BTCPAY_POSTGRES='User ID=btcpay;Password=$BTCPAY_DB_PASS;Host=archy-btcpay-db;Port=5432;Database=btcpay;Include Error Detail=true' \ "$BTCPAY_IMAGE" 2>>"$LOG" || true fi @@ -615,9 +712,9 @@ bitcoin.mainnet=true bitcoin.node=bitcoind [Bitcoind] -bitcoind.rpchost=bitcoin-knots:8332 -bitcoind.rpcuser=$BITCOIN_RPC_USER -bitcoind.rpcpass=$BITCOIN_RPC_PASS +bitcoind.rpchost=$BTC_HOST:$BTC_PORT +bitcoind.rpcuser=$BTC_RPC_USER +bitcoind.rpcpass=$BTC_RPC_PASS bitcoind.rpcpolling=true bitcoind.estimatemode=ECONOMICAL @@ -649,11 +746,11 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint; then --security-opt no-new-privileges:true \ -p 8173:8173 -p 8174:8174 -p 8175:8175 \ -v /var/lib/archipelago/fedimint:/data \ - -e FM_DATA_DIR=/data -e "FM_BITCOIND_USERNAME=$BITCOIN_RPC_USER" -e "FM_BITCOIND_PASSWORD=$BITCOIN_RPC_PASS" \ + -e FM_DATA_DIR=/data -e "FM_BITCOIND_USERNAME=$BTC_RPC_USER" -e "FM_BITCOIND_PASSWORD=$BTC_RPC_PASS" \ -e FM_BITCOIN_NETWORK=bitcoin -e FM_BIND_P2P=0.0.0.0:8173 \ -e FM_BIND_API=0.0.0.0:8174 -e FM_BIND_UI=0.0.0.0:8175 \ -e FM_P2P_URL=fedimint://"$TARGET_IP":8173 -e FM_API_URL=ws://"$TARGET_IP":8174 \ - -e FM_BITCOIND_URL=http://"$TARGET_IP":8332 \ + -e "FM_BITCOIND_URL=http://$BTC_HOST:$BTC_PORT" \ "$FEDIMINT_IMAGE" 2>>"$LOG" || true fi track_container "fedimint" @@ -679,8 +776,8 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint-gateway; th "$FEDIMINT_GATEWAY_IMAGE" \ gatewayd --data-dir /data --listen 0.0.0.0:8176 \ --bcrypt-password-hash "$FEDI_HASH" \ - --network bitcoin --bitcoind-url http://"$TARGET_IP":8332 \ - --bitcoind-username "$BITCOIN_RPC_USER" --bitcoind-password "$BITCOIN_RPC_PASS" \ + --network bitcoin --bitcoind-url "http://$BTC_HOST:$BTC_PORT" \ + --bitcoind-username "$BTC_RPC_USER" --bitcoind-password "$BTC_RPC_PASS" \ lnd --lnd-rpc-host "$TARGET_IP":10009 --lnd-tls-cert /lnd/tls.cert --lnd-macaroon /lnd/admin.macaroon 2>>"$LOG" || true else log " No LND found — using ldk (built-in Lightning)" @@ -694,8 +791,8 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint-gateway; th "$FEDIMINT_GATEWAY_IMAGE" \ gatewayd --data-dir /data --listen 0.0.0.0:8176 \ --bcrypt-password-hash "$FEDI_HASH" \ - --network bitcoin --bitcoind-url http://"$TARGET_IP":8332 \ - --bitcoind-username "$BITCOIN_RPC_USER" --bitcoind-password "$BITCOIN_RPC_PASS" \ + --network bitcoin --bitcoind-url "http://$BTC_HOST:$BTC_PORT" \ + --bitcoind-username "$BTC_RPC_USER" --bitcoind-password "$BTC_RPC_PASS" \ ldk --ldk-lightning-port 9737 --ldk-alias archipelago-gateway 2>>"$LOG" || true fi fi @@ -847,17 +944,8 @@ SEARXCFG "${SEARXNG_IMAGE}" 2>>"$LOG" || true fi track_container "searxng" -if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q onlyoffice; then - log "Creating OnlyOffice..." - $DOCKER run -d --name onlyoffice --restart unless-stopped \ - --health-cmd="curl -sf http://localhost:80/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \ - --memory=$(mem_limit onlyoffice) \ - --cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \ - --security-opt no-new-privileges:true \ - -p 9980:80 \ - "$ONLYOFFICE_IMAGE" 2>>"$LOG" || true -fi -track_container "onlyoffice" +# OnlyOffice removed — incompatible with rootless Podman (internal postgres/rabbitmq) +# CryptPad is the replacement (single Node.js process, e2e encrypted) if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q filebrowser; then log "Creating File Browser..." mkdir -p /var/lib/archipelago/filebrowser /var/lib/archipelago/filebrowser-data diff --git a/scripts/image-versions.sh b/scripts/image-versions.sh index 5ca656a3..3d40bb0c 100644 --- a/scripts/image-versions.sh +++ b/scripts/image-versions.sh @@ -38,7 +38,9 @@ OLLAMA_IMAGE="$ARCHY_REGISTRY/ollama:latest" VAULTWARDEN_IMAGE="$ARCHY_REGISTRY/vaultwarden:1.30.0-alpine" NEXTCLOUD_IMAGE="$ARCHY_REGISTRY/nextcloud:29" SEARXNG_IMAGE="$ARCHY_REGISTRY/searxng:latest" -ONLYOFFICE_IMAGE="$ARCHY_REGISTRY/onlyoffice:latest" +# OnlyOffice removed — incompatible with rootless Podman (internal postgres/rabbitmq fail) +# Replaced by CryptPad (single Node.js process, e2e encrypted) +CRYPTPAD_IMAGE="$ARCHY_REGISTRY/cryptpad:2024.12.0" FILEBROWSER_IMAGE="$ARCHY_REGISTRY/filebrowser:v2.27.0" NPM_IMAGE="$ARCHY_REGISTRY/nginx-proxy-manager:latest" PORTAINER_IMAGE="$ARCHY_REGISTRY/portainer:latest"