fix: container stability, OnlyOffice removal, node bootstrapping, UI fixes
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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" <<BSTRAP
|
||||
# Bootstrap Bitcoin node — used during Initial Block Download
|
||||
# Services connect here until the local node is fully synced
|
||||
# First-boot tries LAN, then Tor (works from any network)
|
||||
BOOTSTRAP_LAN_HOST=${DEV_IP}
|
||||
BOOTSTRAP_ONION=${BOOTSTRAP_ONION}
|
||||
BOOTSTRAP_RPC_USER=archipelago
|
||||
BOOTSTRAP_RPC_PASS=${BOOTSTRAP_RPC_PASS}
|
||||
BSTRAP
|
||||
chmod 600 "$ARCH_DIR/bootstrap.conf"
|
||||
echo " ✅ Bootstrap node config embedded (LAN: ${DEV_IP}, Tor: ${BOOTSTRAP_ONION:-none})"
|
||||
else
|
||||
echo " ⚠ No bootstrap config — no Bitcoin RPC password found on build host"
|
||||
fi
|
||||
|
||||
# Bundle bootstrap switchover script + systemd timer
|
||||
if [ -f "$SCRIPT_DIR/../scripts/bootstrap-switchover.sh" ]; then
|
||||
cp "$SCRIPT_DIR/../scripts/bootstrap-switchover.sh" "$ARCH_DIR/scripts/"
|
||||
chmod +x "$ARCH_DIR/scripts/bootstrap-switchover.sh"
|
||||
echo " ✅ Bundled bootstrap switchover script"
|
||||
fi
|
||||
|
||||
# Bundle E2E test script for post-install validation
|
||||
if [ -f "$SCRIPT_DIR/../scripts/run-e2e-tests.sh" ]; then
|
||||
cp "$SCRIPT_DIR/../scripts/run-e2e-tests.sh" "$ARCH_DIR/scripts/"
|
||||
@@ -2598,7 +2635,46 @@ chown -R 1000:1000 /mnt/target/home/archipelago/.config 2>/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
|
||||
|
||||
@@ -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 '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
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;
|
||||
|
||||
@@ -193,19 +193,9 @@ location /app/fedimint-gateway/ {
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
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 '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
# 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/;
|
||||
|
||||
Reference in New Issue
Block a user