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:
parent
5c003daafa
commit
b3a6d2103a
@ -59,7 +59,7 @@ pub(super) fn get_app_capabilities(app_id: &str) -> Vec<String> {
|
|||||||
"--cap-add=NET_RAW".to_string(),
|
"--cap-add=NET_RAW".to_string(),
|
||||||
],
|
],
|
||||||
"nextcloud" | "btcpay-server" | "btcpayserver"
|
"nextcloud" | "btcpay-server" | "btcpayserver"
|
||||||
| "onlyoffice" | "onlyoffice-documentserver" | "portainer" => vec![
|
| "portainer" => vec![
|
||||||
"--cap-add=CHOWN".to_string(),
|
"--cap-add=CHOWN".to_string(),
|
||||||
"--cap-add=SETUID".to_string(),
|
"--cap-add=SETUID".to_string(),
|
||||||
"--cap-add=SETGID".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.
|
/// Get container health check arguments for podman run.
|
||||||
/// Returns (health-cmd, interval, retries) args to append to run_args.
|
/// 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> {
|
pub(super) fn get_health_check_args(app_id: &str, _rpc_pass: &str) -> Vec<String> {
|
||||||
let btc_health = format!(
|
// bitcoin-cli reads the .cookie file from -datadir automatically (no plaintext creds needed)
|
||||||
"bitcoin-cli -rpcuser=archipelago -rpcpassword={} getblockchaininfo || exit 1",
|
let btc_health = "bitcoin-cli -datadir=/home/bitcoin/.bitcoin getblockchaininfo || exit 1"
|
||||||
rpc_pass
|
.to_string();
|
||||||
);
|
|
||||||
let (cmd, interval, retries) = match app_id {
|
let (cmd, interval, retries) = match app_id {
|
||||||
"bitcoin" | "bitcoin-core" | "bitcoin-knots" => (btc_health.as_str(), "30s", "3"),
|
"bitcoin" | "bitcoin-core" | "bitcoin-knots" => (btc_health.as_str(), "30s", "3"),
|
||||||
"lnd" => ("lncli getinfo || exit 1", "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 {
|
match app_id {
|
||||||
// Heavy apps
|
// Heavy apps
|
||||||
"bitcoin" | "bitcoin-core" | "bitcoin-knots" => "4g",
|
"bitcoin" | "bitcoin-core" | "bitcoin-knots" => "4g",
|
||||||
"onlyoffice" | "onlyoffice-documentserver" => "2g",
|
"cryptpad" => "512m",
|
||||||
"ollama" => "4g",
|
"ollama" => "4g",
|
||||||
// Medium apps
|
// Medium apps
|
||||||
"lnd" => "512m",
|
"lnd" => "512m",
|
||||||
@ -566,9 +565,9 @@ pub(super) async fn get_app_config(
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
"onlyoffice" | "onlyoffice-documentserver" => (
|
"cryptpad" => (
|
||||||
vec!["9980:80".to_string()],
|
vec!["3003:3000".to_string()],
|
||||||
vec![],
|
vec!["/var/lib/archipelago/cryptpad:/cryptpad/datastore".to_string()],
|
||||||
vec![],
|
vec![],
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|||||||
@ -360,11 +360,11 @@ fn get_app_metadata(app_id: &str) -> AppMetadata {
|
|||||||
repo: "https://github.com/searxng/searxng".to_string(),
|
repo: "https://github.com/searxng/searxng".to_string(),
|
||||||
tier: "",
|
tier: "",
|
||||||
},
|
},
|
||||||
"onlyoffice" | "onlyoffice-documentserver" => AppMetadata {
|
"cryptpad" => AppMetadata {
|
||||||
title: "OnlyOffice".to_string(),
|
title: "CryptPad".to_string(),
|
||||||
description: "Office suite and document collaboration".to_string(),
|
description: "End-to-end encrypted document collaboration".to_string(),
|
||||||
icon: "/assets/img/app-icons/onlyoffice.webp".to_string(),
|
icon: "/assets/img/app-icons/cryptpad.webp".to_string(),
|
||||||
repo: "https://github.com/ONLYOFFICE/DocumentServer".to_string(),
|
repo: "https://github.com/cryptpad/cryptpad".to_string(),
|
||||||
tier: "",
|
tier: "",
|
||||||
},
|
},
|
||||||
"penpot" | "penpot-frontend" => AppMetadata {
|
"penpot" | "penpot-frontend" => AppMetadata {
|
||||||
|
|||||||
@ -165,9 +165,9 @@ pub async fn get_electrs_sync_status() -> ElectrsSyncStatus {
|
|||||||
let tor_onion = {
|
let tor_onion = {
|
||||||
let mut onion = None;
|
let mut onion = None;
|
||||||
for path in &[
|
for path in &[
|
||||||
"/var/lib/archipelago/tor-hostnames/electrs",
|
"/var/lib/archipelago/tor-hostnames/electrumx",
|
||||||
"/var/lib/tor/hidden_service_electrs/hostname",
|
"/var/lib/tor/hidden_service_electrumx/hostname",
|
||||||
"/var/lib/archipelago/tor/hidden_service_electrs/hostname",
|
"/var/lib/archipelago/tor/hidden_service_electrumx/hostname",
|
||||||
] {
|
] {
|
||||||
if let Ok(addr) = tokio::fs::read_to_string(path).await {
|
if let Ok(addr) = tokio::fs::read_to_string(path).await {
|
||||||
let addr = addr.trim().to_string();
|
let addr = addr.trim().to_string();
|
||||||
|
|||||||
@ -116,7 +116,7 @@ impl PodmanClient {
|
|||||||
"grafana" => "http://localhost:3000",
|
"grafana" => "http://localhost:3000",
|
||||||
"searxng" => "http://localhost:8888",
|
"searxng" => "http://localhost:8888",
|
||||||
"ollama" => "http://localhost:11434",
|
"ollama" => "http://localhost:11434",
|
||||||
"onlyoffice" => "http://localhost:9980",
|
"cryptpad" => "http://localhost:3003",
|
||||||
"penpot" => "http://localhost:9001",
|
"penpot" => "http://localhost:9001",
|
||||||
"nextcloud" => "http://localhost:8085",
|
"nextcloud" => "http://localhost:8085",
|
||||||
"vaultwarden" => "http://localhost:8082",
|
"vaultwarden" => "http://localhost:8082",
|
||||||
|
|||||||
@ -355,7 +355,10 @@
|
|||||||
|
|
||||||
async function updateStatus() {
|
async function updateStatus() {
|
||||||
try {
|
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();
|
var data = await resp.json();
|
||||||
|
|
||||||
// Extract Tor onion from status response
|
// Extract Tor onion from status response
|
||||||
@ -410,8 +413,14 @@
|
|||||||
document.getElementById('connSubtitle').textContent = 'Connections will be available once ElectrumX has completed syncing.';
|
document.getElementById('connSubtitle').textContent = 'Connections will be available once ElectrumX has completed syncing.';
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
document.getElementById('syncStatusText').textContent = 'Unable to fetch status: ' + e.message;
|
var msg = e.message || 'Unknown error';
|
||||||
document.getElementById('syncStatusText').style.color = '#f87171';
|
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';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,14 +5,8 @@ server {
|
|||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
location /electrs-status {
|
# electrs-status is fetched via absolute /electrs-status path,
|
||||||
proxy_pass http://127.0.0.1:5678/electrs-status;
|
# handled by the host nginx → backend at :5678 directly
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
add_header Access-Control-Allow-Origin *;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,13 +5,8 @@ server {
|
|||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
location /lnd-connect-info {
|
# lnd-connect-info is fetched via absolute URL path,
|
||||||
proxy_pass http://127.0.0.1:5678/lnd-connect-info;
|
# handled by the host nginx → backend at :5678 directly
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -300,6 +300,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
plymouth \
|
plymouth \
|
||||||
plymouth-themes \
|
plymouth-themes \
|
||||||
zstd \
|
zstd \
|
||||||
|
socat \
|
||||||
python3 \
|
python3 \
|
||||||
apache2-utils \
|
apache2-utils \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
@ -1250,6 +1251,7 @@ HiddenServicePort 80 127.0.0.1:80
|
|||||||
|
|
||||||
HiddenServiceDir $TOR_DIR/hidden_service_bitcoin
|
HiddenServiceDir $TOR_DIR/hidden_service_bitcoin
|
||||||
HiddenServicePort 8333 127.0.0.1:8333
|
HiddenServicePort 8333 127.0.0.1:8333
|
||||||
|
HiddenServicePort 8332 127.0.0.1:8332
|
||||||
|
|
||||||
HiddenServiceDir $TOR_DIR/hidden_service_electrumx
|
HiddenServiceDir $TOR_DIR/hidden_service_electrumx
|
||||||
HiddenServicePort 50001 127.0.0.1:50001
|
HiddenServicePort 50001 127.0.0.1:50001
|
||||||
@ -1475,6 +1477,41 @@ FBCSERVICE
|
|||||||
fi
|
fi
|
||||||
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
|
# Bundle E2E test script for post-install validation
|
||||||
if [ -f "$SCRIPT_DIR/../scripts/run-e2e-tests.sh" ]; then
|
if [ -f "$SCRIPT_DIR/../scripts/run-e2e-tests.sh" ]; then
|
||||||
cp "$SCRIPT_DIR/../scripts/run-e2e-tests.sh" "$ARCH_DIR/scripts/"
|
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
|
mkdir -p /mnt/target/etc/tmpfiles.d
|
||||||
echo 'd /run/user/1000 0700 archipelago archipelago -' > /mnt/target/etc/tmpfiles.d/archipelago-runtime.conf
|
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
|
# 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 archipelago.service 2>/dev/null || true
|
||||||
chroot /mnt/target systemctl enable nginx.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
|
chroot /mnt/target systemctl enable archipelago-load-images.service 2>/dev/null || true
|
||||||
|
|||||||
@ -67,6 +67,7 @@ server {
|
|||||||
proxy_cache off;
|
proxy_cache off;
|
||||||
proxy_connect_timeout 120s;
|
proxy_connect_timeout 120s;
|
||||||
proxy_read_timeout 300s;
|
proxy_read_timeout 300s;
|
||||||
|
proxy_send_timeout 120s;
|
||||||
}
|
}
|
||||||
|
|
||||||
# AIUI web search proxy — SearXNG on port 8888
|
# AIUI web search proxy — SearXNG on port 8888
|
||||||
@ -84,6 +85,16 @@ server {
|
|||||||
return 503 '{"error":"SearXNG is not running"}';
|
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)
|
# Icons, favicon, manifest — always revalidate (no heuristic caching)
|
||||||
location ~* ^/(favicon\.ico|manifest\.webmanifest|assets/icon/) {
|
location ~* ^/(favicon\.ico|manifest\.webmanifest|assets/icon/) {
|
||||||
add_header Cache-Control "no-cache, must-revalidate";
|
add_header Cache-Control "no-cache, must-revalidate";
|
||||||
@ -94,7 +105,7 @@ server {
|
|||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Peer-to-peer node messaging (receives from other nodes over Tor)
|
# Peer-to-peer node messaging (receives from other nodes over Tor)
|
||||||
location /archipelago/ {
|
location /archipelago/ {
|
||||||
limit_req zone=peer burst=20 nodelay;
|
limit_req zone=peer burst=20 nodelay;
|
||||||
@ -106,6 +117,8 @@ server {
|
|||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
error_page 502 503 = @backend_unavailable;
|
||||||
|
error_page 504 = @backend_timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Proxy API requests to backend
|
# Proxy API requests to backend
|
||||||
@ -116,7 +129,6 @@ server {
|
|||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
# Connection header managed by nginx default
|
|
||||||
|
|
||||||
# Limit request body to 1MB for RPC calls
|
# Limit request body to 1MB for RPC calls
|
||||||
client_max_body_size 1m;
|
client_max_body_size 1m;
|
||||||
@ -125,6 +137,8 @@ server {
|
|||||||
proxy_connect_timeout 600s;
|
proxy_connect_timeout 600s;
|
||||||
proxy_send_timeout 600s;
|
proxy_send_timeout 600s;
|
||||||
proxy_read_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)
|
# 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_pass http://127.0.0.1:5678/health;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
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 {
|
location /electrs-status {
|
||||||
proxy_pass http://127.0.0.1:5678/electrs-status;
|
proxy_pass http://127.0.0.1:5678/electrs-status;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
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 {
|
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_pass http://127.0.0.1:5678/lnd-connect-info;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header Cookie $http_cookie;
|
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-Origin $http_origin always;
|
||||||
add_header Access-Control-Allow-Credentials "true" 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)
|
# Content sharing — peer access over Tor (no auth)
|
||||||
@ -162,6 +188,8 @@ server {
|
|||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
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)
|
# DWN endpoints — peer access over Tor (no auth)
|
||||||
@ -175,6 +203,8 @@ server {
|
|||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
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
|
# Proxy apps that set X-Frame-Options - strip header so iframe works
|
||||||
@ -481,20 +511,8 @@ server {
|
|||||||
}
|
}
|
||||||
location /app/tailscale/ {
|
location /app/tailscale/ {
|
||||||
# Tailscale has no web UI — managed via CLI/Tailscale app
|
# Tailscale has no web UI — managed via CLI/Tailscale app
|
||||||
# proxy_pass placeholder for future Tailscale admin UI
|
default_type application/json;
|
||||||
return 503;
|
return 503 '{"error":{"code":"NO_WEB_UI","message":"Tailscale is managed via CLI"}}';
|
||||||
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>';
|
|
||||||
}
|
}
|
||||||
location /app/ollama/ {
|
location /app/ollama/ {
|
||||||
proxy_pass http://127.0.0.1:11434/;
|
proxy_pass http://127.0.0.1:11434/;
|
||||||
@ -615,7 +633,6 @@ server {
|
|||||||
proxy_hide_header X-Frame-Options;
|
proxy_hide_header X-Frame-Options;
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
proxy_hide_header Content-Security-Policy;
|
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-Embedder-Policy;
|
||||||
proxy_hide_header Cross-Origin-Opener-Policy;
|
proxy_hide_header Cross-Origin-Opener-Policy;
|
||||||
proxy_hide_header Cross-Origin-Resource-Policy;
|
proxy_hide_header Cross-Origin-Resource-Policy;
|
||||||
@ -676,7 +693,6 @@ server {
|
|||||||
proxy_hide_header X-Frame-Options;
|
proxy_hide_header X-Frame-Options;
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
proxy_hide_header Content-Security-Policy;
|
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-Embedder-Policy;
|
||||||
proxy_hide_header Cross-Origin-Opener-Policy;
|
proxy_hide_header Cross-Origin-Opener-Policy;
|
||||||
proxy_hide_header Cross-Origin-Resource-Policy;
|
proxy_hide_header Cross-Origin-Resource-Policy;
|
||||||
@ -727,6 +743,16 @@ server {
|
|||||||
add_header X-DNS-Prefetch-Control "off" always;
|
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;
|
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
|
# AIUI SPA (Chat mode iframe) — SPA fallback for client-side routing
|
||||||
location /aiui/ {
|
location /aiui/ {
|
||||||
alias /opt/archipelago/web-ui/aiui/;
|
alias /opt/archipelago/web-ui/aiui/;
|
||||||
@ -754,6 +780,7 @@ server {
|
|||||||
proxy_cache off;
|
proxy_cache off;
|
||||||
proxy_connect_timeout 120s;
|
proxy_connect_timeout 120s;
|
||||||
proxy_read_timeout 300s;
|
proxy_read_timeout 300s;
|
||||||
|
proxy_send_timeout 120s;
|
||||||
}
|
}
|
||||||
location /aiui/api/openrouter/ {
|
location /aiui/api/openrouter/ {
|
||||||
proxy_pass https://openrouter.ai/api/;
|
proxy_pass https://openrouter.ai/api/;
|
||||||
@ -785,29 +812,43 @@ server {
|
|||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
error_page 502 503 = @backend_unavailable;
|
||||||
|
error_page 504 = @backend_timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /health {
|
location /health {
|
||||||
proxy_pass http://127.0.0.1:5678/health;
|
proxy_pass http://127.0.0.1:5678/health;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
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 {
|
location /electrs-status {
|
||||||
proxy_pass http://127.0.0.1:5678/electrs-status;
|
proxy_pass http://127.0.0.1:5678/electrs-status;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
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 {
|
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_pass http://127.0.0.1:5678/lnd-connect-info;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header Cookie $http_cookie;
|
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-Origin $http_origin always;
|
||||||
add_header Access-Control-Allow-Credentials "true" 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)
|
# Content sharing — peer access over Tor (no auth)
|
||||||
@ -821,6 +862,8 @@ server {
|
|||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
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)
|
# DWN endpoints — peer access over Tor (no auth)
|
||||||
@ -834,6 +877,8 @@ server {
|
|||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
error_page 502 503 = @backend_unavailable;
|
||||||
|
error_page 504 = @backend_timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /rpc/ {
|
location /rpc/ {
|
||||||
@ -843,7 +888,6 @@ server {
|
|||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
# Connection header managed by nginx default
|
|
||||||
|
|
||||||
# Limit request body to 1MB for RPC calls
|
# Limit request body to 1MB for RPC calls
|
||||||
client_max_body_size 1m;
|
client_max_body_size 1m;
|
||||||
@ -851,6 +895,8 @@ server {
|
|||||||
proxy_connect_timeout 600s;
|
proxy_connect_timeout 600s;
|
||||||
proxy_send_timeout 600s;
|
proxy_send_timeout 600s;
|
||||||
proxy_read_timeout 600s;
|
proxy_read_timeout 600s;
|
||||||
|
error_page 502 503 = @backend_unavailable;
|
||||||
|
error_page 504 = @backend_timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /app/nextcloud/ {
|
location /app/nextcloud/ {
|
||||||
@ -965,7 +1011,6 @@ server {
|
|||||||
proxy_hide_header X-Frame-Options;
|
proxy_hide_header X-Frame-Options;
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
proxy_hide_header Content-Security-Policy;
|
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-Embedder-Policy;
|
||||||
proxy_hide_header Cross-Origin-Opener-Policy;
|
proxy_hide_header Cross-Origin-Opener-Policy;
|
||||||
proxy_hide_header Cross-Origin-Resource-Policy;
|
proxy_hide_header Cross-Origin-Resource-Policy;
|
||||||
@ -1026,7 +1071,6 @@ server {
|
|||||||
proxy_hide_header X-Frame-Options;
|
proxy_hide_header X-Frame-Options;
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
proxy_hide_header Content-Security-Policy;
|
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-Embedder-Policy;
|
||||||
proxy_hide_header Cross-Origin-Opener-Policy;
|
proxy_hide_header Cross-Origin-Opener-Policy;
|
||||||
proxy_hide_header Cross-Origin-Resource-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>';
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||||
}
|
}
|
||||||
location /app/tailscale/ {
|
location /app/tailscale/ {
|
||||||
# Tailscale: no web UI
|
# Tailscale has no web UI — managed via CLI/Tailscale app
|
||||||
return 503;
|
default_type application/json;
|
||||||
proxy_http_version 1.1;
|
return 503 '{"error":{"code":"NO_WEB_UI","message":"Tailscale is managed via CLI"}}';
|
||||||
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>';
|
|
||||||
}
|
}
|
||||||
location /app/ollama/ {
|
location /app/ollama/ {
|
||||||
proxy_pass http://127.0.0.1:11434/;
|
proxy_pass http://127.0.0.1:11434/;
|
||||||
|
|||||||
113
scripts/bootstrap-switchover.sh
Executable file
113
scripts/bootstrap-switchover.sh
Executable 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 ==="
|
||||||
@ -111,7 +111,12 @@ if [ ! -f "$SECRETS_DIR/bitcoin-rpc-password" ]; then
|
|||||||
chmod 600 "$SECRETS_DIR/bitcoin-rpc-password"
|
chmod 600 "$SECRETS_DIR/bitcoin-rpc-password"
|
||||||
fi
|
fi
|
||||||
BITCOIN_RPC_USER="archipelago"
|
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 line for bitcoin.conf (salted HMAC-SHA256 hash)
|
||||||
generate_rpcauth() {
|
generate_rpcauth() {
|
||||||
@ -273,7 +278,7 @@ log "Fixing rootless podman UID mapping..."
|
|||||||
# Containers running as root (UID 0 → host UID 100000)
|
# Containers running as root (UID 0 → host UID 100000)
|
||||||
for dir in lnd electrumx btcpay nbxplorer jellyfin vaultwarden \
|
for dir in lnd electrumx btcpay nbxplorer jellyfin vaultwarden \
|
||||||
home-assistant fedimint fedimint-gateway photoprism ollama filebrowser \
|
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
|
[ -d "/var/lib/archipelago/$dir" ] && chown -R 100000:100000 "/var/lib/archipelago/$dir" 2>/dev/null
|
||||||
done
|
done
|
||||||
# Bitcoin Knots: container UID 101 → host UID 100101
|
# Bitcoin Knots: container UID 101 → host UID 100101
|
||||||
@ -300,7 +305,7 @@ LOW_MEM=false
|
|||||||
mem_limit() {
|
mem_limit() {
|
||||||
case "$1" in
|
case "$1" in
|
||||||
bitcoin-knots) $LOW_MEM && echo "2g" || echo "4g";;
|
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";;
|
ollama) $LOW_MEM && echo "1g" || echo "4g";;
|
||||||
lnd) echo "512m";;
|
lnd) echo "512m";;
|
||||||
electrumx) echo "1g";;
|
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"
|
log " Large disk (${DISK_GB}GB) — enabling txindex"
|
||||||
fi
|
fi
|
||||||
if $DOCKER run -d --name bitcoin-knots --restart unless-stopped \
|
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 \
|
--memory=$(mem_limit bitcoin-knots) --network archy-net --network-alias bitcoin-knots \
|
||||||
$ADD_HOST_FLAG \
|
$ADD_HOST_FLAG \
|
||||||
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
|
--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)
|
# Check Bitcoin Knots RPC (informational — containers created regardless)
|
||||||
# Dependent containers use --restart=unless-stopped and the health monitor
|
# Dependent containers use --restart=unless-stopped and the health monitor
|
||||||
# will auto-restart them once Bitcoin becomes responsive.
|
# 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
|
BITCOIN_READY=true
|
||||||
log "Bitcoin Knots is ready"
|
log "Bitcoin Knots is ready"
|
||||||
else
|
else
|
||||||
@ -421,12 +426,104 @@ fi
|
|||||||
track_container "bitcoin-knots"
|
track_container "bitcoin-knots"
|
||||||
|
|
||||||
# Ensure wallet exists (Bitcoin Knots no longer auto-creates a default wallet)
|
# 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
|
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 "-rpcuser=$BITCOIN_RPC_USER" "-rpcpassword=$BITCOIN_RPC_PASS" loadwallet "archipelago" 2>/dev/null || \
|
$DOCKER exec bitcoin-knots bitcoin-cli -datadir=/home/bitcoin/.bitcoin loadwallet "archipelago" 2>/dev/null || \
|
||||||
$DOCKER exec bitcoin-knots bitcoin-cli "-rpcuser=$BITCOIN_RPC_USER" "-rpcpassword=$BITCOIN_RPC_PASS" createwallet "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"
|
log "Bitcoin Knots wallet 'archipelago' created/loaded"
|
||||||
fi
|
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
|
# 2. Mempool stack (matches deploy) — depends on Bitcoin
|
||||||
# Note: containers created regardless of BITCOIN_READY — they will restart
|
# Note: containers created regardless of BITCOIN_READY — they will restart
|
||||||
# automatically once Bitcoin becomes responsive (--restart=unless-stopped).
|
# 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 \
|
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
|
||||||
--security-opt no-new-privileges:true \
|
--security-opt no-new-privileges:true \
|
||||||
-p 50001:50001 -v /var/lib/archipelago/electrumx:/data \
|
-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 COIN=Bitcoin -e DB_DIRECTORY=/data \
|
||||||
-e SERVICES=tcp://:50001,rpc://0.0.0.0:8000 \
|
-e SERVICES=tcp://:50001,rpc://0.0.0.0:8000 \
|
||||||
"$ELECTRUMX_IMAGE" 2>>"$LOG" || true
|
"$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 \
|
--security-opt no-new-privileges:true \
|
||||||
-p 8999:8999 -v /var/lib/archipelago/mempool:/data \
|
-p 8999:8999 -v /var/lib/archipelago/mempool:/data \
|
||||||
-e MEMPOOL_BACKEND=electrum -e ELECTRUM_HOST=electrumx -e ELECTRUM_PORT=50001 \
|
-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 ELECTRUM_TLS_ENABLED=false -e "CORE_RPC_HOST=$BTC_HOST" -e CORE_RPC_PORT=8332 \
|
||||||
-e "CORE_RPC_USERNAME=$BITCOIN_RPC_USER" -e "CORE_RPC_PASSWORD=$BITCOIN_RPC_PASS" \
|
-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_ENABLED=true -e DATABASE_HOST="$MYSQL_CNT" -e DATABASE_DATABASE=mempool \
|
||||||
-e DATABASE_USERNAME=mempool -e "DATABASE_PASSWORD=$MEMPOOL_DB_PASS" \
|
-e DATABASE_USERNAME=mempool -e "DATABASE_PASSWORD=$MEMPOOL_DB_PASS" \
|
||||||
"$MEMPOOL_BACKEND_IMAGE" 2>>"$LOG" || true
|
"$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 \
|
--security-opt no-new-privileges:true \
|
||||||
-p 32838:32838 -v /var/lib/archipelago/nbxplorer:/data \
|
-p 32838:32838 -v /var/lib/archipelago/nbxplorer:/data \
|
||||||
-e NBXPLORER_DATADIR=/data -e NBXPLORER_NETWORK=mainnet -e NBXPLORER_CHAINS=btc \
|
-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_BIND=0.0.0.0:32838 -e "NBXPLORER_BTCRPCURL=http://$BTC_HOST:$BTC_PORT" \
|
||||||
-e "NBXPLORER_BTCRPCUSER=$BITCOIN_RPC_USER" -e "NBXPLORER_BTCRPCPASSWORD=$BITCOIN_RPC_PASS" \
|
-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' \
|
-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
|
"$NBXPLORER_IMAGE" 2>>"$LOG" && sleep 5 || true
|
||||||
fi
|
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 ASPNETCORE_URLS=http://0.0.0.0:49392 -e BTCPAY_PROTOCOL=http \
|
||||||
-e BTCPAY_HOST="$TARGET_IP:23000" -e BTCPAY_CHAINS=btc \
|
-e BTCPAY_HOST="$TARGET_IP:23000" -e BTCPAY_CHAINS=btc \
|
||||||
-e BTCPAY_BTCEXPLORERURL=http://archy-nbxplorer:32838 \
|
-e BTCPAY_BTCEXPLORERURL=http://archy-nbxplorer:32838 \
|
||||||
-e BTCPAY_BTCRPCURL=http://bitcoin-knots:8332 \
|
-e "BTCPAY_BTCRPCURL=http://$BTC_HOST:$BTC_PORT" \
|
||||||
-e "BTCPAY_BTCRPCUSER=$BITCOIN_RPC_USER" -e "BTCPAY_BTCRPCPASSWORD=$BITCOIN_RPC_PASS" \
|
-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' \
|
-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
|
"$BTCPAY_IMAGE" 2>>"$LOG" || true
|
||||||
fi
|
fi
|
||||||
@ -615,9 +712,9 @@ bitcoin.mainnet=true
|
|||||||
bitcoin.node=bitcoind
|
bitcoin.node=bitcoind
|
||||||
|
|
||||||
[Bitcoind]
|
[Bitcoind]
|
||||||
bitcoind.rpchost=bitcoin-knots:8332
|
bitcoind.rpchost=$BTC_HOST:$BTC_PORT
|
||||||
bitcoind.rpcuser=$BITCOIN_RPC_USER
|
bitcoind.rpcuser=$BTC_RPC_USER
|
||||||
bitcoind.rpcpass=$BITCOIN_RPC_PASS
|
bitcoind.rpcpass=$BTC_RPC_PASS
|
||||||
bitcoind.rpcpolling=true
|
bitcoind.rpcpolling=true
|
||||||
bitcoind.estimatemode=ECONOMICAL
|
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 \
|
--security-opt no-new-privileges:true \
|
||||||
-p 8173:8173 -p 8174:8174 -p 8175:8175 \
|
-p 8173:8173 -p 8174:8174 -p 8175:8175 \
|
||||||
-v /var/lib/archipelago/fedimint:/data \
|
-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_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_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_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
|
"$FEDIMINT_IMAGE" 2>>"$LOG" || true
|
||||||
fi
|
fi
|
||||||
track_container "fedimint"
|
track_container "fedimint"
|
||||||
@ -679,8 +776,8 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint-gateway; th
|
|||||||
"$FEDIMINT_GATEWAY_IMAGE" \
|
"$FEDIMINT_GATEWAY_IMAGE" \
|
||||||
gatewayd --data-dir /data --listen 0.0.0.0:8176 \
|
gatewayd --data-dir /data --listen 0.0.0.0:8176 \
|
||||||
--bcrypt-password-hash "$FEDI_HASH" \
|
--bcrypt-password-hash "$FEDI_HASH" \
|
||||||
--network bitcoin --bitcoind-url http://"$TARGET_IP":8332 \
|
--network bitcoin --bitcoind-url "http://$BTC_HOST:$BTC_PORT" \
|
||||||
--bitcoind-username "$BITCOIN_RPC_USER" --bitcoind-password "$BITCOIN_RPC_PASS" \
|
--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
|
lnd --lnd-rpc-host "$TARGET_IP":10009 --lnd-tls-cert /lnd/tls.cert --lnd-macaroon /lnd/admin.macaroon 2>>"$LOG" || true
|
||||||
else
|
else
|
||||||
log " No LND found — using ldk (built-in Lightning)"
|
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" \
|
"$FEDIMINT_GATEWAY_IMAGE" \
|
||||||
gatewayd --data-dir /data --listen 0.0.0.0:8176 \
|
gatewayd --data-dir /data --listen 0.0.0.0:8176 \
|
||||||
--bcrypt-password-hash "$FEDI_HASH" \
|
--bcrypt-password-hash "$FEDI_HASH" \
|
||||||
--network bitcoin --bitcoind-url http://"$TARGET_IP":8332 \
|
--network bitcoin --bitcoind-url "http://$BTC_HOST:$BTC_PORT" \
|
||||||
--bitcoind-username "$BITCOIN_RPC_USER" --bitcoind-password "$BITCOIN_RPC_PASS" \
|
--bitcoind-username "$BTC_RPC_USER" --bitcoind-password "$BTC_RPC_PASS" \
|
||||||
ldk --ldk-lightning-port 9737 --ldk-alias archipelago-gateway 2>>"$LOG" || true
|
ldk --ldk-lightning-port 9737 --ldk-alias archipelago-gateway 2>>"$LOG" || true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -847,17 +944,8 @@ SEARXCFG
|
|||||||
"${SEARXNG_IMAGE}" 2>>"$LOG" || true
|
"${SEARXNG_IMAGE}" 2>>"$LOG" || true
|
||||||
fi
|
fi
|
||||||
track_container "searxng"
|
track_container "searxng"
|
||||||
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q onlyoffice; then
|
# OnlyOffice removed — incompatible with rootless Podman (internal postgres/rabbitmq)
|
||||||
log "Creating OnlyOffice..."
|
# CryptPad is the replacement (single Node.js process, e2e encrypted)
|
||||||
$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"
|
|
||||||
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q filebrowser; then
|
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q filebrowser; then
|
||||||
log "Creating File Browser..."
|
log "Creating File Browser..."
|
||||||
mkdir -p /var/lib/archipelago/filebrowser /var/lib/archipelago/filebrowser-data
|
mkdir -p /var/lib/archipelago/filebrowser /var/lib/archipelago/filebrowser-data
|
||||||
|
|||||||
@ -38,7 +38,9 @@ OLLAMA_IMAGE="$ARCHY_REGISTRY/ollama:latest"
|
|||||||
VAULTWARDEN_IMAGE="$ARCHY_REGISTRY/vaultwarden:1.30.0-alpine"
|
VAULTWARDEN_IMAGE="$ARCHY_REGISTRY/vaultwarden:1.30.0-alpine"
|
||||||
NEXTCLOUD_IMAGE="$ARCHY_REGISTRY/nextcloud:29"
|
NEXTCLOUD_IMAGE="$ARCHY_REGISTRY/nextcloud:29"
|
||||||
SEARXNG_IMAGE="$ARCHY_REGISTRY/searxng:latest"
|
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"
|
FILEBROWSER_IMAGE="$ARCHY_REGISTRY/filebrowser:v2.27.0"
|
||||||
NPM_IMAGE="$ARCHY_REGISTRY/nginx-proxy-manager:latest"
|
NPM_IMAGE="$ARCHY_REGISTRY/nginx-proxy-manager:latest"
|
||||||
PORTAINER_IMAGE="$ARCHY_REGISTRY/portainer:latest"
|
PORTAINER_IMAGE="$ARCHY_REGISTRY/portainer:latest"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user