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

@ -59,7 +59,7 @@ pub(super) fn get_app_capabilities(app_id: &str) -> Vec<String> {
"--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<String> {
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<String> {
// 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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

113
scripts/bootstrap-switchover.sh Executable file
View File

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

View File

@ -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 <<TUNNELSVC
[Unit]
Description=Bootstrap Bitcoin RPC tunnel via Tor
After=tor.service
[Service]
Type=simple
User=archipelago
ExecStart=/usr/bin/socat TCP-LISTEN:18332,bind=127.0.0.1,reuseaddr,fork SOCKS4A:127.0.0.1:${BOOTSTRAP_ONION}:8332,socksport=9050
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
TUNNELSVC
systemctl daemon-reload
systemctl enable --now archipelago-bootstrap-tunnel.service 2>/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

View File

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