From 95fbb094b08f0001c548fed7851fd9214c384892 Mon Sep 17 00:00:00 2001 From: Dorian Date: Sat, 21 Mar 2026 01:39:22 +0000 Subject: [PATCH] fix: deploy locking, safe eval replacement, first-boot error handling, script hardening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - S4: Add Bitcoin readiness gate and container tracking with final summary - S5: Replace eval "$DB_PASSWORDS" with safe case-based variable parsing - S6: Add deploy locking with stale lock detection (30min timeout) - S7: Deploy rollback already implemented — verified existing mechanism - S8: Switch trust-archipelago-cert.sh to SSH key auth, sshpass as fallback - S9: Pipe MariaDB SQL via stdin to avoid password in ps output - S17: Add disk space pre-flight check (abort if >85% full) Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/deploy-to-target.sh | 53 ++++++++++++++++- scripts/first-boot-containers.sh | 98 +++++++++++++++++++++++++------ scripts/trust-archipelago-cert.sh | 15 +++-- 3 files changed, 142 insertions(+), 24 deletions(-) diff --git a/scripts/deploy-to-target.sh b/scripts/deploy-to-target.sh index e3c03f56..90cad772 100755 --- a/scripts/deploy-to-target.sh +++ b/scripts/deploy-to-target.sh @@ -75,6 +75,36 @@ if [ -n "$TAILSCALE_NODE" ]; then exec "$SCRIPT_DIR/deploy-tailscale.sh" "$TAILSCALE_NODE" fi +# Deploy locking — prevent concurrent deploys to the same target +TARGET_IP_FOR_LOCK="$(echo "$TARGET_HOST" | cut -d@ -f2)" +LOCK_DIR="/tmp/archipelago-deploy-${TARGET_IP_FOR_LOCK}.lock" +# Check for stale lock (older than 30 minutes) +if [ -d "$LOCK_DIR" ]; then + LOCK_STAMP="$LOCK_DIR/pid" + if [ -f "$LOCK_STAMP" ]; then + # macOS uses stat -f %m, Linux uses stat -c %Y + if stat -c %Y "$LOCK_STAMP" >/dev/null 2>&1; then + LOCK_MTIME=$(stat -c %Y "$LOCK_STAMP") + else + LOCK_MTIME=$(stat -f %m "$LOCK_STAMP") + fi + LOCK_AGE=$(( $(date +%s) - ${LOCK_MTIME:-0} )) + if [ "$LOCK_AGE" -gt 1800 ]; then + echo "$(timestamp) WARNING: Removing stale lock (${LOCK_AGE}s old)" + rm -rf "$LOCK_DIR" + fi + fi +fi +# mkdir is atomic — fails if directory already exists +if ! mkdir "$LOCK_DIR" 2>/dev/null; then + echo "ERROR: Deploy already in progress for $TARGET_HOST (lock: $LOCK_DIR)" + exit 1 +fi +echo $$ > "$LOCK_DIR/pid" +# Clean up lock on exit (normal, error, or signal) +cleanup_lock() { rm -rf "$LOCK_DIR"; } +trap cleanup_lock EXIT + # Dry run mode: show what would be deployed without executing if [[ "$DRY_RUN" == "true" ]]; then echo "═══ DRY RUN MODE — no changes will be made ═══" @@ -168,6 +198,13 @@ if ! ssh $SSH_OPTS -o ConnectTimeout=5 "$TARGET_HOST" "echo ok" >/dev/null 2>&1; fi echo " Connected." +# Disk space pre-flight — abort if target is dangerously full +DISK_PCT=$(ssh $SSH_OPTS $TARGET_HOST "df / | tail -1 | awk '{print \$(NF-1)}' | tr -d '%'" 2>/dev/null) +if [ -n "$DISK_PCT" ] && [ "$DISK_PCT" -gt 85 ] 2>/dev/null; then + echo "ERROR: Target disk at ${DISK_PCT}% — need <85% for safe deploy. Free space and retry." + exit 1 +fi + # Install prerequisites if missing (rsync for code sync, python3 for Claude API proxy) progress "Checking prerequisites" ssh $SSH_OPTS "$TARGET_HOST" ' @@ -940,7 +977,21 @@ MANIFEST_EOF echo "FEDI_HASH=$(sudo cat "$SECRETS_DIR/fedimint-gateway-hash")" fi ' 2>/dev/null) - eval "$DB_PASSWORDS" + # Safe variable parsing — never eval untrusted SSH output + while IFS='=' read -r key value; do + # Skip empty lines + [ -z "$key" ] && continue + # Only allow expected variable names + case "$key" in + MEMPOOL_DB_PASS) MEMPOOL_DB_PASS="$value" ;; + BTCPAY_DB_PASS) BTCPAY_DB_PASS="$value" ;; + IMMICH_DB_PASS) IMMICH_DB_PASS="$value" ;; + PENPOT_DB_PASS) PENPOT_DB_PASS="$value" ;; + MYSQL_ROOT_PASS) MYSQL_ROOT_PASS="$value" ;; + FEDI_HASH) FEDI_HASH="$value" ;; + *) echo " WARNING: Ignoring unexpected variable from server: $key" ;; + esac + done <<< "$DB_PASSWORDS" # Fallback if hash not available if [ -z "${FEDI_HASH:-}" ]; then FEDI_HASH='$2y$10$t9YjjxkiktrlYvjajB/zgOMDnSNVg4HqrbDqh47u7Jf42whNdxNqC' diff --git a/scripts/first-boot-containers.sh b/scripts/first-boot-containers.sh index 416687fe..663434bb 100644 --- a/scripts/first-boot-containers.sh +++ b/scripts/first-boot-containers.sh @@ -115,6 +115,24 @@ else fi log "Fedimint gateway password stored in $SECRETS_DIR/fedimint-gateway-password" +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 +} + log "First-boot container creation starting (host=$TARGET_IP)" # Create swap file if not present (50% of RAM, min 2GB, max 8GB) @@ -269,7 +287,14 @@ else log "Bitcoin Knots already running" fi # Wait for Bitcoin Knots RPC to be responsive -wait_for_container "Bitcoin Knots RPC" "$DOCKER exec bitcoin-knots bitcoin-cli -rpcuser=$BITCOIN_RPC_USER -rpcpassword=$BITCOIN_RPC_PASS getblockchaininfo" 60 +if wait_for_container "Bitcoin Knots RPC" "$DOCKER exec bitcoin-knots bitcoin-cli -rpcuser=$BITCOIN_RPC_USER -rpcpassword=$BITCOIN_RPC_PASS getblockchaininfo" 60; then + BITCOIN_READY=true + log "Bitcoin Knots is ready — dependent containers will proceed" +else + BITCOIN_READY=false + log "WARNING: Bitcoin Knots NOT ready — skipping dependent containers (electrumx, lnd, mempool, btcpay, fedimint)" +fi +track_container "bitcoin-knots" # Ensure wallet exists (Bitcoin Knots no longer auto-creates a default wallet) if ! $DOCKER exec bitcoin-knots bitcoin-cli -rpcuser=$BITCOIN_RPC_USER -rpcpassword=$BITCOIN_RPC_PASS listwallets 2>/dev/null | grep -q "archipelago"; then @@ -278,7 +303,8 @@ if ! $DOCKER exec bitcoin-knots bitcoin-cli -rpcuser=$BITCOIN_RPC_USER -rpcpassw log "Bitcoin Knots wallet 'archipelago' created/loaded" fi -# 2. Mempool stack (matches deploy) +# 2. Mempool stack (matches deploy) — depends on Bitcoin +if [ "$BITCOIN_READY" = "true" ]; then 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 @@ -289,11 +315,12 @@ if ! $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-mempool-d -e MYSQL_DATABASE=mempool -e MYSQL_USER=mempool -e MYSQL_PASSWORD=$MEMPOOL_DB_PASS \ -e MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASS \ docker.io/mariadb:10.11 2>>"$LOG" || true - wait_for_container "Mempool MariaDB" "$DOCKER exec archy-mempool-db mariadb -uroot -p$MYSQL_ROOT_PASS -e 'SELECT 1'" 30 + wait_for_container "Mempool MariaDB" "echo 'SELECT 1' | $DOCKER exec -i archy-mempool-db mariadb -uroot --password=\"$MYSQL_ROOT_PASS\"" 30 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 +track_container "archy-mempool-db" 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 @@ -311,6 +338,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q electrumx; then docker.io/lukechilds/electrumx:v1.18.0 2>>"$LOG" || true fi fi +track_container "electrumx" if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q mempool-api; then log "Creating mempool-api..." @@ -326,6 +354,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q mempool-api; then -e DATABASE_USERNAME=mempool -e DATABASE_PASSWORD=$MEMPOOL_DB_PASS \ docker.io/mempool/backend:v2.5.0 2>>"$LOG" || true fi +track_container "mempool-api" if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-mempool-web|mempool-web'; then log "Creating mempool frontend..." @@ -335,6 +364,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-mempool-web| -p 4080:8080 -e FRONTEND_HTTP_PORT=8080 -e BACKEND_MAINNET_HTTP_HOST=mempool-api \ docker.io/mempool/frontend:v2.5.0 2>>"$LOG" || true fi +track_container "archy-mempool-web" # 2b. ElectrumX UI (status dashboard on port 50002, host network for backend access) if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q electrs-ui; then @@ -357,7 +387,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q electrs-ui; then fi fi -# 3. BTCPay stack (matches deploy) +# 3. BTCPay stack (matches deploy) — depends on Bitcoin 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 @@ -369,6 +399,7 @@ if ! $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-btcpay-db docker.io/postgres:15-alpine 2>>"$LOG" || true wait_for_container "BTCPay PostgreSQL" "$DOCKER exec archy-btcpay-db pg_isready -U postgres" 30 fi +track_container "archy-btcpay-db" # 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 || \ @@ -392,6 +423,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q archy-nbxplorer; the docker.io/nicolasdorier/nbxplorer:2.6.0 2>>"$LOG" && sleep 5 || true fi fi +track_container "archy-nbxplorer" if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q btcpay-server; then log "Creating BTCPay Server..." @@ -410,12 +442,13 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q btcpay-server; then -e BTCPAY_POSTGRES='User ID=btcpay;Password=$BTCPAY_DB_PASS;Host=archy-btcpay-db;Port=5432;Database=btcpay;Include Error Detail=true' \ docker.io/btcpayserver/btcpayserver:1.13.5 2>>"$LOG" || true fi +track_container "btcpay-server" # ── Tier 2: Core Services ───────────────────────────────────────────────── log "=== Tier 2: Core Services ===" sleep 5 # Let databases stabilize -# 4. LND +# 4. LND — depends on Bitcoin if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE '^lnd$'; then log "Creating LND..." mkdir -p /var/lib/archipelago/lnd @@ -457,8 +490,9 @@ LNDCONF -v /var/lib/archipelago/lnd:/root/.lnd \ docker.io/lightninglabs/lnd:v0.18.4-beta 2>>"$LOG" || true fi +track_container "lnd" -# 5. Fedimint +# 5. Fedimint — depends on Bitcoin if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint; then log "Creating Fedimint..." mkdir -p /var/lib/archipelago/fedimint @@ -476,6 +510,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint; then -e FM_BITCOIND_URL=http://"$TARGET_IP":8332 \ docker.io/fedimint/fedimintd:v0.10.0 2>>"$LOG" || true fi +track_container "fedimint" # 5b. Fedimint Gateway (companion to fedimint) # Auto-detect LND: if running with credentials, use lnd mode; otherwise use ldk (built-in) @@ -518,8 +553,13 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint-gateway; th ldk --ldk-lightning-port 9737 --ldk-alias archipelago-gateway 2>>"$LOG" || true fi fi +track_container "fedimint-gateway" -# ── Tier 3: Applications ────────────────────────────────────────────────── +else + log "SKIPPED: mempool stack, electrumx, btcpay stack, lnd, fedimint (Bitcoin not ready)" +fi # end BITCOIN_READY + +# ── Tier 3: Applications (independent — always attempt) ─────────────────── log "=== Tier 3: Applications ===" sleep 5 # Let core services stabilize @@ -536,6 +576,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'homeassistant|home -e TZ=UTC \ docker.io/homeassistant/home-assistant:2024.1 2>>"$LOG" || true fi +track_container "homeassistant" # 7. Single-container apps (Grafana, Uptime Kuma, Jellyfin, PhotoPrism, Ollama, Vaultwarden) if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q grafana; then @@ -552,6 +593,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q grafana; then -e GF_PATHS_DATA=/var/lib/grafana -e GF_USERS_ALLOW_SIGN_UP=false \ docker.io/grafana/grafana:10.2.0 2>>"$LOG" || true fi +track_container "grafana" if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q uptime-kuma; then log "Creating Uptime Kuma..." mkdir -p /var/lib/archipelago/uptime-kuma @@ -564,6 +606,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q uptime-kuma; then -e TZ=UTC \ docker.io/louislam/uptime-kuma:1 2>>"$LOG" || true fi +track_container "uptime-kuma" 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 @@ -576,6 +619,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q jellyfin; then -v /var/lib/archipelago/jellyfin/cache:/cache \ docker.io/jellyfin/jellyfin:10.8.13 2>>"$LOG" || true fi +track_container "jellyfin" if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q photoprism; then log "Creating PhotoPrism..." mkdir -p /var/lib/archipelago/photoprism @@ -588,6 +632,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q photoprism; then -e PHOTOPRISM_ADMIN_PASSWORD=archipelago -e PHOTOPRISM_DEFAULT_LOCALE=en \ "${PHOTOPRISM_IMAGE:-docker.io/photoprism/photoprism:240915}" 2>>"$LOG" || true fi +track_container "photoprism" if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q ollama; then log "Creating Ollama..." mkdir -p /var/lib/archipelago/ollama @@ -599,6 +644,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q ollama; then -p 11434:11434 -v /var/lib/archipelago/ollama:/root/.ollama \ "${OLLAMA_IMAGE:-docker.io/ollama/ollama:0.5.4}" 2>>"$LOG" || true fi +track_container "ollama" if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q vaultwarden; then log "Creating Vaultwarden..." mkdir -p /var/lib/archipelago/vaultwarden @@ -610,6 +656,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q vaultwarden; then -p 8082:80 -v /var/lib/archipelago/vaultwarden:/data \ docker.io/vaultwarden/server:1.30.0-alpine 2>>"$LOG" || true fi +track_container "vaultwarden" if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q nextcloud; then log "Creating Nextcloud..." mkdir -p /var/lib/archipelago/nextcloud @@ -621,6 +668,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q nextcloud; then -p 8085:80 -v /var/lib/archipelago/nextcloud:/var/www/html \ docker.io/library/nextcloud:28 2>>"$LOG" || true fi +track_container "nextcloud" if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q searxng; then log "Creating SearXNG..." $DOCKER run -d --name searxng --restart unless-stopped \ @@ -631,6 +679,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q searxng; then -p 8888:8080 \ "${SEARXNG_IMAGE:-docker.io/searxng/searxng:2024.11.17}" 2>>"$LOG" || true fi +track_container "searxng" if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q onlyoffice; then log "Creating OnlyOffice..." $DOCKER run -d --name onlyoffice --restart unless-stopped \ @@ -641,6 +690,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q onlyoffice; then -p 9980:80 \ docker.io/onlyoffice/documentserver:7.5.1 2>>"$LOG" || true fi +track_container "onlyoffice" if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q filebrowser; then log "Creating File Browser..." mkdir -p /var/lib/archipelago/filebrowser /var/lib/archipelago/filebrowser-db @@ -650,6 +700,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q filebrowser; then -p 8083:80 -v /var/lib/archipelago/filebrowser:/srv \ docker.io/filebrowser/filebrowser:v2.27.0 2>>"$LOG" || true fi +track_container "filebrowser" 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 @@ -663,6 +714,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q nginx-proxy-manager; -v /var/lib/archipelago/nginx-proxy-manager/letsencrypt:/etc/letsencrypt \ "${NPM_IMAGE:-docker.io/jc21/nginx-proxy-manager:2}" 2>>"$LOG" || true fi +track_container "nginx-proxy-manager" if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q portainer; then log "Creating Portainer..." mkdir -p /var/lib/archipelago/portainer @@ -676,6 +728,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q portainer; then -v /var/run/podman/podman.sock:/var/run/docker.sock \ docker.io/portainer/portainer-ce:2.19.4 2>>"$LOG" || true fi +track_container "portainer" if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q tailscale; then log "Creating Tailscale..." mkdir -p /var/lib/archipelago/tailscale @@ -695,6 +748,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q tailscale; then docker.io/tailscale/tailscale:stable \ sh -c 'tailscale web --listen 0.0.0.0:8240 & exec tailscaled' 2>>"$LOG" || true fi +track_container "tailscale" # Immich stack (postgres + redis + server - ML optional) # Remove old single-container 'immich' if present (wrong port 2283:3001, conflicts with immich_server) @@ -739,6 +793,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q immich_server; then ghcr.io/immich-app/immich-server:release 2>>"$LOG" || true fi fi +track_container "immich_server" # Penpot stack (postgres + valkey + backend + exporter + frontend) if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q penpot-frontend; then @@ -798,6 +853,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q penpot-frontend; the "${PENPOT_FRONTEND_IMAGE:-docker.io/penpotapp/frontend:2.4.2}" 2>>"$LOG" || true fi fi +track_container "penpot-frontend" # 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 @@ -912,18 +968,7 @@ mkdir -p /var/lib/archipelago/identities # Ensure archipelago user can write to these directories chown -R 1000:1000 /var/lib/archipelago/tor-config /var/lib/archipelago/identity /var/lib/archipelago/identities 2>/dev/null || true -# 11. Post-boot validation -log "Validating container creation..." -TOTAL=0; RUNNING=0 -for c in bitcoin-knots lnd btcpay-server fedimint homeassistant grafana uptime-kuma; do - TOTAL=$((TOTAL + 1)) - if $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q "$c"; then - RUNNING=$((RUNNING + 1)) - fi -done -log "Post-boot validation: $RUNNING/$TOTAL core containers running" - -# 12. Run container doctor for any remaining issues +# 11. Run container doctor for any remaining issues log "Running container doctor..." SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" if [ -x "$SCRIPT_DIR/container-doctor.sh" ]; then @@ -932,4 +977,19 @@ elif [ -x "/opt/archipelago/scripts/container-doctor.sh" ]; then bash "/opt/archipelago/scripts/container-doctor.sh" --local 2>&1 | tee -a "$LOG" fi +# 12. Final summary +FAILED=$((TOTAL - SUCCESS)) +log "=============================================" +log " FIRST-BOOT CONTAINER SUMMARY" +log "=============================================" +log " Total tracked: $TOTAL" +log " Running: $SUCCESS" +log " Failed: $FAILED" +if [ "$BITCOIN_READY" != "true" ]; then + log " Bitcoin: NOT READY (dependent containers skipped)" +fi +if [ -n "$FAILED_LIST" ]; then + log " Failed list: $FAILED_LIST" +fi +log "=============================================" log "First-boot container creation complete" diff --git a/scripts/trust-archipelago-cert.sh b/scripts/trust-archipelago-cert.sh index 50f1beac..55120f2b 100755 --- a/scripts/trust-archipelago-cert.sh +++ b/scripts/trust-archipelago-cert.sh @@ -18,13 +18,20 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" # Try to fetch cert from server via SSH (most reliable) -if [ -f "$SCRIPT_DIR/deploy-config.sh" ]; then +SSH_KEY="${ARCHIPELAGO_SSH_KEY:-$HOME/.ssh/archipelago-deploy}" +echo "Fetching certificate from server..." +if [ -f "$SSH_KEY" ]; then + ssh -o StrictHostKeyChecking=no -i "$SSH_KEY" archipelago@${HOST} \ + 'sudo -n cat /etc/archipelago/ssl/archipelago.crt' > "$CERT_FILE" 2>/dev/null || true +elif [ -f "$SCRIPT_DIR/deploy-config.sh" ]; then + # Last-resort fallback: password auth (leaks credentials to process list) . "$SCRIPT_DIR/deploy-config.sh" - SSH_OPTS="-o StrictHostKeyChecking=no -o PreferredAuthentications=password -o PubkeyAuthentication=no" + echo "WARNING: SSH key not found at $SSH_KEY — falling back to password auth" if command -v sshpass >/dev/null 2>&1; then - echo "Fetching certificate from server..." - sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS archipelago@${HOST} \ + sshpass -p "$ARCHIPELAGO_PASSWORD" ssh -o StrictHostKeyChecking=no archipelago@${HOST} \ 'sudo -n cat /etc/archipelago/ssl/archipelago.crt' > "$CERT_FILE" 2>/dev/null || true + else + echo "WARNING: No SSH key and sshpass not installed — skipping SSH fetch" fi fi