2026-02-25 18:04:41 +00:00
#!/bin/bash
#
# First-boot container creation for Archipelago autoinstaller
# Creates core containers so My Apps works out of the box after ISO install
# Runs after archipelago-load-images.service and archipelago-setup-tor.service
#
# Based on scripts/deploy-to-target.sh (--live) container logic - do not diverge.
# No set -e: each section continues even if one fails (idempotent, best-effort).
#
2026-03-21 03:06:29 +00:00
# Image versions: sourced from /opt/archipelago/image-versions.sh (single source of truth).
2026-03-26 14:06:21 +00:00
# All container image references use the $*_IMAGE variables defined there.
2026-04-11 09:33:10 -04:00
# Images pull from the Archipelago app registry (git.tx1138.com/lfg2025/).
2026-03-21 03:06:29 +00:00
#
# --- PLANNED REFACTOR (post-beta) ---
# This script is ~995 lines and should be split into a modular library.
# DO NOT split until tested on the build server — this is critical infrastructure.
#
2026-02-25 18:04:41 +00:00
LOG = "/var/log/archipelago-first-boot.log"
2026-03-21 01:32:28 +00:00
# Source pinned image versions (single source of truth)
2026-04-02 01:28:11 +01:00
# ISO copies to scripts/ subdir; also check the direct path for manual installs
source /opt/archipelago/scripts/image-versions.sh 2>/dev/null \
|| source /opt/archipelago/image-versions.sh 2>/dev/null \
|| source /home/archipelago/archy/scripts/image-versions.sh 2>/dev/null \
|| true
# Verify image-versions loaded — fail loudly if not
if [ -z " $ARCHY_REGISTRY " ] || [ -z " $BITCOIN_KNOTS_IMAGE " ] ; then
log "FATAL: image-versions.sh not loaded — checked:"
log " /opt/archipelago/scripts/image-versions.sh"
log " /opt/archipelago/image-versions.sh"
log " /home/archipelago/archy/scripts/image-versions.sh"
log "Container creation will fail. Check ISO build."
fi
2026-03-21 01:32:28 +00:00
2026-03-21 03:06:29 +00:00
# Source shared utility library
SCRIPT_DIR_FBC = " $( cd " $( dirname " $0 " ) " && pwd ) "
[ -f " $SCRIPT_DIR_FBC /lib/common.sh " ] && source " $SCRIPT_DIR_FBC /lib/common.sh " || true
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
# Must run as root for system setup (sysctl, loginctl, subuid, chown).
# Podman commands run as the archipelago user (rootless) so the backend
# (which also runs as archipelago) can see and manage the containers.
2026-02-25 18:04:41 +00:00
[ " $( id -u) " -eq 0 ] || { echo "Must run as root" >& 2; exit 1; }
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
# Run podman as the archipelago user (rootless) — NOT as root.
# The backend service runs as User=archipelago and connects to the rootless
2026-04-03 12:33:15 +01:00
# podman socket at /run/user/$(id -u archipelago)/podman/podman.sock. If we create containers
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
# as root (rootful podman), the backend can't see them at all.
2026-04-03 12:33:15 +01:00
DOCKER = " runuser -u archipelago -- env XDG_RUNTIME_DIR=/run/user/ $( id -u archipelago) podman "
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
2026-05-17 17:30:04 -04:00
PORT_ALLOC_FILE = "/var/lib/archipelago/port-allocations.env"
mkdir -p /var/lib/archipelago 2>/dev/null || true
[ -f " $PORT_ALLOC_FILE " ] && . " $PORT_ALLOC_FILE "
port_available( ) {
local port = " $1 "
ss -ltn 2>/dev/null | awk -v p = " : $port " '$4 == p || $4 ~ p "$" { found=1 } END { exit found ? 1 : 0 }'
}
alloc_port( ) {
local key = " $1 " preferred = " $2 " var = " PORT_ ${ key //[^A-Za-z0-9]/_ } " cur = ""
eval " cur=\${ $var :-} "
if [ -n " $cur " ] && port_available " $cur " ; then
printf '%s' " $cur "
return
fi
if port_available " $preferred " ; then
cur = " $preferred "
else
cur = ""
for p in $( seq 8085 9999) ; do
if port_available " $p " ; then cur = " $p " ; break; fi
done
fi
[ -n " $cur " ] || cur = " $preferred "
if ! grep -q " ^ $var = " " $PORT_ALLOC_FILE " 2>/dev/null; then
printf '%s=%s\n' " $var " " $cur " >> " $PORT_ALLOC_FILE "
fi
printf '%s' " $cur "
}
2026-04-10 04:01:35 -04:00
# UNBUNDLED mode: only create FileBrowser, skip all other containers.
# Users install apps on-demand from the Marketplace.
UNBUNDLED_MARKER = "/opt/archipelago/.unbundled"
if [ -f " $UNBUNDLED_MARKER " ] ; then
log "UNBUNDLED mode detected — creating FileBrowser only (apps install from Marketplace)"
# Core setup: secrets, podman prerequisites, FileBrowser
SECRETS_DIR = "/var/lib/archipelago/secrets"
mkdir -p " $SECRETS_DIR " && chmod 700 " $SECRETS_DIR "
if [ ! -f " $SECRETS_DIR /bitcoin-rpc-password " ] ; then
openssl rand -hex 16 > " $SECRETS_DIR /bitcoin-rpc-password "
chmod 600 " $SECRETS_DIR /bitcoin-rpc-password "
fi
# Generate all DB passwords upfront so they're stable
for svc in mempool btcpay mysql-root; do
if [ ! -f " $SECRETS_DIR / ${ svc } -db-password " ] ; then
openssl rand -hex 16 > " $SECRETS_DIR / ${ svc } -db-password "
chmod 600 " $SECRETS_DIR / ${ svc } -db-password "
fi
done
chown -R 1000:1000 " $SECRETS_DIR "
# Podman prerequisites
loginctl enable-linger archipelago 2>/dev/null || true
DOCKER = " runuser -u archipelago -- env XDG_RUNTIME_DIR=/run/user/ $( id -u archipelago) podman "
$DOCKER system migrate 2>/dev/null || true
# Ensure archy-net exists
$DOCKER network create archy-net 2>/dev/null || true
2026-04-12 09:06:12 -04:00
# Helper: pull image with fallback registry
pull_with_fallback( ) {
local img = " $1 "
log " Pulling $img ... "
if $DOCKER pull " $img " 2>>" $LOG " ; then
return 0
fi
# Try fallback registry
local fallback_img
fallback_img = $( echo " $img " | sed " s| ${ ARCHY_REGISTRY } | ${ ARCHY_REGISTRY_FALLBACK } | " )
if [ " $fallback_img " != " $img " ] && [ -n " $ARCHY_REGISTRY_FALLBACK " ] ; then
log " Primary failed, trying fallback: $fallback_img "
if $DOCKER pull " $fallback_img " --tls-verify= false 2>>" $LOG " ; then
$DOCKER tag " $fallback_img " " $img " 2>/dev/null
return 0
fi
fi
# Try docker.io as last resort for common images
local short_name
short_name = $( echo " $img " | sed 's|.*/||' )
local dockerhub = " docker.io/library/ $short_name "
log " Fallback failed, trying docker.io: $dockerhub "
$DOCKER pull " $dockerhub " 2>>" $LOG " && $DOCKER tag " $dockerhub " " $img " 2>/dev/null && return 0
return 1
}
# Create FileBrowser (noauth — behind Archipelago login)
2026-04-10 04:01:35 -04:00
if ! $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q filebrowser; then
2026-04-12 09:06:12 -04:00
log "Creating FileBrowser (noauth)..."
2026-04-10 04:01:35 -04:00
mkdir -p /var/lib/archipelago/filebrowser /var/lib/archipelago/filebrowser-data
2026-04-12 09:06:12 -04:00
mkdir -p /var/lib/archipelago/filebrowser/{ Documents,Photos,Music,Videos,Downloads}
2026-04-18 11:07:08 -04:00
chown -R 100000:100000 /var/lib/archipelago/filebrowser
chown -R 100000:100000 /var/lib/archipelago/filebrowser-data
2026-04-12 09:06:12 -04:00
# Write config with database on persistent volume
cat > /var/lib/archipelago/filebrowser-data/.filebrowser.json <<'FBEOF '
{ "port" :80,"baseURL" :"" ,"address" :"0.0.0.0" ,"database" :"/data/filebrowser.db" ,"root" :"/srv" ,"log" :"stdout" }
FBEOF
2026-04-18 11:07:08 -04:00
chown 100000:100000 /var/lib/archipelago/filebrowser-data/.filebrowser.json
2026-04-12 09:06:12 -04:00
pull_with_fallback " ${ FILEBROWSER_IMAGE } "
2026-04-10 04:01:35 -04:00
$DOCKER run -d --name filebrowser --restart unless-stopped \
2026-04-12 09:06:12 -04:00
--network archy-net \
2026-04-10 04:01:35 -04:00
--cap-drop= ALL --cap-add= DAC_OVERRIDE --cap-add= NET_BIND_SERVICE \
--security-opt= no-new-privileges:true \
2026-04-12 09:06:12 -04:00
--health-cmd= 'wget -q --spider http://localhost:80/health || exit 1' \
2026-04-10 04:01:35 -04:00
--health-interval= 30s --health-timeout= 5s --health-retries= 3 \
--memory= 256m \
-p 8083:80 \
-v /var/lib/archipelago/filebrowser:/srv \
-v /var/lib/archipelago/filebrowser-data:/data \
${ FILEBROWSER_IMAGE } \
2026-04-12 09:06:12 -04:00
--config /data/.filebrowser.json 2>>" $LOG " && \
2026-04-10 04:01:35 -04:00
log " FileBrowser created" || log " WARNING: FileBrowser creation failed"
2026-04-12 09:06:12 -04:00
# Set noauth after first start
sleep 3
$DOCKER exec filebrowser /filebrowser config set --auth.method= noauth --database /data/filebrowser.db 2>>" $LOG " || true
$DOCKER exec filebrowser /filebrowser users add admin admin --perm.admin --database /data/filebrowser.db 2>>" $LOG " || true
$DOCKER restart filebrowser 2>>" $LOG " || true
# Create filebrowser password for backend token flow
mkdir -p /var/lib/archipelago/secrets/filebrowser
echo -n "admin" > /var/lib/archipelago/secrets/filebrowser/password
chown -R 1000:1000 /var/lib/archipelago/secrets
2026-04-10 04:01:35 -04:00
fi
2026-04-18 11:07:08 -04:00
# Generate WireGuard keys for standalone VPN (archipelago-wg service)
WG_DIR = "/var/lib/archipelago/wireguard"
if [ ! -f " $WG_DIR /private.key " ] ; then
2026-04-12 11:40:52 -04:00
log "Generating WireGuard keys..."
2026-04-18 11:07:08 -04:00
mkdir -p " $WG_DIR "
wg genkey > " $WG_DIR /private.key " 2>/dev/null
chmod 600 " $WG_DIR /private.key "
wg pubkey < " $WG_DIR /private.key " > " $WG_DIR /public.key "
chown -R 1000:1000 " $WG_DIR "
log " WireGuard keypair generated: pubkey= $( cat " $WG_DIR /public.key " ) "
2026-04-12 11:40:52 -04:00
fi
2026-04-18 11:07:08 -04:00
# Start standalone WireGuard service (wg0:51820 on 10.44.0.1/16)
modprobe wireguard 2>/dev/null || true
systemctl enable --now archipelago-wg 2>/dev/null || true
systemctl enable --now archipelago-wg-address 2>/dev/null || true
if command -v ufw >/dev/null 2>& 1 && ufw status | grep -q "Status: active" ; then
ufw allow 51820/udp >/dev/null 2>& 1 || true
fi
log " Standalone WireGuard started (wg0:51820)"
2026-04-12 11:40:52 -04:00
2026-04-10 04:01:35 -04:00
log "Unbundled first-boot complete"
exit 0
fi
2026-02-25 18:04:41 +00:00
TARGET_IP = $( hostname -I 2>/dev/null | awk '{print $1}' )
[ -z " $TARGET_IP " ] && TARGET_IP = "127.0.0.1"
2026-04-28 15:00:58 -04:00
# Stable mDNS hostname for federation/consensus URLs (survives DHCP / reinstalls).
# Falls back to $TARGET_IP if avahi is not available.
HOST_MDNS = " $( hostname 2>/dev/null) .local "
if [ -z " $HOST_MDNS " ] || [ " $HOST_MDNS " = ".local" ] ; then
HOST_MDNS = " $TARGET_IP "
fi
2026-04-23 04:16:42 -04:00
# Map host.containers.internal to the rootless-podman host gateway.
# Podman 4.4+ supports the magic string "host-gateway" which resolves to
# the correct in-container-network gateway IP at container start. We used
# to compute a value from `ip route` here, but that returned the LAN
# router (e.g. 192.168.1.254 or 192.168.1.1) — the gateway out to the
# internet, not the gateway to the host — which broke every container
# trying to reach bitcoin-core's RPC on the host (LND, ElectrumX, etc).
ADD_HOST_FLAG = "--add-host=host.containers.internal:host-gateway"
2026-04-02 01:28:11 +01:00
2026-03-31 04:33:55 +01:00
log( ) { echo " $( date '+%Y-%m-%d %H:%M:%S' ) $* " | tee -a " $LOG " ; }
2026-03-31 03:43:01 +01:00
# Ensure Tor is running for hidden services (LND connect, Electrumx, etc.)
if ! systemctl is-active tor >/dev/null 2>& 1; then
log "Starting Tor..."
systemctl enable tor 2>/dev/null || true
systemctl start tor 2>/dev/null || true
2026-03-31 04:33:55 +01:00
sleep 5
2026-03-31 03:43:01 +01:00
if systemctl is-active tor >/dev/null 2>& 1; then
log " Tor started successfully"
else
log " WARNING: Tor failed to start, hidden services will be unavailable"
fi
fi
2026-03-31 04:33:55 +01:00
# Populate tor-hostnames directory so the backend can read onion addresses
# The backend reads from /var/lib/archipelago/tor-hostnames/{service} at startup
TOR_HOSTNAMES = "/var/lib/archipelago/tor-hostnames"
mkdir -p " $TOR_HOSTNAMES "
2026-04-08 20:27:38 +02:00
# Wait for Tor to generate hostname files (setup-tor.sh may still be running)
for attempt in $( seq 1 10) ; do
[ -f /var/lib/tor/hidden_service_archipelago/hostname ] && break
log " Waiting for Tor hostnames (attempt $attempt /10)... "
sleep 3
done
for svc in archipelago bitcoin lnd electrumx btcpay mempool fedimint relay; do
2026-03-31 04:33:55 +01:00
for dir in /var/lib/tor/hidden_service_${ svc } ; do
if [ -f " $dir /hostname " ] ; then
cp " $dir /hostname " " $TOR_HOSTNAMES / $svc " 2>/dev/null
fi
done
done
chown -R archipelago:archipelago " $TOR_HOSTNAMES " 2>/dev/null
log " Tor hostnames populated: $( ls $TOR_HOSTNAMES 2>/dev/null | tr '\n' ' ' ) "
2026-02-25 18:04:41 +00:00
2026-04-08 20:27:38 +02:00
# ── Standalone WireGuard: generate keypair and start wg0 ──────────────
WG_DIR = "/var/lib/archipelago/wireguard"
mkdir -p " $WG_DIR "
if [ ! -f " $WG_DIR /private.key " ] ; then
wg genkey > " $WG_DIR /private.key " 2>/dev/null
chmod 600 " $WG_DIR /private.key "
wg pubkey < " $WG_DIR /private.key " > " $WG_DIR /public.key "
chown -R archipelago:archipelago " $WG_DIR "
log "WireGuard keypair generated"
fi
modprobe wireguard 2>/dev/null || true
systemctl enable --now archipelago-wg 2>/dev/null || true
systemctl enable --now archipelago-wg-address 2>/dev/null || true
# Open firewall port for standalone WG
if command -v ufw >/dev/null 2>& 1 && ufw status | grep -q "Status: active" ; then
ufw allow 51820/udp >/dev/null 2>& 1 || true
fi
log "Standalone WireGuard (wg0:51820) started"
2026-04-08 15:06:27 +02:00
# ── Private Nostr Relay: start for VPN signaling and general use ──────
if command -v nostr-rs-relay >/dev/null 2>& 1; then
# Relay config is pre-installed by ISO at /var/lib/archipelago/nostr-relay/config.toml
mkdir -p /var/lib/archipelago/nostr-relay
if [ ! -f /var/lib/archipelago/nostr-relay/config.toml ] && [ -f /etc/archipelago/nostr-relay-config.toml ] ; then
cp /etc/archipelago/nostr-relay-config.toml /var/lib/archipelago/nostr-relay/config.toml
fi
chown -R archipelago:archipelago /var/lib/archipelago/nostr-relay
systemctl enable --now nostr-relay 2>/dev/null || true
log "Private Nostr relay started on port 7777"
else
log "nostr-rs-relay binary not found — skipping relay setup"
fi
2026-04-18 11:07:08 -04:00
# ── NostrVPN: DISABLED — using standalone WireGuard only ──────────────
# NostrVPN (nvpn) is disabled for now. Standalone WireGuard (archipelago-wg)
# handles VPN with QR-based peer provisioning via the web UI.
log "NostrVPN disabled — standalone WireGuard only (wg0:51820)"
2026-04-07 14:40:33 +01:00
2026-03-10 23:29:05 +00:00
# Wait for a container to be healthy (accepting connections)
wait_for_container( ) {
local name = " $1 " check_cmd = " $2 " max_wait = " ${ 3 :- 30 } "
local waited = 0
while [ $waited -lt $max_wait ] ; do
if eval " $check_cmd " 2>/dev/null; then
log " $name is ready ( ${ waited } s) "
return 0
fi
sleep 2
waited = $(( waited + 2 ))
done
log " WARNING: $name not ready after ${ max_wait } s, continuing anyway "
return 1
}
2026-03-20 11:56:20 +00:00
# rpcauth: password hash in bitcoin.conf, plaintext in secrets file only.
# Credentials are STABLE across reboots, restarts, and deploys.
feat: Phase 1 — per-installation credential generation, eliminate hardcoded passwords
Generate unique random passwords at first boot for Bitcoin RPC, all database
services (mempool, btcpay, immich, penpot, mysql-root), and Fedimint gateway.
Credentials stored in /var/lib/archipelago/secrets/ with 600 permissions.
Scripts: first-boot-containers.sh, deploy-to-target.sh, deploy-bitcoin-knots.sh,
container-doctor.sh all read from secrets files instead of hardcoded values.
Rust backend: new bitcoin_rpc module reads password from secrets file, env var,
or dev fallback. All .basic_auth() calls and container config strings now use
the shared credential reader instead of hardcoded "archipelago123".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:39:52 +00:00
SECRETS_DIR = "/var/lib/archipelago/secrets"
mkdir -p " $SECRETS_DIR " && chmod 700 " $SECRETS_DIR "
if [ ! -f " $SECRETS_DIR /bitcoin-rpc-password " ] ; then
2026-03-20 11:56:20 +00:00
openssl rand -hex 16 > " $SECRETS_DIR /bitcoin-rpc-password "
feat: Phase 1 — per-installation credential generation, eliminate hardcoded passwords
Generate unique random passwords at first boot for Bitcoin RPC, all database
services (mempool, btcpay, immich, penpot, mysql-root), and Fedimint gateway.
Credentials stored in /var/lib/archipelago/secrets/ with 600 permissions.
Scripts: first-boot-containers.sh, deploy-to-target.sh, deploy-bitcoin-knots.sh,
container-doctor.sh all read from secrets files instead of hardcoded values.
Rust backend: new bitcoin_rpc module reads password from secrets file, env var,
or dev fallback. All .basic_auth() calls and container config strings now use
the shared credential reader instead of hardcoded "archipelago123".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:39:52 +00:00
chmod 600 " $SECRETS_DIR /bitcoin-rpc-password "
fi
fix: ISO boot, container installs, VPN, nginx, companion input
- LUKS auto-unlock: initramfs hook + systemd service + nofail fstab
- Rootfs packages: add passt, aardvark-dns, netavark, nftables for Podman 5.x
- nginx: resolver + variable proxy_pass for external domains (DNS at boot)
- Boot: loglevel=0 suppresses kernel warnings, serial console for QEMU
- Container installs: write configs before chown, sudo chown for LUKS volumes
- Container installs: build UI sidecars locally (not from registry) for auth injection
- Bitcoin UI: inject RPC auth from secrets file, --no-cache rebuild
- Secrets: chown to archipelago user in first-boot (backend needs read access)
- Podman: image_copy_tmp_dir for read-only /var/tmp in user namespace
- NostrVPN: enable service in auto-install, always include public relays
- NostrVPN: read tunnel IP from nvpn status (not just config file)
- VPN invite: v2 base64 no-pad format matching phone app
- Companion input: relay always active, kiosk skips relay listener (prevents double input)
- dev-start.sh: production build includes AIUI deployment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:10:49 -04:00
# Ensure archipelago user can read secrets (backend runs as archipelago, not root)
chown -R 1000:1000 " $SECRETS_DIR "
feat: Phase 1 — per-installation credential generation, eliminate hardcoded passwords
Generate unique random passwords at first boot for Bitcoin RPC, all database
services (mempool, btcpay, immich, penpot, mysql-root), and Fedimint gateway.
Credentials stored in /var/lib/archipelago/secrets/ with 600 permissions.
Scripts: first-boot-containers.sh, deploy-to-target.sh, deploy-bitcoin-knots.sh,
container-doctor.sh all read from secrets files instead of hardcoded values.
Rust backend: new bitcoin_rpc module reads password from secrets file, env var,
or dev fallback. All .basic_auth() calls and container config strings now use
the shared credential reader instead of hardcoded "archipelago123".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:39:52 +00:00
BITCOIN_RPC_USER = "archipelago"
2026-04-02 16:15:04 +01:00
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
feat: Phase 1 — per-installation credential generation, eliminate hardcoded passwords
Generate unique random passwords at first boot for Bitcoin RPC, all database
services (mempool, btcpay, immich, penpot, mysql-root), and Fedimint gateway.
Credentials stored in /var/lib/archipelago/secrets/ with 600 permissions.
Scripts: first-boot-containers.sh, deploy-to-target.sh, deploy-bitcoin-knots.sh,
container-doctor.sh all read from secrets files instead of hardcoded values.
Rust backend: new bitcoin_rpc module reads password from secrets file, env var,
or dev fallback. All .basic_auth() calls and container config strings now use
the shared credential reader instead of hardcoded "archipelago123".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:39:52 +00:00
2026-03-20 11:56:20 +00:00
# Generate rpcauth line for bitcoin.conf (salted HMAC-SHA256 hash)
generate_rpcauth( ) {
local user = " $1 " pass = " $2 "
local salt = $( openssl rand -hex 16)
local hash = $( echo -n " $pass " | openssl dgst -sha256 -hmac " $salt " -hex 2>/dev/null | awk '{print $NF}' )
echo " ${ user } : ${ salt } \$ ${ hash } "
}
# Write bitcoin.conf with rpcauth if not exists or needs update
BITCOIN_CONF = "/var/lib/archipelago/bitcoin/bitcoin.conf"
if [ ! -f " $BITCOIN_CONF " ] || ! grep -q "^rpcauth=" " $BITCOIN_CONF " 2>/dev/null; then
mkdir -p /var/lib/archipelago/bitcoin
RPCAUTH = $( generate_rpcauth " $BITCOIN_RPC_USER " " $BITCOIN_RPC_PASS " )
cat > " $BITCOIN_CONF " << BTCCONF
# rpcauth: salted hash only — no plaintext password in config or CLI
rpcauth = ${ RPCAUTH }
server = 1
rpcallowip = 0.0.0.0/0
listen = 1
printtoconsole = 1
fix: overhaul container lifecycle — recovery, health, uninstall, UI state
Container recovery:
- Health monitor: MAX_RESTART_ATTEMPTS 3→10, interval 60s→120s
- Dependency-aware restarts: won't restart services before their deps
- Reset dependent counters when a dependency recovers
- Handle "created" state containers (were invisible to health monitor)
- Added IndeedHub, mempool-api, mysql to tier system
- Crash recovery: podman start timeout 30s→120s with retry
- Podman client: socket timeout 5s→30s, added restart policy
UI state representation:
- Exit code 0 shows "stopped" (gray), not "crashed" (red)
- Exit code 137 shows "killed (OOM)"
- Non-zero exit shows "crashed" (red)
- Added exit_code field to PackageDataEntry
Install/uninstall fixes:
- Install returns error when container doesn't start (was silent success)
- Post-install hooks awaited instead of fire-and-forget tokio::spawn
- Uninstall: graceful rm before force, volume prune, network cleanup
- Uninstall returns error on partial failure (was 200 OK)
Config consistency:
- DB passwords read from /var/lib/archipelago/secrets/ (was hardcoded)
- Bitcoin: added ZMQ ports 28332/28333 for LND block notifications
- IndeedHub port 7777→8190 (was conflicting with strfry)
- Marketplace versions: LND 0.17.4→0.18.4, Mempool 2.5.0→3.0.0
Performance:
- Metrics collector interval 60s→300s (was duplicating health monitor)
- Podman client: proper error propagation instead of unwrap_or_default
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 07:03:57 +01:00
# ZMQ publishers for LND and other services that need real-time block/tx notifications
zmqpubrawblock = tcp://0.0.0.0:28332
zmqpubrawtx = tcp://0.0.0.0:28333
2026-03-20 11:56:20 +00:00
BTCCONF
log "Generated bitcoin.conf with rpcauth (no plaintext credentials)"
fi
feat: Phase 1 — per-installation credential generation, eliminate hardcoded passwords
Generate unique random passwords at first boot for Bitcoin RPC, all database
services (mempool, btcpay, immich, penpot, mysql-root), and Fedimint gateway.
Credentials stored in /var/lib/archipelago/secrets/ with 600 permissions.
Scripts: first-boot-containers.sh, deploy-to-target.sh, deploy-bitcoin-knots.sh,
container-doctor.sh all read from secrets files instead of hardcoded values.
Rust backend: new bitcoin_rpc module reads password from secrets file, env var,
or dev fallback. All .basic_auth() calls and container config strings now use
the shared credential reader instead of hardcoded "archipelago123".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:39:52 +00:00
# Generate per-installation database passwords if not already saved
2026-03-22 03:30:21 +00:00
for svc in mempool btcpay mysql-root; do
feat: Phase 1 — per-installation credential generation, eliminate hardcoded passwords
Generate unique random passwords at first boot for Bitcoin RPC, all database
services (mempool, btcpay, immich, penpot, mysql-root), and Fedimint gateway.
Credentials stored in /var/lib/archipelago/secrets/ with 600 permissions.
Scripts: first-boot-containers.sh, deploy-to-target.sh, deploy-bitcoin-knots.sh,
container-doctor.sh all read from secrets files instead of hardcoded values.
Rust backend: new bitcoin_rpc module reads password from secrets file, env var,
or dev fallback. All .basic_auth() calls and container config strings now use
the shared credential reader instead of hardcoded "archipelago123".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:39:52 +00:00
if [ ! -f " $SECRETS_DIR / ${ svc } -db-password " ] ; then
openssl rand -base64 24 > " $SECRETS_DIR / ${ svc } -db-password "
chmod 600 " $SECRETS_DIR / ${ svc } -db-password "
fi
done
MEMPOOL_DB_PASS = $( cat " $SECRETS_DIR /mempool-db-password " )
BTCPAY_DB_PASS = $( cat " $SECRETS_DIR /btcpay-db-password " )
MYSQL_ROOT_PASS = $( cat " $SECRETS_DIR /mysql-root-db-password " )
# Generate Fedimint gateway password and bcrypt hash
if [ ! -f " $SECRETS_DIR /fedimint-gateway-password " ] ; then
FEDI_PASS = $( openssl rand -base64 16)
echo " $FEDI_PASS " > " $SECRETS_DIR /fedimint-gateway-password "
chmod 600 " $SECRETS_DIR /fedimint-gateway-password "
# Pre-compute bcrypt hash (requires htpasswd from apache2-utils)
if command -v htpasswd >/dev/null 2>& 1; then
htpasswd -bnBC 10 "" " $FEDI_PASS " | tr -d ':\n' > " $SECRETS_DIR /fedimint-gateway-hash "
chmod 600 " $SECRETS_DIR /fedimint-gateway-hash "
fi
fi
FEDI_PASS = $( cat " $SECRETS_DIR /fedimint-gateway-password " )
if [ -f " $SECRETS_DIR /fedimint-gateway-hash " ] ; then
FEDI_HASH = $( cat " $SECRETS_DIR /fedimint-gateway-hash " )
else
# Fallback: generate hash now
if command -v htpasswd >/dev/null 2>& 1; then
FEDI_HASH = $( htpasswd -bnBC 10 "" " $FEDI_PASS " | tr -d ':\n' )
echo " $FEDI_HASH " > " $SECRETS_DIR /fedimint-gateway-hash "
chmod 600 " $SECRETS_DIR /fedimint-gateway-hash "
else
log "WARNING: htpasswd not found, using default Fedimint gateway hash"
FEDI_HASH = '$2y$10$t9YjjxkiktrlYvjajB/zgOMDnSNVg4HqrbDqh47u7Jf42whNdxNqC'
fi
fi
log " Fedimint gateway password stored in $SECRETS_DIR /fedimint-gateway-password "
2026-03-21 01:39:22 +00:00
BITCOIN_READY = false
TOTAL = 0
SUCCESS = 0
FAILED_LIST = ""
# Track container start result — call after each container creation attempt
track_container( ) {
local name = " $1 "
TOTAL = $(( TOTAL + 1 ))
if $DOCKER ps --filter " name=^ ${ name } $" --format '{{.Names}}' 2>/dev/null | grep -q " ^ ${ name } $" ; then
SUCCESS = $(( SUCCESS + 1 ))
log " [OK] $name is running "
else
FAILED_LIST = " $FAILED_LIST $name "
log " [FAIL] $name is NOT running "
fi
}
2026-02-25 18:04:41 +00:00
log " First-boot container creation starting (host= $TARGET_IP ) "
2026-03-14 03:05:04 +00:00
# Create swap file if not present (50% of RAM, min 2GB, max 8GB)
if ! swapon --show | grep -q /swapfile; then
TOTAL_MEM_KB = $( awk '/MemTotal/ {print $2}' /proc/meminfo)
SWAP_MB = $(( TOTAL_MEM_KB / 2 / 1024 ))
[ " $SWAP_MB " -lt 2048 ] && SWAP_MB = 2048
[ " $SWAP_MB " -gt 8192 ] && SWAP_MB = 8192
log " Creating ${ SWAP_MB } MB swap file... "
if dd if = /dev/zero of = /swapfile bs = 1M count = " $SWAP_MB " status = progress 2>>" $LOG " ; then
chmod 600 /swapfile
mkswap /swapfile >>" $LOG " 2>& 1
swapon /swapfile
if ! grep -q '/swapfile' /etc/fstab; then
echo '/swapfile none swap sw 0 0' >> /etc/fstab
fi
log " Swap created: ${ SWAP_MB } MB "
else
log "WARNING: Failed to create swap file"
fi
else
log "Swap already configured"
fi
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
# Rootless podman prerequisites (run as root, configures for archipelago user)
log "Setting up rootless podman prerequisites..."
2026-06-11 00:24:32 -04:00
if ! command -v catatonit >/dev/null 2>& 1; then
log "Installing catatonit for Podman init support..."
if command -v apt-get >/dev/null 2>& 1; then
apt-get update >>" $LOG " 2>& 1 || true
apt-get install -y catatonit >>" $LOG " 2>& 1 || true
elif command -v dnf >/dev/null 2>& 1; then
dnf install -y catatonit >>" $LOG " 2>& 1 || true
elif command -v apk >/dev/null 2>& 1; then
apk add catatonit >>" $LOG " 2>& 1 || true
fi
fi
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
# Allow binding to ports >= 80 (rootless default is 1024)
if ! grep -q "unprivileged_port_start=80" /etc/sysctl.d/99-rootless-podman.conf 2>/dev/null; then
echo "net.ipv4.ip_unprivileged_port_start=80" > /etc/sysctl.d/99-rootless-podman.conf
sysctl -p /etc/sysctl.d/99-rootless-podman.conf 2>/dev/null
log " Rootless port binding enabled (>=80)"
fi
# Linger for container persistence after logout
if [ " $( loginctl show-user archipelago 2>/dev/null | grep Linger) " != "Linger=yes" ] ; then
loginctl enable-linger archipelago 2>/dev/null
log " Linger enabled for archipelago user"
fi
# Ensure subuid/subgid mappings
grep -q "^archipelago:" /etc/subuid 2>/dev/null || {
echo "archipelago:100000:65536" >> /etc/subuid
echo "archipelago:100000:65536" >> /etc/subgid
log " subuid/subgid configured"
}
2026-04-18 11:07:08 -04:00
# Apply podman migrations after subuid/subgid changes (per official tutorial)
$DOCKER system migrate 2>/dev/null || true
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
# Ensure /etc/hosts is readable (rootless podman needs it)
chmod 644 /etc/hosts 2>/dev/null
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
# Ensure XDG_RUNTIME_DIR exists for rootless podman
2026-04-03 12:33:15 +01:00
mkdir -p /run/user/$( id -u archipelago)
chown archipelago:archipelago /run/user/$( id -u archipelago)
chmod 700 /run/user/$( id -u archipelago)
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
# Start rootless podman socket (required before first podman command)
2026-04-03 12:33:15 +01:00
runuser -u archipelago -- env XDG_RUNTIME_DIR = /run/user/$( id -u archipelago) \
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
systemctl --user start podman.socket 2>/dev/null || true
2026-03-31 18:31:00 +01:00
# Ensure archy-net exists — critical for inter-container DNS (mempool→bitcoin, etc.)
2026-02-25 18:04:41 +00:00
$DOCKER network create archy-net 2>/dev/null || true
2026-03-31 18:31:00 +01:00
if ! $DOCKER network exists archy-net 2>/dev/null; then
log "WARNING: archy-net creation failed, retrying in 5s..."
sleep 5
$DOCKER network create archy-net 2>>" $LOG "
if ! $DOCKER network exists archy-net 2>/dev/null; then
log "FATAL: Cannot create archy-net — inter-container DNS will not work."
log " All containers requiring archy-net will fail. Exiting."
exit 1
fi
fi
log "archy-net network ready"
2026-02-25 18:04:41 +00:00
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
# Rootless podman UID mapping: fix data dir ownership so container processes
# can write. Rootless podman maps container UIDs via subuid (container UID N
# → host UID 100000+N). Must run BEFORE container creation.
log "Fixing rootless podman UID mapping..."
# Containers running as root (UID 0 → host UID 100000)
2026-03-22 03:30:21 +00:00
for dir in lnd electrumx btcpay nbxplorer jellyfin vaultwarden \
2026-05-19 09:26:43 -04:00
home-assistant fedimint fedimint-gateway photoprism ollama filebrowser \
nextcloud uptime-kuma portainer nostr-rs-relay; do
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
[ -d " /var/lib/archipelago/ $dir " ] && chown -R 100000:100000 " /var/lib/archipelago/ $dir " 2>/dev/null
done
2026-05-19 09:26:43 -04:00
# Nginx Proxy Manager runs as root in the rootless user namespace, which maps to
# the archipelago user on host bind mounts. Keep certbot's webroot writable.
[ -d /var/lib/archipelago/nginx-proxy-manager ] && chown -R 1000:1000 /var/lib/archipelago/nginx-proxy-manager 2>/dev/null
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
# Bitcoin Knots: container UID 101 → host UID 100101
[ -d /var/lib/archipelago/bitcoin ] && chown -R 100101:100101 /var/lib/archipelago/bitcoin 2>/dev/null
# Postgres: container UID 70 → host UID 100070
2026-03-22 03:30:21 +00:00
for dir in postgres-btcpay; do
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
[ -d " /var/lib/archipelago/ $dir " ] && chown -R 100070:100070 " /var/lib/archipelago/ $dir " 2>/dev/null
done
# MariaDB: container UID 999 → host UID 100999
for dir in mempool mysql-mempool; do
[ -d " /var/lib/archipelago/ $dir " ] && chown -R 100999:100999 " /var/lib/archipelago/ $dir " 2>/dev/null
done
2026-05-06 09:23:57 -04:00
# Grafana: chown inside podman's user namespace so container UID 472 can write SQLite.
[ -d /var/lib/archipelago/grafana ] && podman unshare chown -R 472:472 /var/lib/archipelago/grafana 2>/dev/null || true
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
log "UID mapping done"
# ── Memory limits per container ──────────────────────────────────────────
# Matches core/archipelago/src/api/rpc/package.rs get_memory_limit()
# Prevents a single runaway container from OOMing the whole system.
TOTAL_MEM_MB = $(( $( awk '/MemTotal/{print $2}' /proc/meminfo) / 1024 ))
LOW_MEM = false
[ " $TOTAL_MEM_MB " -lt 12000 ] && LOW_MEM = true && log " Low-memory system ( ${ TOTAL_MEM_MB } MB) — reducing limits "
mem_limit( ) {
case " $1 " in
2026-05-18 11:47:12 -04:00
bitcoin| bitcoin-core| bitcoin-knots) $LOW_MEM && echo "4g" || echo "8g" ; ;
2026-04-02 16:15:04 +01:00
cryptpad) echo "512m" ; ;
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
ollama) $LOW_MEM && echo "1g" || echo "4g" ; ;
lnd) echo "512m" ; ;
2026-05-18 11:47:12 -04:00
electrumx| mempool-electrs| electrs) echo "4g" ; ;
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
nextcloud) echo "1g" ; ;
2026-05-18 11:47:12 -04:00
btcpay-server| btcpayserver) echo "1g" ; ;
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
homeassistant) echo "512m" ; ;
fedimint) echo "512m" ; ;
fedimint-gateway) echo "512m" ; ;
photoprism) $LOW_MEM && echo "512m" || echo "1g" ; ;
mempool-api) echo "512m" ; ;
jellyfin) echo "1g" ; ;
searxng) echo "512m" ; ;
archy-btcpay-db) echo "512m" ; ;
archy-nbxplorer) echo "512m" ; ;
archy-mempool-db) echo "512m" ; ;
archy-mempool-web) echo "256m" ; ;
grafana) echo "256m" ; ;
vaultwarden) echo "256m" ; ;
uptime-kuma) echo "256m" ; ;
filebrowser) echo "256m" ; ;
portainer) echo "256m" ; ;
nginx-proxy-manager) echo "256m" ; ;
tailscale) echo "256m" ; ;
indeedhub| archy-bitcoin-ui| archy-lnd-ui| archy-electrs-ui) echo "128m" ; ;
*) echo "512m" ; ;
esac
}
2026-03-31 18:31:00 +01:00
# ── Verify critical images are loaded ──────────────────────────────────
# archipelago-load-images.service should have loaded these from tarballs.
# If any are missing (corrupt tarball, disk full, etc.), try re-loading.
log "Verifying container images..."
MISSING_IMAGES = ""
for img_var in BITCOIN_KNOTS_IMAGE MARIADB_IMAGE ELECTRUMX_IMAGE \
MEMPOOL_BACKEND_IMAGE MEMPOOL_WEB_IMAGE BTCPAY_POSTGRES_IMAGE \
NBXPLORER_IMAGE BTCPAY_IMAGE LND_IMAGE FEDIMINT_IMAGE \
FEDIMINT_GATEWAY_IMAGE HOMEASSISTANT_IMAGE GRAFANA_IMAGE \
UPTIME_KUMA_IMAGE JELLYFIN_IMAGE VAULTWARDEN_IMAGE \
NEXTCLOUD_IMAGE SEARXNG_IMAGE FILEBROWSER_IMAGE; do
img = " ${ !img_var } "
if [ -z " $img " ] ; then
continue # Variable not defined in image-versions.sh
fi
if ! $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -qF " $img " ; then
MISSING_IMAGES = " $MISSING_IMAGES $img_var "
fi
done
if [ -n " $MISSING_IMAGES " ] ; then
log " WARNING: Missing images: $MISSING_IMAGES "
log "Attempting to re-load from /opt/archipelago/container-images/..."
RELOAD_COUNT = 0
for tarfile in /opt/archipelago/container-images/*.tar; do
if [ -f " $tarfile " ] ; then
if $DOCKER load -i " $tarfile " 2>>" $LOG " ; then
RELOAD_COUNT = $(( RELOAD_COUNT + 1 ))
else
log " Failed to load: $tarfile "
fi
fi
done
log " Re-loaded $RELOAD_COUNT image tarballs "
else
log "All critical images verified"
fi
2026-03-14 03:06:20 +00:00
# ── Tier 1: Databases & Core Infrastructure ──────────────────────────────
log "=== Tier 1: Databases & Core Infrastructure ==="
2026-02-25 18:04:41 +00:00
# 1. Bitcoin Knots (matches deploy exactly)
2026-03-16 12:58:35 +00:00
# Auto-detect: if disk < 1TB, use pruning to prevent disk-full crashes
2026-02-25 18:04:41 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'bitcoin-knots|archy-bitcoin-knots' ; then
log "Creating Bitcoin Knots..."
mkdir -p /var/lib/archipelago/bitcoin
2026-04-02 10:34:58 +01:00
# Check the DATA partition size, not root — Bitcoin data goes to /var/lib/archipelago
DISK_GB = $( df --output= size -BG /var/lib/archipelago 2>/dev/null | tail -1 | tr -dc '0-9' )
[ -z " $DISK_GB " ] && DISK_GB = $( df --output= size -BG / 2>/dev/null | tail -1 | tr -dc '0-9' )
2026-03-16 12:58:35 +00:00
if [ " ${ DISK_GB :- 0 } " -lt 1000 ] ; then
BTC_EXTRA_ARGS = "-prune=550"
2026-05-18 11:47:12 -04:00
if [ " $LOW_MEM " = "true" ] ; then
BTC_DBCACHE = 2048
else
BTC_DBCACHE = 4096
fi
2026-03-16 12:58:35 +00:00
log " Small disk ( ${ DISK_GB } GB) — enabling pruning "
else
BTC_EXTRA_ARGS = "-txindex=1"
2026-05-13 22:59:55 -04:00
BTC_DBCACHE = 4096
2026-03-16 12:58:35 +00:00
log " Large disk ( ${ DISK_GB } GB) — enabling txindex "
fi
2026-03-21 01:11:05 +00:00
if $DOCKER run -d --name bitcoin-knots --restart unless-stopped \
2026-04-02 16:15:04 +01:00
--health-cmd= "bitcoin-cli -datadir=/home/bitcoin/.bitcoin getblockchaininfo || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-04-02 10:34:58 +01:00
--memory= $( mem_limit bitcoin-knots) --network archy-net --network-alias bitcoin-knots \
2026-04-02 01:28:11 +01:00
$ADD_HOST_FLAG \
2026-03-12 22:19:04 +00:00
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
fix: harden container isolation in first-boot script (PENTEST-03)
Add --cap-drop ALL and --security-opt no-new-privileges:true to all
containers in first-boot-containers.sh that were missing it:
- Bitcoin Knots, LND, Fedimint, Fedimint Gateway (+ CHOWN/SETUID/SETGID)
- BTCPay Server, Home Assistant (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Nextcloud (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Grafana, Uptime Kuma, PhotoPrism, Ollama, Vaultwarden, FileBrowser
(zero extra caps + --read-only + tmpfs for /tmp and /run)
- Jellyfin (zero extra caps)
Tailscale retains --privileged (required for TUN/iptables/routing).
SearXNG, OnlyOffice, Nginx Proxy Manager, Portainer already hardened.
The Rust RPC layer already applies equivalent hardening for all UI
installs; this brings the ISO first-boot path to parity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:40:04 +00:00
--security-opt no-new-privileges:true \
fix: overhaul container lifecycle — recovery, health, uninstall, UI state
Container recovery:
- Health monitor: MAX_RESTART_ATTEMPTS 3→10, interval 60s→120s
- Dependency-aware restarts: won't restart services before their deps
- Reset dependent counters when a dependency recovers
- Handle "created" state containers (were invisible to health monitor)
- Added IndeedHub, mempool-api, mysql to tier system
- Crash recovery: podman start timeout 30s→120s with retry
- Podman client: socket timeout 5s→30s, added restart policy
UI state representation:
- Exit code 0 shows "stopped" (gray), not "crashed" (red)
- Exit code 137 shows "killed (OOM)"
- Non-zero exit shows "crashed" (red)
- Added exit_code field to PackageDataEntry
Install/uninstall fixes:
- Install returns error when container doesn't start (was silent success)
- Post-install hooks awaited instead of fire-and-forget tokio::spawn
- Uninstall: graceful rm before force, volume prune, network cleanup
- Uninstall returns error on partial failure (was 200 OK)
Config consistency:
- DB passwords read from /var/lib/archipelago/secrets/ (was hardcoded)
- Bitcoin: added ZMQ ports 28332/28333 for LND block notifications
- IndeedHub port 7777→8190 (was conflicting with strfry)
- Marketplace versions: LND 0.17.4→0.18.4, Mempool 2.5.0→3.0.0
Performance:
- Metrics collector interval 60s→300s (was duplicating health monitor)
- Podman client: proper error propagation instead of unwrap_or_default
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 07:03:57 +01:00
-p 8332:8332 -p 8333:8333 -p 28332:28332 -p 28333:28333 \
2026-02-25 18:04:41 +00:00
-v /var/lib/archipelago/bitcoin:/home/bitcoin/.bitcoin \
2026-03-26 14:06:21 +00:00
" ${ BITCOIN_KNOTS_IMAGE } " \
2026-04-03 00:56:26 +01:00
$BTC_EXTRA_ARGS \
2026-06-11 00:24:32 -04:00
-printtoconsole= 1 -dbcache= $BTC_DBCACHE -par= 0 -maxconnections= 125 -rpcthreads= 16 -rpcworkqueue= 256 2>>" $LOG " ; then
2026-02-25 18:04:41 +00:00
log "Bitcoin Knots started"
else
log "Bitcoin Knots failed (may already exist)"
fi
else
$DOCKER network connect archy-net bitcoin-knots 2>/dev/null || true
log "Bitcoin Knots already running"
fi
2026-03-31 18:31:00 +01:00
# 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.
2026-04-02 16:15:04 +01:00
if wait_for_container "Bitcoin Knots RPC" " $DOCKER exec bitcoin-knots bitcoin-cli -datadir=/home/bitcoin/.bitcoin getblockchaininfo " 60; then
2026-03-21 01:39:22 +00:00
BITCOIN_READY = true
2026-03-31 18:31:00 +01:00
log "Bitcoin Knots is ready"
2026-03-21 01:39:22 +00:00
else
BITCOIN_READY = false
2026-03-31 18:31:00 +01:00
log "Bitcoin Knots not yet responsive (normal during IBD) — creating dependent containers anyway"
log " They will auto-restart via health monitor once Bitcoin is ready"
2026-03-21 01:39:22 +00:00
fi
track_container "bitcoin-knots"
2026-02-25 18:04:41 +00:00
2026-03-16 15:34:04 +00:00
# Ensure wallet exists (Bitcoin Knots no longer auto-creates a default wallet)
2026-04-02 16:15:04 +01:00
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
2026-03-16 15:34:04 +00:00
log "Bitcoin Knots wallet 'archipelago' created/loaded"
fi
2026-04-02 16:15:04 +01:00
# ── 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)
2026-04-23 04:16:42 -04:00
BTC_HOST = " $TARGET_IP "
2026-04-02 16:15:04 +01:00
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 }
2026-03-21 01:39:22 +00:00
# 2. Mempool stack (matches deploy) — depends on Bitcoin
2026-03-31 18:31:00 +01:00
# Note: containers created regardless of BITCOIN_READY — they will restart
# automatically once Bitcoin becomes responsive (--restart=unless-stopped).
2026-02-25 18:04:41 +00:00
if ! $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-mempool-db|mysql-mempool' ; then
log "Creating mysql-mempool..."
mkdir -p /var/lib/archipelago/mysql-mempool
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name archy-mempool-db --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "mariadb -uroot -e 'SELECT 1' || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-04-02 10:34:58 +01:00
--memory= $( mem_limit archy-mempool-db) --network archy-net --network-alias archy-mempool-db \
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
2026-02-25 18:04:41 +00:00
-v /var/lib/archipelago/mysql-mempool:/var/lib/mysql \
2026-03-21 02:06:08 +00:00
-e MYSQL_DATABASE = mempool -e MYSQL_USER = mempool -e " MYSQL_PASSWORD= $MEMPOOL_DB_PASS " \
-e " MYSQL_ROOT_PASSWORD= $MYSQL_ROOT_PASS " \
2026-03-26 14:06:21 +00:00
" $MARIADB_IMAGE " 2>>" $LOG " || true
2026-03-21 01:39:22 +00:00
wait_for_container "Mempool MariaDB" " echo 'SELECT 1' | $DOCKER exec -i archy-mempool-db mariadb -uroot --password=\" $MYSQL_ROOT_PASS \" " 30
2026-02-25 18:04:41 +00:00
fi
MYSQL_CNT = $( $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -E 'mysql-mempool|archy-mempool-db' | head -1)
MYSQL_CNT = ${ MYSQL_CNT :- archy -mempool-db }
$DOCKER network connect archy-net " $MYSQL_CNT " 2>/dev/null || true
2026-03-21 01:39:22 +00:00
track_container "archy-mempool-db"
2026-02-25 18:04:41 +00:00
2026-03-16 12:58:35 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q electrumx; then
if $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q electrumx; then
$DOCKER start electrumx 2>/dev/null || true
2026-02-25 18:04:41 +00:00
else
2026-03-16 12:58:35 +00:00
log "Creating electrumx..."
mkdir -p /var/lib/archipelago/electrumx
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name electrumx --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:8000/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-04-02 10:34:58 +01:00
--memory= $( mem_limit electrumx) --network archy-net --network-alias electrumx \
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
2026-03-16 12:58:35 +00:00
-p 50001:50001 -v /var/lib/archipelago/electrumx:/data \
2026-04-02 16:15:04 +01:00
-e " DAEMON_URL=http:// $BTC_RPC_USER : $BTC_RPC_PASS @ $BTC_HOST : $BTC_PORT / " \
2026-03-16 12:58:35 +00:00
-e COIN = Bitcoin -e DB_DIRECTORY = /data \
-e SERVICES = tcp://:50001,rpc://0.0.0.0:8000 \
2026-03-26 14:06:21 +00:00
" $ELECTRUMX_IMAGE " 2>>" $LOG " || true
2026-02-25 18:04:41 +00:00
fi
fi
2026-03-21 01:39:22 +00:00
track_container "electrumx"
2026-02-25 18:04:41 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q mempool-api; then
log "Creating mempool-api..."
mkdir -p /var/lib/archipelago/mempool
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name mempool-api --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:8999/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-04-02 10:34:58 +01:00
--memory= $( mem_limit mempool-api) --network archy-net --network-alias mempool-api \
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
2026-02-25 18:04:41 +00:00
-p 8999:8999 -v /var/lib/archipelago/mempool:/data \
2026-03-16 12:58:35 +00:00
-e MEMPOOL_BACKEND = electrum -e ELECTRUM_HOST = electrumx -e ELECTRUM_PORT = 50001 \
2026-04-02 16:15:04 +01:00
-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 " \
2026-02-25 18:04:41 +00:00
-e DATABASE_ENABLED = true -e DATABASE_HOST = " $MYSQL_CNT " -e DATABASE_DATABASE = mempool \
2026-03-21 02:06:08 +00:00
-e DATABASE_USERNAME = mempool -e " DATABASE_PASSWORD= $MEMPOOL_DB_PASS " \
2026-03-26 14:06:21 +00:00
" $MEMPOOL_BACKEND_IMAGE " 2>>" $LOG " || true
2026-02-25 18:04:41 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "mempool-api"
2026-02-25 18:04:41 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-mempool-web|mempool-web' ; then
log "Creating mempool frontend..."
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name archy-mempool-web --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:8080/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-04-02 10:34:58 +01:00
--memory= $( mem_limit archy-mempool-web) --network archy-net --network-alias archy-mempool-web \
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
2026-02-25 18:04:41 +00:00
-p 4080:8080 -e FRONTEND_HTTP_PORT = 8080 -e BACKEND_MAINNET_HTTP_HOST = mempool-api \
2026-03-26 14:06:21 +00:00
" $MEMPOOL_WEB_IMAGE " 2>>" $LOG " || true
2026-02-25 18:04:41 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "archy-mempool-web"
2026-02-25 18:04:41 +00:00
2026-03-16 12:58:35 +00:00
# 2b. ElectrumX UI (status dashboard on port 50002, host network for backend access)
2026-03-08 02:27:58 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q electrs-ui; then
if $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q 'electrs-ui' ; then
2026-03-16 12:58:35 +00:00
log "Starting ElectrumX UI from pre-built image..."
2026-03-08 02:27:58 +00:00
$DOCKER run -d --name archy-electrs-ui --network host --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:80/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
fix: container DNS, nginx chown, onboarding guard, seed UX, install flow
Backend:
- Add --add-host host.containers.internal:host-gateway to LND and Bitcoin
Knots containers (fixes DNS resolution failure in rootless podman)
- Add --user 0:0 and DAC_OVERRIDE to nginx UI sidecar containers
(fixes chown crash in rootless podman for bitcoin-ui, electrs-ui, lnd-ui)
- Add hostadd to Rust Podman API client for web UI container installs
- Add Chromium privacy flags to kiosk launcher (disable telemetry)
Frontend:
- Fix onboarding reset on raw IP visits (trust localStorage as first-class
signal, skip boot screen when server is up but not onboarded)
- Fix seed regression: persist challenge indices in sessionStorage so going
back from Verify doesn't change which words are asked
- Remove glass container from seed Generate/Verify/Restore screens
- Add Back button to Restore from Seed screen
- Replace Network card: Tor (purple), VPN status (orange), Bitcoin sync (orange)
- Add ElectrumX to curated app list with correct .webp icon
- Install flow: navigate to My Apps immediately with toast, hide
installed/installing apps from marketplace and discover views
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 13:06:57 +01:00
--user 0:0 \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
2026-03-21 01:32:28 +00:00
localhost/electrs-ui:local 2>>" $LOG " || \
2026-03-08 02:27:58 +00:00
$DOCKER run -d --name archy-electrs-ui --network host --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:80/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
fix: container DNS, nginx chown, onboarding guard, seed UX, install flow
Backend:
- Add --add-host host.containers.internal:host-gateway to LND and Bitcoin
Knots containers (fixes DNS resolution failure in rootless podman)
- Add --user 0:0 and DAC_OVERRIDE to nginx UI sidecar containers
(fixes chown crash in rootless podman for bitcoin-ui, electrs-ui, lnd-ui)
- Add hostadd to Rust Podman API client for web UI container installs
- Add Chromium privacy flags to kiosk launcher (disable telemetry)
Frontend:
- Fix onboarding reset on raw IP visits (trust localStorage as first-class
signal, skip boot screen when server is up but not onboarded)
- Fix seed regression: persist challenge indices in sessionStorage so going
back from Verify doesn't change which words are asked
- Remove glass container from seed Generate/Verify/Restore screens
- Add Back button to Restore from Seed screen
- Replace Network card: Tor (purple), VPN status (orange), Bitcoin sync (orange)
- Add ElectrumX to curated app list with correct .webp icon
- Install flow: navigate to My Apps immediately with toast, hide
installed/installing apps from marketplace and discover views
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 13:06:57 +01:00
--user 0:0 \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
2026-03-21 01:32:28 +00:00
electrs-ui:local 2>>" $LOG " || true
2026-03-08 02:27:58 +00:00
elif [ -d /opt/archipelago/docker/electrs-ui ] ; then
2026-03-16 12:58:35 +00:00
log "Building and starting ElectrumX UI from source..."
2026-03-21 01:32:28 +00:00
$DOCKER build -t electrs-ui:local /opt/archipelago/docker/electrs-ui 2>>" $LOG " && \
2026-03-08 02:27:58 +00:00
$DOCKER run -d --name archy-electrs-ui --network host --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:80/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
fix: container DNS, nginx chown, onboarding guard, seed UX, install flow
Backend:
- Add --add-host host.containers.internal:host-gateway to LND and Bitcoin
Knots containers (fixes DNS resolution failure in rootless podman)
- Add --user 0:0 and DAC_OVERRIDE to nginx UI sidecar containers
(fixes chown crash in rootless podman for bitcoin-ui, electrs-ui, lnd-ui)
- Add hostadd to Rust Podman API client for web UI container installs
- Add Chromium privacy flags to kiosk launcher (disable telemetry)
Frontend:
- Fix onboarding reset on raw IP visits (trust localStorage as first-class
signal, skip boot screen when server is up but not onboarded)
- Fix seed regression: persist challenge indices in sessionStorage so going
back from Verify doesn't change which words are asked
- Remove glass container from seed Generate/Verify/Restore screens
- Add Back button to Restore from Seed screen
- Replace Network card: Tor (purple), VPN status (orange), Bitcoin sync (orange)
- Add ElectrumX to curated app list with correct .webp icon
- Install flow: navigate to My Apps immediately with toast, hide
installed/installing apps from marketplace and discover views
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 13:06:57 +01:00
--user 0:0 \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
2026-03-21 01:32:28 +00:00
electrs-ui:local 2>>" $LOG " || true
2026-03-08 02:27:58 +00:00
else
2026-03-16 12:58:35 +00:00
log "ElectrumX UI: no image or source found, skipping"
2026-03-08 02:27:58 +00:00
fi
fi
2026-03-21 01:39:22 +00:00
# 3. BTCPay stack (matches deploy) — depends on Bitcoin
2026-02-25 18:04:41 +00:00
if ! $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-btcpay-db|postgres-btcpay' ; then
log "Creating PostgreSQL for BTCPay..."
mkdir -p /var/lib/archipelago/postgres-btcpay
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name archy-btcpay-db --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "pg_isready -U postgres || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-04-02 10:34:58 +01:00
--memory= $( mem_limit archy-btcpay-db) --network archy-net --network-alias archy-btcpay-db \
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
2026-02-25 18:04:41 +00:00
-v /var/lib/archipelago/postgres-btcpay:/var/lib/postgresql/data \
2026-03-21 02:06:08 +00:00
-e POSTGRES_DB = btcpay -e POSTGRES_USER = btcpay -e " POSTGRES_PASSWORD= $BTCPAY_DB_PASS " \
2026-03-26 14:06:21 +00:00
" $BTCPAY_POSTGRES_IMAGE " 2>>" $LOG " || true
2026-03-10 23:29:05 +00:00
wait_for_container "BTCPay PostgreSQL" " $DOCKER exec archy-btcpay-db pg_isready -U postgres " 30
2026-02-25 18:04:41 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "archy-btcpay-db"
2026-02-25 18:04:41 +00:00
# Create nbxplorer DB only if postgres is running
if $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-btcpay-db|postgres-btcpay' ; then
$DOCKER exec archy-btcpay-db psql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname='nbxplorer'" 2>/dev/null | grep -q 1 || \
2026-03-21 02:06:08 +00:00
$DOCKER exec -e " PGPASSWORD= $BTCPAY_DB_PASS " archy-btcpay-db psql -U postgres -c "CREATE DATABASE nbxplorer;" 2>/dev/null || true
2026-02-25 18:04:41 +00:00
fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q archy-nbxplorer; then
if $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q archy-nbxplorer; then
$DOCKER start archy-nbxplorer 2>/dev/null || true
else
log "Creating NBXplorer..."
2026-04-02 20:28:53 +01:00
mkdir -p /var/lib/archipelago/nbxplorer/Main
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name archy-nbxplorer --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:32838/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-04-02 10:34:58 +01:00
--memory= $( mem_limit archy-nbxplorer) --network archy-net --network-alias archy-nbxplorer \
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
2026-02-25 18:04:41 +00:00
-p 32838:32838 -v /var/lib/archipelago/nbxplorer:/data \
-e NBXPLORER_DATADIR = /data -e NBXPLORER_NETWORK = mainnet -e NBXPLORER_CHAINS = btc \
2026-04-02 16:15:04 +01:00
-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 " \
feat: Phase 1 — per-installation credential generation, eliminate hardcoded passwords
Generate unique random passwords at first boot for Bitcoin RPC, all database
services (mempool, btcpay, immich, penpot, mysql-root), and Fedimint gateway.
Credentials stored in /var/lib/archipelago/secrets/ with 600 permissions.
Scripts: first-boot-containers.sh, deploy-to-target.sh, deploy-bitcoin-knots.sh,
container-doctor.sh all read from secrets files instead of hardcoded values.
Rust backend: new bitcoin_rpc module reads password from secrets file, env var,
or dev fallback. All .basic_auth() calls and container config strings now use
the shared credential reader instead of hardcoded "archipelago123".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:39:52 +00:00
-e NBXPLORER_POSTGRES = 'User ID=btcpay;Password=$BTCPAY_DB_PASS;Host=archy-btcpay-db;Port=5432;Database=nbxplorer;Include Error Detail=true' \
2026-03-26 14:06:21 +00:00
" $NBXPLORER_IMAGE " 2>>" $LOG " && sleep 5 || true
2026-02-25 18:04:41 +00:00
fi
fi
2026-03-21 01:39:22 +00:00
track_container "archy-nbxplorer"
2026-02-25 18:04:41 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q btcpay-server; then
log "Creating BTCPay Server..."
2026-04-02 20:28:53 +01:00
mkdir -p /var/lib/archipelago/btcpay/Main
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name btcpay-server --restart unless-stopped \
2026-05-19 09:26:43 -04:00
--health-cmd= "bash -ec '</dev/tcp/127.0.0.1/49392'" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-04-02 10:34:58 +01:00
--memory= $( mem_limit btcpay-server) --network archy-net --network-alias btcpay-server \
fix: harden container isolation in first-boot script (PENTEST-03)
Add --cap-drop ALL and --security-opt no-new-privileges:true to all
containers in first-boot-containers.sh that were missing it:
- Bitcoin Knots, LND, Fedimint, Fedimint Gateway (+ CHOWN/SETUID/SETGID)
- BTCPay Server, Home Assistant (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Nextcloud (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Grafana, Uptime Kuma, PhotoPrism, Ollama, Vaultwarden, FileBrowser
(zero extra caps + --read-only + tmpfs for /tmp and /run)
- Jellyfin (zero extra caps)
Tailscale retains --privileged (required for TUN/iptables/routing).
SearXNG, OnlyOffice, Nginx Proxy Manager, Portainer already hardened.
The Rust RPC layer already applies equivalent hardening for all UI
installs; this brings the ISO first-boot path to parity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:40:04 +00:00
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
2026-02-25 18:04:41 +00:00
-p 23000:49392 -v /var/lib/archipelago/btcpay:/datadir \
-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 \
2026-04-02 16:15:04 +01:00
-e " BTCPAY_BTCRPCURL=http:// $BTC_HOST : $BTC_PORT " \
-e " BTCPAY_BTCRPCUSER= $BTC_RPC_USER " -e " BTCPAY_BTCRPCPASSWORD= $BTC_RPC_PASS " \
feat: Phase 1 — per-installation credential generation, eliminate hardcoded passwords
Generate unique random passwords at first boot for Bitcoin RPC, all database
services (mempool, btcpay, immich, penpot, mysql-root), and Fedimint gateway.
Credentials stored in /var/lib/archipelago/secrets/ with 600 permissions.
Scripts: first-boot-containers.sh, deploy-to-target.sh, deploy-bitcoin-knots.sh,
container-doctor.sh all read from secrets files instead of hardcoded values.
Rust backend: new bitcoin_rpc module reads password from secrets file, env var,
or dev fallback. All .basic_auth() calls and container config strings now use
the shared credential reader instead of hardcoded "archipelago123".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:39:52 +00:00
-e BTCPAY_POSTGRES = 'User ID=btcpay;Password=$BTCPAY_DB_PASS;Host=archy-btcpay-db;Port=5432;Database=btcpay;Include Error Detail=true' \
2026-03-26 14:06:21 +00:00
" $BTCPAY_IMAGE " 2>>" $LOG " || true
2026-02-25 18:04:41 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "btcpay-server"
2026-02-25 18:04:41 +00:00
2026-03-14 03:06:20 +00:00
# ── Tier 2: Core Services ─────────────────────────────────────────────────
log "=== Tier 2: Core Services ==="
sleep 5 # Let databases stabilize
2026-03-21 01:39:22 +00:00
# 4. LND — depends on Bitcoin
2026-02-25 18:04:41 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE '^lnd$' ; then
log "Creating LND..."
mkdir -p /var/lib/archipelago/lnd
2026-03-20 11:56:20 +00:00
# Create lnd.conf with rpcauth credentials (stable across restarts)
2026-03-09 17:09:59 +00:00
if [ ! -f /var/lib/archipelago/lnd/lnd.conf ] ; then
feat: Phase 1 — per-installation credential generation, eliminate hardcoded passwords
Generate unique random passwords at first boot for Bitcoin RPC, all database
services (mempool, btcpay, immich, penpot, mysql-root), and Fedimint gateway.
Credentials stored in /var/lib/archipelago/secrets/ with 600 permissions.
Scripts: first-boot-containers.sh, deploy-to-target.sh, deploy-bitcoin-knots.sh,
container-doctor.sh all read from secrets files instead of hardcoded values.
Rust backend: new bitcoin_rpc module reads password from secrets file, env var,
or dev fallback. All .basic_auth() calls and container config strings now use
the shared credential reader instead of hardcoded "archipelago123".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:39:52 +00:00
cat > /var/lib/archipelago/lnd/lnd.conf <<LNDCONF
2026-03-09 17:09:59 +00:00
[ Application Options]
listen = 0.0.0.0:9735
rpclisten = 0.0.0.0:10009
restlisten = 0.0.0.0:8080
debuglevel = info
noseedbackup = true
2026-03-31 23:01:51 +01:00
tlsextraip = 0.0.0.0
tlsextradomain = lnd
2026-03-18 01:05:22 +00:00
tor.active= true
2026-03-20 11:56:20 +00:00
tor.socks= host.containers.internal:9050
2026-03-18 01:05:22 +00:00
tor.streamisolation= true
2026-03-09 17:09:59 +00:00
[ Bitcoin]
bitcoin.mainnet= true
bitcoin.node= bitcoind
[ Bitcoind]
2026-04-02 16:15:04 +01:00
bitcoind.rpchost= $BTC_HOST :$BTC_PORT
bitcoind.rpcuser= $BTC_RPC_USER
bitcoind.rpcpass= $BTC_RPC_PASS
2026-03-09 17:09:59 +00:00
bitcoind.rpcpolling= true
bitcoind.estimatemode= ECONOMICAL
[ autopilot]
autopilot.active= false
LNDCONF
2026-03-20 11:56:20 +00:00
log "LND config created (rpcauth credentials, Tor via system)"
2026-03-09 17:09:59 +00:00
fi
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name lnd --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf --insecure https://localhost:8080/v1/getinfo || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-04-02 10:34:58 +01:00
--memory= $( mem_limit lnd) --network archy-net --network-alias lnd \
2026-04-02 01:28:11 +01:00
$ADD_HOST_FLAG \
2026-03-31 23:19:09 +01:00
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE --cap-add NET_RAW \
fix: harden container isolation in first-boot script (PENTEST-03)
Add --cap-drop ALL and --security-opt no-new-privileges:true to all
containers in first-boot-containers.sh that were missing it:
- Bitcoin Knots, LND, Fedimint, Fedimint Gateway (+ CHOWN/SETUID/SETGID)
- BTCPay Server, Home Assistant (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Nextcloud (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Grafana, Uptime Kuma, PhotoPrism, Ollama, Vaultwarden, FileBrowser
(zero extra caps + --read-only + tmpfs for /tmp and /run)
- Jellyfin (zero extra caps)
Tailscale retains --privileged (required for TUN/iptables/routing).
SearXNG, OnlyOffice, Nginx Proxy Manager, Portainer already hardened.
The Rust RPC layer already applies equivalent hardening for all UI
installs; this brings the ISO first-boot path to parity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:40:04 +00:00
--security-opt no-new-privileges:true \
2026-05-13 15:09:22 -04:00
-p 9735:9735 -p 10009:10009 -p 18080:8080 \
2026-02-25 18:04:41 +00:00
-v /var/lib/archipelago/lnd:/root/.lnd \
2026-03-26 14:06:21 +00:00
" $LND_IMAGE " 2>>" $LOG " || true
2026-02-25 18:04:41 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "lnd"
2026-02-25 18:04:41 +00:00
2026-03-21 01:39:22 +00:00
# 5. Fedimint — depends on Bitcoin
2026-02-25 18:04:41 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint; then
log "Creating Fedimint..."
mkdir -p /var/lib/archipelago/fedimint
2026-04-02 20:28:53 +01:00
chmod 775 /var/lib/archipelago/fedimint # fedimint container runs as non-root
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name fedimint --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:8174/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-04-02 10:34:58 +01:00
--memory= $( mem_limit fedimint) --network archy-net --network-alias fedimint \
2026-03-12 22:19:04 +00:00
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
fix: harden container isolation in first-boot script (PENTEST-03)
Add --cap-drop ALL and --security-opt no-new-privileges:true to all
containers in first-boot-containers.sh that were missing it:
- Bitcoin Knots, LND, Fedimint, Fedimint Gateway (+ CHOWN/SETUID/SETGID)
- BTCPay Server, Home Assistant (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Nextcloud (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Grafana, Uptime Kuma, PhotoPrism, Ollama, Vaultwarden, FileBrowser
(zero extra caps + --read-only + tmpfs for /tmp and /run)
- Jellyfin (zero extra caps)
Tailscale retains --privileged (required for TUN/iptables/routing).
SearXNG, OnlyOffice, Nginx Proxy Manager, Portainer already hardened.
The Rust RPC layer already applies equivalent hardening for all UI
installs; this brings the ISO first-boot path to parity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:40:04 +00:00
--security-opt no-new-privileges:true \
2026-02-25 18:04:41 +00:00
-p 8173:8173 -p 8174:8174 -p 8175:8175 \
-v /var/lib/archipelago/fedimint:/data \
2026-04-02 16:15:04 +01:00
-e FM_DATA_DIR = /data -e " FM_BITCOIND_USERNAME= $BTC_RPC_USER " -e " FM_BITCOIND_PASSWORD= $BTC_RPC_PASS " \
2026-02-25 18:04:41 +00:00
-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 \
2026-04-28 15:00:58 -04:00
-e FM_P2P_URL = fedimint://" $HOST_MDNS " :8173 -e FM_API_URL = ws://" $HOST_MDNS " :8174 \
2026-04-02 16:15:04 +01:00
-e " FM_BITCOIND_URL=http:// $BTC_HOST : $BTC_PORT " \
2026-03-26 14:06:21 +00:00
" $FEDIMINT_IMAGE " 2>>" $LOG " || true
2026-02-25 18:04:41 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "fedimint"
2026-02-25 18:04:41 +00:00
2026-03-09 07:43:12 +00:00
# 5b. Fedimint Gateway (companion to fedimint)
# Auto-detect LND: if running with credentials, use lnd mode; otherwise use ldk (built-in)
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint-gateway; then
log "Creating Fedimint Gateway..."
mkdir -p /var/lib/archipelago/fedimint-gateway
LND_CERT = /var/lib/archipelago/lnd/tls.cert
LND_MACAROON = /var/lib/archipelago/lnd/data/chain/bitcoin/mainnet/admin.macaroon
if $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q '^lnd$' && [ -f " $LND_CERT " ] && [ -f " $LND_MACAROON " ] ; then
log " LND detected — using lnd mode"
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name fedimint-gateway --restart unless-stopped \
2026-04-11 22:49:01 -04:00
--health-cmd= "curl -sf http://localhost:8176/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-04-02 10:34:58 +01:00
--memory= $( mem_limit fedimint-gateway) --network archy-net --network-alias fedimint-gateway \
2026-03-12 22:19:04 +00:00
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
fix: harden container isolation in first-boot script (PENTEST-03)
Add --cap-drop ALL and --security-opt no-new-privileges:true to all
containers in first-boot-containers.sh that were missing it:
- Bitcoin Knots, LND, Fedimint, Fedimint Gateway (+ CHOWN/SETUID/SETGID)
- BTCPay Server, Home Assistant (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Nextcloud (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Grafana, Uptime Kuma, PhotoPrism, Ollama, Vaultwarden, FileBrowser
(zero extra caps + --read-only + tmpfs for /tmp and /run)
- Jellyfin (zero extra caps)
Tailscale retains --privileged (required for TUN/iptables/routing).
SearXNG, OnlyOffice, Nginx Proxy Manager, Portainer already hardened.
The Rust RPC layer already applies equivalent hardening for all UI
installs; this brings the ISO first-boot path to parity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:40:04 +00:00
--security-opt no-new-privileges:true \
2026-03-09 07:43:12 +00:00
-p 8176:8176 \
-v /var/lib/archipelago/fedimint-gateway:/data \
-v " $LND_CERT " :/lnd/tls.cert:ro \
-v " $LND_MACAROON " :/lnd/admin.macaroon:ro \
2026-03-26 14:06:21 +00:00
" $FEDIMINT_GATEWAY_IMAGE " \
2026-03-09 07:43:12 +00:00
gatewayd --data-dir /data --listen 0.0.0.0:8176 \
feat: Phase 1 — per-installation credential generation, eliminate hardcoded passwords
Generate unique random passwords at first boot for Bitcoin RPC, all database
services (mempool, btcpay, immich, penpot, mysql-root), and Fedimint gateway.
Credentials stored in /var/lib/archipelago/secrets/ with 600 permissions.
Scripts: first-boot-containers.sh, deploy-to-target.sh, deploy-bitcoin-knots.sh,
container-doctor.sh all read from secrets files instead of hardcoded values.
Rust backend: new bitcoin_rpc module reads password from secrets file, env var,
or dev fallback. All .basic_auth() calls and container config strings now use
the shared credential reader instead of hardcoded "archipelago123".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:39:52 +00:00
--bcrypt-password-hash " $FEDI_HASH " \
2026-04-02 16:15:04 +01:00
--network bitcoin --bitcoind-url " http:// $BTC_HOST : $BTC_PORT " \
--bitcoind-username " $BTC_RPC_USER " --bitcoind-password " $BTC_RPC_PASS " \
2026-04-28 15:00:58 -04:00
lnd --lnd-rpc-host lnd:10009 --lnd-tls-cert /lnd/tls.cert --lnd-macaroon /lnd/admin.macaroon 2>>" $LOG " || true
2026-03-09 07:43:12 +00:00
else
log " No LND found — using ldk (built-in Lightning)"
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name fedimint-gateway --restart unless-stopped \
2026-04-11 22:49:01 -04:00
--health-cmd= "curl -sf http://localhost:8176/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-04-02 10:34:58 +01:00
--memory= $( mem_limit fedimint-gateway) --network archy-net --network-alias fedimint-gateway \
2026-03-12 22:19:04 +00:00
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
fix: harden container isolation in first-boot script (PENTEST-03)
Add --cap-drop ALL and --security-opt no-new-privileges:true to all
containers in first-boot-containers.sh that were missing it:
- Bitcoin Knots, LND, Fedimint, Fedimint Gateway (+ CHOWN/SETUID/SETGID)
- BTCPay Server, Home Assistant (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Nextcloud (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Grafana, Uptime Kuma, PhotoPrism, Ollama, Vaultwarden, FileBrowser
(zero extra caps + --read-only + tmpfs for /tmp and /run)
- Jellyfin (zero extra caps)
Tailscale retains --privileged (required for TUN/iptables/routing).
SearXNG, OnlyOffice, Nginx Proxy Manager, Portainer already hardened.
The Rust RPC layer already applies equivalent hardening for all UI
installs; this brings the ISO first-boot path to parity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:40:04 +00:00
--security-opt no-new-privileges:true \
2026-03-09 07:43:12 +00:00
-p 8176:8176 -p 9737:9737 \
-v /var/lib/archipelago/fedimint-gateway:/data \
2026-03-26 14:06:21 +00:00
" $FEDIMINT_GATEWAY_IMAGE " \
2026-03-09 07:43:12 +00:00
gatewayd --data-dir /data --listen 0.0.0.0:8176 \
feat: Phase 1 — per-installation credential generation, eliminate hardcoded passwords
Generate unique random passwords at first boot for Bitcoin RPC, all database
services (mempool, btcpay, immich, penpot, mysql-root), and Fedimint gateway.
Credentials stored in /var/lib/archipelago/secrets/ with 600 permissions.
Scripts: first-boot-containers.sh, deploy-to-target.sh, deploy-bitcoin-knots.sh,
container-doctor.sh all read from secrets files instead of hardcoded values.
Rust backend: new bitcoin_rpc module reads password from secrets file, env var,
or dev fallback. All .basic_auth() calls and container config strings now use
the shared credential reader instead of hardcoded "archipelago123".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:39:52 +00:00
--bcrypt-password-hash " $FEDI_HASH " \
2026-04-02 16:15:04 +01:00
--network bitcoin --bitcoind-url " http:// $BTC_HOST : $BTC_PORT " \
--bitcoind-username " $BTC_RPC_USER " --bitcoind-password " $BTC_RPC_PASS " \
2026-03-09 07:43:12 +00:00
ldk --ldk-lightning-port 9737 --ldk-alias archipelago-gateway 2>>" $LOG " || true
fi
fi
2026-03-21 01:39:22 +00:00
track_container "fedimint-gateway"
2026-03-09 07:43:12 +00:00
2026-03-31 18:31:00 +01:00
# (Bitcoin-dependent containers created above regardless of BITCOIN_READY)
2026-03-21 01:39:22 +00:00
# ── Tier 3: Applications (independent — always attempt) ───────────────────
2026-03-14 03:06:20 +00:00
log "=== Tier 3: Applications ==="
sleep 5 # Let core services stabilize
2026-02-25 18:04:41 +00:00
# 6. Home Assistant
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'homeassistant|home-assistant' ; then
log "Creating Home Assistant..."
mkdir -p /var/lib/archipelago/home-assistant
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name homeassistant --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:8123/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-03-21 01:11:05 +00:00
--memory= $( mem_limit homeassistant) \
fix: harden container isolation in first-boot script (PENTEST-03)
Add --cap-drop ALL and --security-opt no-new-privileges:true to all
containers in first-boot-containers.sh that were missing it:
- Bitcoin Knots, LND, Fedimint, Fedimint Gateway (+ CHOWN/SETUID/SETGID)
- BTCPay Server, Home Assistant (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Nextcloud (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Grafana, Uptime Kuma, PhotoPrism, Ollama, Vaultwarden, FileBrowser
(zero extra caps + --read-only + tmpfs for /tmp and /run)
- Jellyfin (zero extra caps)
Tailscale retains --privileged (required for TUN/iptables/routing).
SearXNG, OnlyOffice, Nginx Proxy Manager, Portainer already hardened.
The Rust RPC layer already applies equivalent hardening for all UI
installs; this brings the ISO first-boot path to parity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:40:04 +00:00
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
2026-02-25 18:04:41 +00:00
-p 8123:8123 -v /var/lib/archipelago/home-assistant:/config \
-e TZ = UTC \
2026-03-26 14:06:21 +00:00
" $HOMEASSISTANT_IMAGE " 2>>" $LOG " || true
2026-02-25 18:04:41 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "homeassistant"
2026-02-25 18:04:41 +00:00
# 7. Single-container apps (Grafana, Uptime Kuma, Jellyfin, PhotoPrism, Ollama, Vaultwarden)
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q grafana; then
log "Creating Grafana..."
mkdir -p /var/lib/archipelago/grafana
2026-05-06 09:23:57 -04:00
podman unshare chown -R 472:472 /var/lib/archipelago/grafana 2>/dev/null || true
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name grafana --restart unless-stopped \
2026-05-06 09:23:57 -04:00
--health-cmd= "test -w /var/lib/grafana && test -w /var/lib/grafana/grafana.db && curl -sf http://localhost:3000/api/health || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-03-21 01:11:05 +00:00
--memory= $( mem_limit grafana) \
fix: harden container isolation in first-boot script (PENTEST-03)
Add --cap-drop ALL and --security-opt no-new-privileges:true to all
containers in first-boot-containers.sh that were missing it:
- Bitcoin Knots, LND, Fedimint, Fedimint Gateway (+ CHOWN/SETUID/SETGID)
- BTCPay Server, Home Assistant (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Nextcloud (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Grafana, Uptime Kuma, PhotoPrism, Ollama, Vaultwarden, FileBrowser
(zero extra caps + --read-only + tmpfs for /tmp and /run)
- Jellyfin (zero extra caps)
Tailscale retains --privileged (required for TUN/iptables/routing).
SearXNG, OnlyOffice, Nginx Proxy Manager, Portainer already hardened.
The Rust RPC layer already applies equivalent hardening for all UI
installs; this brings the ISO first-boot path to parity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:40:04 +00:00
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
--read-only --tmpfs /tmp:rw,noexec,nosuid,size= 256m --tmpfs /run:rw,noexec,nosuid,size= 64m \
2026-02-25 18:04:41 +00:00
-p 3000:3000 -v /var/lib/archipelago/grafana:/var/lib/grafana \
-e GF_PATHS_DATA = /var/lib/grafana -e GF_USERS_ALLOW_SIGN_UP = false \
2026-03-26 14:06:21 +00:00
" $GRAFANA_IMAGE " 2>>" $LOG " || true
2026-02-25 18:04:41 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "grafana"
2026-02-25 18:04:41 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q uptime-kuma; then
log "Creating Uptime Kuma..."
mkdir -p /var/lib/archipelago/uptime-kuma
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name uptime-kuma --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:3001/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-03-21 01:11:05 +00:00
--memory= $( mem_limit uptime-kuma) \
2026-03-12 22:19:04 +00:00
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
2026-02-25 18:04:41 +00:00
-p 3001:3001 -v /var/lib/archipelago/uptime-kuma:/app/data \
-e TZ = UTC \
2026-03-26 14:06:21 +00:00
" $UPTIME_KUMA_IMAGE " 2>>" $LOG " || true
2026-02-25 18:04:41 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "uptime-kuma"
2026-02-25 18:04:41 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q jellyfin; then
log "Creating Jellyfin..."
mkdir -p /var/lib/archipelago/jellyfin/config /var/lib/archipelago/jellyfin/cache
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name jellyfin --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:8096/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-03-21 01:11:05 +00:00
--memory= $( mem_limit jellyfin) \
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
--tmpfs /tmp:rw,exec,size= 256m \
2026-02-25 18:04:41 +00:00
-p 8096:8096 \
-v /var/lib/archipelago/jellyfin/config:/config \
-v /var/lib/archipelago/jellyfin/cache:/cache \
2026-03-26 14:06:21 +00:00
" $JELLYFIN_IMAGE " 2>>" $LOG " || true
2026-02-25 18:04:41 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "jellyfin"
2026-02-25 18:04:41 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q photoprism; then
log "Creating PhotoPrism..."
mkdir -p /var/lib/archipelago/photoprism
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name photoprism --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:2342/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-03-21 01:11:05 +00:00
--memory= $( mem_limit photoprism) \
2026-03-12 22:19:04 +00:00
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
2026-02-25 18:04:41 +00:00
-p 2342:2342 -v /var/lib/archipelago/photoprism:/photoprism/storage \
-e PHOTOPRISM_ADMIN_PASSWORD = archipelago -e PHOTOPRISM_DEFAULT_LOCALE = en \
2026-03-26 14:06:21 +00:00
" ${ PHOTOPRISM_IMAGE } " 2>>" $LOG " || true
2026-02-25 18:04:41 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "photoprism"
2026-02-25 18:04:41 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q ollama; then
log "Creating Ollama..."
mkdir -p /var/lib/archipelago/ollama
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name ollama --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:11434/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-03-21 01:11:05 +00:00
--memory= $( mem_limit ollama) \
fix: harden container isolation in first-boot script (PENTEST-03)
Add --cap-drop ALL and --security-opt no-new-privileges:true to all
containers in first-boot-containers.sh that were missing it:
- Bitcoin Knots, LND, Fedimint, Fedimint Gateway (+ CHOWN/SETUID/SETGID)
- BTCPay Server, Home Assistant (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Nextcloud (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Grafana, Uptime Kuma, PhotoPrism, Ollama, Vaultwarden, FileBrowser
(zero extra caps + --read-only + tmpfs for /tmp and /run)
- Jellyfin (zero extra caps)
Tailscale retains --privileged (required for TUN/iptables/routing).
SearXNG, OnlyOffice, Nginx Proxy Manager, Portainer already hardened.
The Rust RPC layer already applies equivalent hardening for all UI
installs; this brings the ISO first-boot path to parity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:40:04 +00:00
--cap-drop ALL --security-opt no-new-privileges:true \
--read-only --tmpfs /tmp:rw,noexec,nosuid,size= 256m --tmpfs /run:rw,noexec,nosuid,size= 64m \
2026-02-25 18:04:41 +00:00
-p 11434:11434 -v /var/lib/archipelago/ollama:/root/.ollama \
2026-03-26 14:06:21 +00:00
" ${ OLLAMA_IMAGE } " 2>>" $LOG " || true
2026-02-25 18:04:41 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "ollama"
2026-02-25 18:04:41 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q vaultwarden; then
log "Creating Vaultwarden..."
mkdir -p /var/lib/archipelago/vaultwarden
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name vaultwarden --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:80/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-03-21 01:11:05 +00:00
--memory= $( mem_limit vaultwarden) \
2026-03-12 22:19:04 +00:00
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add NET_BIND_SERVICE \
--security-opt no-new-privileges:true \
2026-02-25 18:04:41 +00:00
-p 8082:80 -v /var/lib/archipelago/vaultwarden:/data \
2026-03-26 14:06:21 +00:00
" $VAULTWARDEN_IMAGE " 2>>" $LOG " || true
2026-02-25 18:04:41 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "vaultwarden"
2026-02-25 18:04:41 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q nextcloud; then
log "Creating Nextcloud..."
mkdir -p /var/lib/archipelago/nextcloud
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name nextcloud --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:80/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-03-21 01:11:05 +00:00
--memory= $( mem_limit nextcloud) \
fix: harden container isolation in first-boot script (PENTEST-03)
Add --cap-drop ALL and --security-opt no-new-privileges:true to all
containers in first-boot-containers.sh that were missing it:
- Bitcoin Knots, LND, Fedimint, Fedimint Gateway (+ CHOWN/SETUID/SETGID)
- BTCPay Server, Home Assistant (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Nextcloud (+ CHOWN/SETUID/SETGID/DAC_OVERRIDE)
- Grafana, Uptime Kuma, PhotoPrism, Ollama, Vaultwarden, FileBrowser
(zero extra caps + --read-only + tmpfs for /tmp and /run)
- Jellyfin (zero extra caps)
Tailscale retains --privileged (required for TUN/iptables/routing).
SearXNG, OnlyOffice, Nginx Proxy Manager, Portainer already hardened.
The Rust RPC layer already applies equivalent hardening for all UI
installs; this brings the ISO first-boot path to parity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:40:04 +00:00
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
2026-02-25 18:04:41 +00:00
-p 8085:80 -v /var/lib/archipelago/nextcloud:/var/www/html \
2026-03-26 14:06:21 +00:00
" $NEXTCLOUD_IMAGE " 2>>" $LOG " || true
2026-02-25 18:04:41 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "nextcloud"
2026-03-09 00:18:28 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q searxng; then
log "Creating SearXNG..."
2026-03-31 18:31:00 +01:00
# SearXNG requires settings.yml or it exits immediately
SEARXNG_CONF = "/var/lib/archipelago/searxng"
if [ ! -f " $SEARXNG_CONF /settings.yml " ] ; then
mkdir -p " $SEARXNG_CONF "
SEARX_SECRET = $( openssl rand -hex 32)
cat > " $SEARXNG_CONF /settings.yml " <<SEARXCFG
use_default_settings: true
general:
instance_name: Archipelago Search
server:
secret_key: " $SEARX_SECRET "
bind_address: "0.0.0.0"
port: 8080
limiter: false
ui:
default_theme: simple
SEARXCFG
chown -R 100000:100000 " $SEARXNG_CONF " 2>/dev/null
log " Created SearXNG settings.yml"
fi
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name searxng --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:8080/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-03-21 01:11:05 +00:00
--memory= $( mem_limit searxng) \
2026-03-09 00:18:28 +00:00
--cap-drop ALL --security-opt no-new-privileges:true \
--read-only --tmpfs /tmp:rw,noexec,nosuid,size= 256m --tmpfs /run:rw,noexec,nosuid,size= 64m \
-p 8888:8080 \
2026-03-31 18:31:00 +01:00
-v /var/lib/archipelago/searxng:/etc/searxng \
2026-03-26 14:06:21 +00:00
" ${ SEARXNG_IMAGE } " 2>>" $LOG " || true
2026-03-09 00:18:28 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "searxng"
2026-04-02 16:15:04 +01:00
# OnlyOffice removed — incompatible with rootless Podman (internal postgres/rabbitmq)
# CryptPad is the replacement (single Node.js process, e2e encrypted)
2026-03-09 00:18:28 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q filebrowser; then
2026-04-11 22:49:01 -04:00
log "Creating File Browser (noauth — behind Archipelago login)..."
fix: container install flow, filebrowser auth, AppCard enrichment
- Fix .198-style fresh installs: systemd service ExecStartPre creates
/run/user/1000, enable podman.socket, chmod 644 /etc/hosts
- Filebrowser: add /data volume for database (fixes read-only crash),
secure auth with random password via backend RPC (no more admin/admin)
- AppCard: enrich installing state with marketplace metadata (icon,
title, description, tier badge, author, version)
- Registry: btcpayserver 1.13.5 → 1.13.7, images mirrored
- ReadWritePaths: add home container paths for rootless podman
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 13:32:54 +00:00
mkdir -p /var/lib/archipelago/filebrowser /var/lib/archipelago/filebrowser-data
2026-04-02 18:33:28 +01:00
mkdir -p /var/lib/archipelago/filebrowser/{ Documents,Photos,Music,Downloads,Builds}
2026-04-11 22:49:01 -04:00
# Config with noauth + database on persistent volume (survives container recreation)
cat > /var/lib/archipelago/filebrowser-data/.filebrowser.json << 'FBEOF '
{ "port" :80,"baseURL" :"" ,"address" :"0.0.0.0" ,"database" :"/data/filebrowser.db" ,"root" :"/srv" ,"log" :"stdout" }
FBEOF
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name filebrowser --restart unless-stopped \
2026-04-11 22:49:01 -04:00
--health-cmd= "wget -q --spider http://localhost:80/health || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-03-21 01:11:05 +00:00
--memory= $( mem_limit filebrowser) \
fix: container install flow, filebrowser auth, AppCard enrichment
- Fix .198-style fresh installs: systemd service ExecStartPre creates
/run/user/1000, enable podman.socket, chmod 644 /etc/hosts
- Filebrowser: add /data volume for database (fixes read-only crash),
secure auth with random password via backend RPC (no more admin/admin)
- AppCard: enrich installing state with marketplace metadata (icon,
title, description, tier badge, author, version)
- Registry: btcpayserver 1.13.5 → 1.13.7, images mirrored
- ReadWritePaths: add home container paths for rootless podman
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 13:32:54 +00:00
--cap-drop ALL --security-opt no-new-privileges:true \
2026-04-11 22:49:01 -04:00
--tmpfs= /tmp:rw,noexec,nosuid,size= 256m --tmpfs= /run:rw,noexec,nosuid,size= 64m \
fix: container install flow, filebrowser auth, AppCard enrichment
- Fix .198-style fresh installs: systemd service ExecStartPre creates
/run/user/1000, enable podman.socket, chmod 644 /etc/hosts
- Filebrowser: add /data volume for database (fixes read-only crash),
secure auth with random password via backend RPC (no more admin/admin)
- AppCard: enrich installing state with marketplace metadata (icon,
title, description, tier badge, author, version)
- Registry: btcpayserver 1.13.5 → 1.13.7, images mirrored
- ReadWritePaths: add home container paths for rootless podman
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 13:32:54 +00:00
-p 8083:80 \
-v /var/lib/archipelago/filebrowser:/srv \
-v /var/lib/archipelago/filebrowser-data:/data \
" $FILEBROWSER_IMAGE " \
2026-04-11 22:49:01 -04:00
--config /data/.filebrowser.json 2>>" $LOG " || true
# Set noauth after first start (initializes database on volume)
sleep 3
$DOCKER exec filebrowser /filebrowser config set --auth.method= noauth --database /data/filebrowser.db 2>>" $LOG " || true
$DOCKER exec filebrowser /filebrowser users add admin admin --perm.admin --database /data/filebrowser.db 2>>" $LOG " || true
$DOCKER restart filebrowser 2>>" $LOG " || true
2026-03-09 00:18:28 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "filebrowser"
2026-03-09 00:18:28 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q nginx-proxy-manager; then
log "Creating Nginx Proxy Manager..."
mkdir -p /var/lib/archipelago/nginx-proxy-manager/data /var/lib/archipelago/nginx-proxy-manager/letsencrypt
2026-05-19 09:26:43 -04:00
mkdir -p /var/lib/archipelago/nginx-proxy-manager/data/letsencrypt-acme-challenge/.well-known/acme-challenge
chown -R 1000:1000 /var/lib/archipelago/nginx-proxy-manager 2>/dev/null || true
2026-05-17 17:30:04 -04:00
NPM_ADMIN_PORT = $( alloc_port nginx-proxy-manager 8081)
NPM_HTTP_PORT = $( alloc_port nginx-proxy-manager-http 8084)
NPM_HTTPS_PORT = $( alloc_port nginx-proxy-manager-https 8444)
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name nginx-proxy-manager --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:81/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-03-21 01:11:05 +00:00
--memory= $( mem_limit nginx-proxy-manager) \
2026-05-17 17:30:04 -04:00
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE --cap-add NET_BIND_SERVICE \
2026-03-09 00:18:28 +00:00
--security-opt no-new-privileges:true \
2026-05-17 17:30:04 -04:00
-p ${ NPM_ADMIN_PORT } :81 -p ${ NPM_HTTP_PORT } :80 -p ${ NPM_HTTPS_PORT } :443 \
2026-03-09 00:18:28 +00:00
-v /var/lib/archipelago/nginx-proxy-manager/data:/data \
-v /var/lib/archipelago/nginx-proxy-manager/letsencrypt:/etc/letsencrypt \
2026-03-26 14:06:21 +00:00
" ${ NPM_IMAGE } " 2>>" $LOG " || true
2026-03-09 00:18:28 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "nginx-proxy-manager"
2026-03-09 00:18:28 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q portainer; then
log "Creating Portainer..."
2026-06-11 00:24:32 -04:00
mkdir -p /var/lib/archipelago/portainer/compose
chown -R archipelago:archipelago /var/lib/archipelago/portainer 2>/dev/null || true
if [ ! -e /data ] ; then
ln -s /var/lib/archipelago/portainer /data 2>/dev/null || true
elif [ -d /data ] && [ ! -L /data ] && [ ! -e /data/compose ] ; then
ln -s /var/lib/archipelago/portainer/compose /data/compose 2>/dev/null || true
fi
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name portainer --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:9000/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-03-21 01:11:05 +00:00
--memory= $( mem_limit portainer) \
2026-03-09 00:18:28 +00:00
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-p 9000:9000 \
-v /var/lib/archipelago/portainer:/data \
2026-06-11 00:24:32 -04:00
-v /var/lib/archipelago/portainer/compose:/data/compose \
-v /run/user/$( id -u archipelago) /podman/podman.sock:/var/run/docker.sock \
2026-03-26 14:06:21 +00:00
" $PORTAINER_IMAGE " 2>>" $LOG " || true
2026-03-09 00:18:28 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "portainer"
2026-03-09 00:18:28 +00:00
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q tailscale; then
log "Creating Tailscale..."
mkdir -p /var/lib/archipelago/tailscale
2026-03-18 00:42:29 +00:00
# Tailscale needs NET_ADMIN + NET_RAW + TUN device (no --privileged)
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name tailscale --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "tailscale status || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-03-21 01:11:05 +00:00
--memory= $( mem_limit tailscale) \
2026-03-18 00:42:29 +00:00
--network host \
--cap-drop= ALL \
--cap-add= NET_ADMIN \
--cap-add= NET_RAW \
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
--security-opt no-new-privileges:true \
2026-03-18 00:42:29 +00:00
--device= /dev/net/tun:/dev/net/tun \
--read-only \
--tmpfs /tmp \
2026-03-09 00:18:28 +00:00
-v /var/lib/archipelago/tailscale:/var/lib/tailscale \
-e TS_STATE_DIR = /var/lib/tailscale \
2026-03-26 14:06:21 +00:00
" $TAILSCALE_IMAGE " \
2026-06-11 00:24:32 -04:00
sh -c 'tailscaled --tun=userspace-networking & for i in $(seq 1 30); do [ -S /var/run/tailscale/tailscaled.sock ] && break; sleep 1; done; tailscale web --listen 0.0.0.0:8240 & wait' 2>>" $LOG " || true
2026-03-09 00:18:28 +00:00
fi
2026-03-21 01:39:22 +00:00
track_container "tailscale"
2026-02-25 18:04:41 +00:00
# Immich stack (postgres + redis + server - ML optional)
# 8. Nostr relays (optional - only if images were loaded; deploy does not create these on first boot)
# nostr-rs-relay and strfry are in ISO image bundle; create if image exists
if $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q 'nostr-rs-relay' ; then
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q nostr-rs-relay; then
log "Creating nostr-rs-relay..."
mkdir -p /var/lib/archipelago/nostr-rs-relay
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name nostr-rs-relay --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:8080/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-03-21 01:11:05 +00:00
--memory= $( mem_limit nostr-rs-relay) \
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
--read-only --tmpfs /tmp:rw,noexec,nosuid,size= 32m \
2026-02-25 18:04:41 +00:00
-p 7047:7047 -v /var/lib/archipelago/nostr-rs-relay:/data \
2026-03-26 14:06:21 +00:00
" ${ NOSTR_RS_RELAY_IMAGE } " 2>>" $LOG " || true
2026-02-25 18:04:41 +00:00
fi
fi
if $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q 'strfry' ; then
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q strfry; then
log "Creating strfry..."
mkdir -p /var/lib/archipelago/strfry
2026-03-21 01:11:05 +00:00
$DOCKER run -d --name strfry --restart unless-stopped \
2026-03-31 02:42:44 +01:00
--health-cmd= "curl -sf http://localhost:7777/ || exit 1" --health-interval= 120s --health-timeout= 5s --health-retries= 3 \
2026-03-21 01:11:05 +00:00
--memory= $( mem_limit strfry) \
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
2026-02-25 18:04:41 +00:00
-p 7777:7777 -v /var/lib/archipelago/strfry:/data \
2026-03-26 14:06:21 +00:00
" ${ STRFRY_IMAGE } " 2>>" $LOG " || true
2026-02-25 18:04:41 +00:00
fi
fi
2026-03-08 02:27:58 +00:00
# 9. Custom UI containers (bitcoin-ui, lnd-ui)
# These are built from Dockerfiles in /opt/archipelago/docker/ or loaded from pre-built images.
2026-03-31 03:48:22 +01:00
# Inject Bitcoin RPC auth into bitcoin-ui nginx.conf BEFORE building
RPC_USER = "archipelago"
RPC_PASS_FILE = "/var/lib/archipelago/secrets/bitcoin-rpc-password"
if [ -f " $RPC_PASS_FILE " ] ; then
RPC_PASS = $( cat " $RPC_PASS_FILE " )
AUTH_B64 = $( echo -n " ${ RPC_USER } : ${ RPC_PASS } " | base64)
for ui_dir in /opt/archipelago/docker/bitcoin-ui /home/archipelago/archy/docker/bitcoin-ui; do
if [ -f " $ui_dir /nginx.conf " ] ; then
sed -i " s|__BITCOIN_RPC_AUTH__| ${ AUTH_B64 } |g " " $ui_dir /nginx.conf "
log " Injected Bitcoin RPC auth into $ui_dir /nginx.conf "
fi
done
fi
2026-04-02 18:20:52 +01:00
for ui in bitcoin-ui lnd-ui electrs-ui; do
2026-03-08 02:27:58 +00:00
if $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q " $ui " ; then
continue
fi
case $ui in
2026-04-01 22:18:00 +01:00
# UI containers use --network host so they can proxy to localhost services
2026-05-06 09:23:57 -04:00
# Internal nginx ports: bitcoin-ui=8334, electrs-ui=50002, lnd-ui=80 (host 18083)
2026-04-02 18:20:52 +01:00
bitcoin-ui) PORT_ARG = "" ; NET_ARG = "--network host" ; REG_IMG = " ${ BITCOIN_UI_IMAGE } " ; ;
2026-05-06 09:23:57 -04:00
lnd-ui) PORT_ARG = "-p 18083:80" ; NET_ARG = "" ; REG_IMG = " ${ LND_UI_IMAGE } " ; ;
2026-04-02 18:20:52 +01:00
electrs-ui) PORT_ARG = "" ; NET_ARG = "--network host" ; REG_IMG = " ${ ELECTRS_UI_IMAGE } " ; ;
2026-03-08 02:27:58 +00:00
esac
CONTAINER_NAME = " archy- $ui "
2026-04-02 18:20:52 +01:00
UI_CAPS = "--user 0:0 --cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE"
# Try registry image first, then local image, then build from source
if [ -n " $REG_IMG " ] && $DOCKER pull --tls-verify= false " $REG_IMG " 2>>" $LOG " ; then
log " Starting $ui from registry ( $REG_IMG )... "
$DOCKER run -d --name " $CONTAINER_NAME " $PORT_ARG --restart unless-stopped --memory= $( mem_limit " $CONTAINER_NAME " ) $NET_ARG \
$UI_CAPS " $REG_IMG " 2>>" $LOG " || true
elif $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q " $ui " ; then
log " Starting $ui from local image... "
2026-03-08 02:27:58 +00:00
IMG = $( $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep " $ui " | head -1)
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
$DOCKER run -d --name " $CONTAINER_NAME " $PORT_ARG --restart unless-stopped --memory= $( mem_limit " $CONTAINER_NAME " ) $NET_ARG \
2026-04-02 18:20:52 +01:00
$UI_CAPS " $IMG " 2>>" $LOG " || true
2026-03-08 02:27:58 +00:00
elif [ -d " /opt/archipelago/docker/ $ui " ] ; then
2026-04-02 18:20:52 +01:00
log " Building $ui from source... "
2026-03-21 01:32:28 +00:00
if $DOCKER build -t " $ui :local " " /opt/archipelago/docker/ $ui " 2>>" $LOG " ; then
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
$DOCKER run -d --name " $CONTAINER_NAME " $PORT_ARG --restart unless-stopped --memory= $( mem_limit " $CONTAINER_NAME " ) $NET_ARG \
2026-04-02 18:20:52 +01:00
$UI_CAPS " $ui :local " 2>>" $LOG " || true
2026-03-16 12:58:35 +00:00
fi
else
log " $ui : no image or source found, skipping "
2026-03-08 02:27:58 +00:00
fi
done
2026-03-09 17:09:59 +00:00
# 10. Initialize backend data directories
# tor-config: backend stores tor service configs here (writable by archipelago user)
mkdir -p /var/lib/archipelago/tor-config
SERVICES_JSON = /var/lib/archipelago/tor-config/services.json
if [ ! -f " $SERVICES_JSON " ] ; then
cat > " $SERVICES_JSON " <<'SJS ON'
{ "services" :[
{ "name" :"archipelago" ,"local_port" :80,"enabled" :true} ,
2026-05-06 09:23:57 -04:00
{ "name" :"lnd" ,"local_port" :18083,"enabled" :true} ,
2026-03-09 17:09:59 +00:00
{ "name" :"btcpay" ,"local_port" :23000,"enabled" :true} ,
{ "name" :"mempool" ,"local_port" :4080,"enabled" :true} ,
{ "name" :"fedimint" ,"local_port" :8175,"enabled" :true}
] }
SJSON
log "Created initial tor-config/services.json"
fi
2026-03-19 19:19:13 +00:00
# identity: node Ed25519 keypair (DID) — MUST persist across deployments
mkdir -p /var/lib/archipelago/identity
# identities: backend identity manager stores user DIDs here
2026-03-09 17:09:59 +00:00
mkdir -p /var/lib/archipelago/identities
# Ensure archipelago user can write to these directories
2026-03-19 19:19:13 +00:00
chown -R 1000:1000 /var/lib/archipelago/tor-config /var/lib/archipelago/identity /var/lib/archipelago/identities 2>/dev/null || true
2026-03-09 17:09:59 +00:00
2026-03-21 01:39:22 +00:00
# 11. Run container doctor for any remaining issues
feat: v1.2.0-alpha — E2E encrypted mesh relay, steganography, relay status polling
Phase 5 mesh networking:
- E2E encrypted TX relay (X25519 + ChaCha20-Poly1305) — non-Archy nodes
relay encrypted blobs transparently via Meshcore native routing
- Steganographic encoding modes (WeatherStation, SensorNetwork) — traffic
looks like sensor data on the wire, 0xAA marker, configurable per-node
- Pre-flight Bitcoin Core health check on relay node — specific error codes
(bitcoin_unreachable, bitcoin_syncing, tx_rejected) instead of generic fails
- mesh.relay-status RPC endpoint — frontend polls for relay result every 3s
- On-Chain / Lightning tabs in Off-Grid Bitcoin panel
- Archy Peers vs Mesh Broadcast relay mode selector
- Mesh view fills viewport (no page scroll), internal panel scrolling
- Version bump to 1.2.0-alpha
Also includes: deploy hardening, container fixes, IndeedHub updates,
boot screen, dashboard improvements, MASTER_PLAN task tracking
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:56:37 +00:00
log "Running container doctor..."
SCRIPT_DIR = " $( cd " $( dirname " $0 " ) " && pwd ) "
if [ -x " $SCRIPT_DIR /container-doctor.sh " ] ; then
bash " $SCRIPT_DIR /container-doctor.sh " --local 2>& 1 | tee -a " $LOG "
elif [ -x "/opt/archipelago/scripts/container-doctor.sh" ] ; then
bash "/opt/archipelago/scripts/container-doctor.sh" --local 2>& 1 | tee -a " $LOG "
fi
2026-03-31 18:31:00 +01:00
# 11b. If any containers failed, run the reconciler to attempt recovery
2026-03-21 01:39:22 +00:00
FAILED = $(( TOTAL - SUCCESS))
2026-03-31 18:31:00 +01:00
if [ " $FAILED " -gt 0 ] ; then
log " Attempting to recover $FAILED failed container(s) via reconciler... "
RECONCILE_SCRIPT = ""
if [ -x " $SCRIPT_DIR /reconcile-containers.sh " ] ; then
RECONCILE_SCRIPT = " $SCRIPT_DIR /reconcile-containers.sh "
elif [ -x "/opt/archipelago/scripts/reconcile-containers.sh" ] ; then
RECONCILE_SCRIPT = "/opt/archipelago/scripts/reconcile-containers.sh"
fi
if [ -n " $RECONCILE_SCRIPT " ] ; then
runuser -u archipelago -- bash " $RECONCILE_SCRIPT " 2>& 1 | tee -a " $LOG "
# Recount after reconciliation
SUCCESS = 0
for name in $( $DOCKER ps --format '{{.Names}}' 2>/dev/null) ; do
SUCCESS = $(( SUCCESS + 1 ))
done
FAILED = $(( TOTAL - SUCCESS))
log " After reconciliation: $SUCCESS running, $FAILED still failed "
fi
fi
# 12. Final summary
2026-03-21 01:39:22 +00:00
log "============================================="
log " FIRST-BOOT CONTAINER SUMMARY"
log "============================================="
log " Total tracked: $TOTAL "
log " Running: $SUCCESS "
log " Failed: $FAILED "
if [ " $BITCOIN_READY " != "true" ] ; then
2026-03-31 18:31:00 +01:00
log " Bitcoin: NOT READY (dependent containers will auto-restart when ready)"
2026-03-21 01:39:22 +00:00
fi
if [ -n " $FAILED_LIST " ] ; then
log " Failed list: $FAILED_LIST "
fi
log "============================================="
2026-02-25 18:04:41 +00:00
log "First-boot container creation complete"