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:
Dorian
2026-04-02 16:15:04 +01:00
parent 5c003daafa
commit b3a6d2103a
13 changed files with 423 additions and 113 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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/;