#!/bin/bash # # Deploy Archipelago code to the HP ProDesk target # # Usage: # ./scripts/deploy-to-target.sh # Sync and rebuild # ./scripts/deploy-to-target.sh --quick # Sync only, no rebuild # ./scripts/deploy-to-target.sh --live # Deploy to live system (default: 192.168.1.228) # ./scripts/deploy-to-target.sh --both # Deploy to 228, then copy to 198 # set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" # Load deploy config (password etc.) - deploy-config.sh is gitignored [ -f "$SCRIPT_DIR/deploy-config.sh" ] && . "$SCRIPT_DIR/deploy-config.sh" # Configuration TARGET_HOST="${ARCHIPELAGO_TARGET:-archipelago@192.168.1.228}" TARGET_DIR="/home/archipelago/archy" # Password for non-interactive SSH/rsync. Set in deploy-config.sh or ARCHIPELAGO_PASSWORD env. ARCHIPELAGO_PASSWORD="${ARCHIPELAGO_PASSWORD:-archipelago}" # Force password auth when using sshpass (avoids "Permission denied" from SSH key mismatch) SSH_OPTS="-o StrictHostKeyChecking=no -o PreferredAuthentications=password -o PubkeyAuthentication=no" echo "╔════════════════════════════════════════════════════════════════╗" echo "║ Deploying to Archipelago Target ║" echo "╚════════════════════════════════════════════════════════════════╝" echo "" echo "Target: $TARGET_HOST" echo "" # Parse arguments QUICK=false LIVE=false BOTH=false for arg in "$@"; do case $arg in --quick) QUICK=true ;; --live) LIVE=true ;; --both) BOTH=true ;; esac done # When --both: deploy to 228 first, then copy to 198 if [ "$BOTH" = true ]; then echo "Deploying to both servers (228, then 198)..." "$0" --live echo "" echo "📤 Copying to 192.168.1.198 (no rsync/cargo on that node)..." sshpass -p "$ARCHIPELAGO_PASSWORD" scp $SSH_OPTS archipelago@192.168.1.228:$TARGET_DIR/core/target/release/archipelago /tmp/archipelago-both 2>/dev/null || true sshpass -p "$ARCHIPELAGO_PASSWORD" scp $SSH_OPTS /tmp/archipelago-both archipelago@192.168.1.198:/tmp/archipelago-new sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS archipelago@192.168.1.228 "cd $TARGET_DIR && tar cf - web/dist/neode-ui 2>/dev/null" | sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS archipelago@192.168.1.198 "mkdir -p /tmp/web-deploy && cd /tmp/web-deploy && tar xf -" sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS archipelago@192.168.1.198 ' sudo systemctl stop archipelago sudo cp /tmp/archipelago-new /usr/local/bin/archipelago sudo chmod +x /usr/local/bin/archipelago rm -f /tmp/archipelago-new sudo rm -rf /opt/archipelago/web-ui/* sudo cp -r /tmp/web-deploy/web/dist/neode-ui/* /opt/archipelago/web-ui/ 2>/dev/null || true sudo chown -R 1000:1000 /opt/archipelago/web-ui sudo systemctl start archipelago sudo systemctl restart nginx echo " ✅ 192.168.1.198 deployed" ' rm -f /tmp/archipelago-both exit 0 fi # Sync code echo "📦 Syncing code..." sshpass -p "$ARCHIPELAGO_PASSWORD" rsync -avz --delete \ -e "ssh $SSH_OPTS" \ --exclude 'node_modules' \ --exclude 'target' \ --exclude 'dist' \ --exclude '.git' \ --exclude 'image-recipe/build' \ --exclude 'image-recipe/results' \ "$PROJECT_DIR/" "$TARGET_HOST:$TARGET_DIR/" if [ "$QUICK" = true ]; then echo "" echo "✅ Quick sync complete!" exit 0 fi # Build on target echo "" echo "🔨 Building on target..." # Frontend echo " Building frontend..." sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "cd $TARGET_DIR/neode-ui && npm install --silent && npm run build" 2>&1 | sed 's/^/ /' # Backend (if Rust is installed) if sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "source ~/.cargo/env 2>/dev/null && command -v cargo" >/dev/null 2>&1; then echo " Building backend..." sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "source ~/.cargo/env && cd $TARGET_DIR/core && cargo build --release 2>&1" | tail -10 | sed 's/^/ /' else echo " ⚠️ Rust not installed on target, skipping backend build" fi if [ "$LIVE" = true ]; then echo "" echo "🚀 Deploying to live system..." # Deploy backend (check if binary exists) if sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "[ -f $TARGET_DIR/core/target/release/archipelago ]" 2>/dev/null; then echo " Deploying backend binary..." sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "sudo systemctl stop archipelago" sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "sudo cp $TARGET_DIR/core/target/release/archipelago /usr/local/bin/" fi # Deploy frontend echo " Deploying frontend..." sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "sudo rm -rf /opt/archipelago/web-ui/*" sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "sudo cp -r $TARGET_DIR/web/dist/neode-ui/* /opt/archipelago/web-ui/" sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "sudo chown -R 1000:1000 /opt/archipelago/web-ui" # Add /archipelago/ to nginx if missing (for peer messaging over Tor) if [ -f "$SCRIPT_DIR/nginx-archipelago-patch.conf" ]; then sshpass -p "$ARCHIPELAGO_PASSWORD" scp $SSH_OPTS "$SCRIPT_DIR/nginx-archipelago-patch.conf" "$TARGET_HOST:/tmp/archipelago-nginx-patch.conf" 2>/dev/null || true sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" ' CFG=/etc/nginx/sites-available/archipelago if [ -f "$CFG" ] && [ -f /tmp/archipelago-nginx-patch.conf ] && ! grep -q "location /archipelago/" "$CFG"; then echo " Adding /archipelago/ to nginx..." sudo sed -i "/# Proxy API requests to backend/r /tmp/archipelago-nginx-patch.conf" "$CFG" fi rm -f /tmp/archipelago-nginx-patch.conf ' 2>/dev/null || true fi # Restart services echo " Restarting services..." sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "sudo systemctl start archipelago && sudo systemctl restart nginx" # Rebuild and recreate LND UI container (port 8081 so Launch from UI and http://host:8081 both work) echo " Rebuilding LND UI..." if sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "cd $TARGET_DIR/docker/lnd-ui && (command -v podman >/dev/null 2>&1 && sudo podman build --no-cache -t lnd-ui:latest . || sudo docker build --no-cache -t lnd-ui:latest .)" 2>&1 | tail -12 | sed 's/^/ /'; then echo " Recreating LND UI container (port 8081)..." sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" ' DOCKER=podman command -v podman >/dev/null 2>&1 || DOCKER=docker for c in $(sudo $DOCKER ps -a --format "{{.Names}}" 2>/dev/null | grep -i lnd-ui); do [ -n "$c" ] && sudo $DOCKER stop "$c" 2>/dev/null; sudo $DOCKER rm -f "$c" 2>/dev/null done sudo $DOCKER run -d --name archy-lnd-ui -p 8081:80 --restart unless-stopped lnd-ui:latest ' 2>&1 | sed 's/^/ /' || true fi # Rebuild and recreate Electrs UI container (port 50002) echo " Rebuilding Electrs UI..." if sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "cd $TARGET_DIR/docker/electrs-ui && (command -v podman >/dev/null 2>&1 && sudo podman build --no-cache -t electrs-ui:latest . || sudo docker build --no-cache -t electrs-ui:latest .)" 2>&1 | tail -12 | sed 's/^/ /'; then echo " Recreating Electrs UI container (port 50002, host network)..." sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" ' DOCKER=podman command -v podman >/dev/null 2>&1 || DOCKER=docker for c in $(sudo $DOCKER ps -a --format "{{.Names}}" 2>/dev/null | grep -i electrs-ui); do [ -n "$c" ] && sudo $DOCKER stop "$c" 2>/dev/null; sudo $DOCKER rm -f "$c" 2>/dev/null done sudo $DOCKER run -d --name archy-electrs-ui --network host --restart unless-stopped electrs-ui:latest ' 2>&1 | sed 's/^/ /' || true fi # Bitcoin Knots: required for Mempool, Electrs, BTCPay, Fedimint TARGET_IP="$(echo "$TARGET_HOST" | cut -d@ -f2)" echo " Ensuring Bitcoin Knots (required for Electrs/Mempool)..." sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" " DOCKER=podman command -v podman >/dev/null 2>&1 || DOCKER=docker sudo \$DOCKER network create archy-net 2>/dev/null || true NET_OPT='--network archy-net' if ! sudo \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'bitcoin-knots|archy-bitcoin-knots'; then echo ' Creating Bitcoin Knots (mainnet, archipelago RPC)...' sudo mkdir -p /var/lib/archipelago/bitcoin sudo \$DOCKER run -d --name bitcoin-knots --restart unless-stopped \$NET_OPT \ -p 8332:8332 -p 8333:8333 \ -v /var/lib/archipelago/bitcoin:/home/bitcoin/.bitcoin \ docker.io/bitcoinknots/bitcoin:latest \ -server=1 -txindex=1 \ -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 \ -rpcuser=archipelago -rpcpassword=archipelago123 \ -dbcache=4096 echo ' Bitcoin Knots started (sync may take hours)' else sudo \$DOCKER network connect archy-net bitcoin-knots 2>/dev/null || true fi " 2>&1 | sed 's/^/ /' || true # Fix Mempool: clean duplicates, ensure full stack - mysql, backend (8999), frontend (4080) echo " Fixing Mempool stack (host=$TARGET_IP)..." sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" " DOCKER=podman command -v podman >/dev/null 2>&1 || DOCKER=docker TARGET_IP='$TARGET_IP' NET_OPT='--network archy-net' # Clean any duplicate/old mempool containers (user may have two versions) for c in mempool mempool-api mempool-electrs mempool-web archy-mempool-api archy-mempool-web; do sudo \$DOCKER stop \$c 2>/dev/null sudo \$DOCKER rm -f \$c 2>/dev/null done # Create mysql-mempool if missing if ! sudo \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qE 'mysql-mempool|archy-mempool-db'; then echo ' Creating mysql-mempool...' sudo mkdir -p /var/lib/archipelago/mysql-mempool sudo \$DOCKER run -d --name archy-mempool-db --restart unless-stopped \$NET_OPT \ -v /var/lib/archipelago/mysql-mempool:/var/lib/mysql \ -e MYSQL_DATABASE=mempool \ -e MYSQL_USER=mempool \ -e MYSQL_PASSWORD=mempoolpass \ -e MYSQL_ROOT_PASSWORD=rootpass \ docker.io/mariadb:10.11 sleep 3 fi MYSQL_CNT=\$(sudo \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -E 'mysql-mempool|archy-mempool-db' | head -1) MYSQL_CNT=\${MYSQL_CNT:-archy-mempool-db} # Ensure DB is on archy-net so mempool-api can resolve it sudo \$DOCKER network connect archy-net \$MYSQL_CNT 2>/dev/null || true # Create mempool-electrs (indexer - connects to Bitcoin, exposes Electrum protocol on 50001) for c in \$(sudo \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep mempool-electrs); do echo ' Recreating mempool-electrs...' sudo \$DOCKER stop \"\$c\" 2>/dev/null sudo \$DOCKER rm -f \"\$c\" 2>/dev/null done if ! sudo \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q mempool-electrs; then echo ' Creating mempool-electrs (indexer - may take hours to sync)...' sudo mkdir -p /var/lib/archipelago/mempool-electrs # Use host IP to reach Bitcoin (TARGET_IP:8332) - more reliable than container DNS (bitcoin-knots) sudo \$DOCKER run -d --name mempool-electrs --restart unless-stopped \ -p 50001:50001 \ -v /var/lib/archipelago/mempool-electrs:/data \ docker.io/mempool/electrs:latest \ --daemon-rpc-addr \$TARGET_IP:8332 \ --cookie archipelago:archipelago123 \ --jsonrpc-import \ --electrum-rpc-addr 0.0.0.0:50001 \ --db-dir /data \ --lightmode fi # Create/recreate mempool-api (backend on 8999) - required for mempool to work for c in \$(sudo \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -E 'mempool-api|archy-mempool-api'); do echo ' Recreating mempool-api (backend)...' sudo \$DOCKER stop \"\$c\" 2>/dev/null sudo \$DOCKER rm -f \"\$c\" 2>/dev/null done if ! sudo \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q mempool-api; then echo ' Creating mempool-api (backend)...' sudo mkdir -p /var/lib/archipelago/mempool sudo \$DOCKER run -d --name mempool-api --restart unless-stopped \$NET_OPT \ -p 8999:8999 \ -v /var/lib/archipelago/mempool:/data \ -e MEMPOOL_BACKEND=electrum \ -e ELECTRUM_HOST=mempool-electrs \ -e ELECTRUM_PORT=50001 \ -e ELECTRUM_TLS_ENABLED=false \ -e CORE_RPC_HOST=\$TARGET_IP \ -e CORE_RPC_PORT=8332 \ -e CORE_RPC_USERNAME=archipelago \ -e CORE_RPC_PASSWORD=archipelago123 \ -e DATABASE_ENABLED=true \ -e DATABASE_HOST=\$MYSQL_CNT \ -e DATABASE_DATABASE=mempool \ -e DATABASE_USERNAME=mempool \ -e DATABASE_PASSWORD=mempoolpass \ docker.io/mempool/backend:v2.5.0 fi # Recreate mempool frontend - handle both 'mempool' and 'mempool-web' (frontend was on wrong port 8999) for c in \$(sudo \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -E '^mempool\$|mempool-web|archy-mempool-web'); do echo ' Recreating mempool frontend on 4080...' sudo \$DOCKER stop \"\$c\" 2>/dev/null sudo \$DOCKER rm -f \"\$c\" 2>/dev/null break done if ! sudo \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q archy-mempool-web; then echo ' Creating mempool frontend on 4080...' sudo \$DOCKER run -d --name archy-mempool-web --restart unless-stopped \$NET_OPT \ -p 4080:8080 \ -e FRONTEND_HTTP_PORT=8080 \ -e BACKEND_MAINNET_HTTP_HOST=mempool-api \ docker.io/mempool/frontend:v2.5.0 fi " 2>&1 | sed 's/^/ /' || true # Fix BTCPay Server: requires PostgreSQL; create archy-btcpay-db, recreate btcpay-server with BTCPAY_POSTGRES echo " Fixing BTCPay Server stack..." TARGET_IP="$(echo "$TARGET_HOST" | cut -d@ -f2)" sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" " DOCKER=podman command -v podman >/dev/null 2>&1 || DOCKER=docker TARGET_IP='$TARGET_IP' sudo \$DOCKER network create archy-net 2>/dev/null || true NET_OPT='--network archy-net' # Create PostgreSQL for BTCPay if missing if ! sudo \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-btcpay-db|postgres-btcpay'; then echo ' Creating archy-btcpay-db (PostgreSQL)...' sudo mkdir -p /var/lib/archipelago/postgres-btcpay sudo \$DOCKER run -d --name archy-btcpay-db --restart unless-stopped \$NET_OPT \ -v /var/lib/archipelago/postgres-btcpay:/var/lib/postgresql/data \ -e POSTGRES_DB=btcpay \ -e POSTGRES_USER=btcpay \ -e POSTGRES_PASSWORD=btcpaypass \ docker.io/postgres:15-alpine sleep 3 fi # Recreate btcpay-server with PostgreSQL and Bitcoin RPC for c in \$(sudo \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -E 'btcpay-server|archy-btcpay'); do echo ' Recreating btcpay-server with PostgreSQL...' sudo \$DOCKER stop \"\$c\" 2>/dev/null sudo \$DOCKER rm -f \"\$c\" 2>/dev/null break done if ! sudo \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q btcpay-server; then echo ' Creating btcpay-server on 23000...' sudo mkdir -p /var/lib/archipelago/btcpay sudo \$DOCKER run -d --name btcpay-server --restart unless-stopped \$NET_OPT \ -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_BTCRPCURL=http://\$TARGET_IP:8332 \ -e BTCPAY_BTCRPCUSER=archipelago \ -e BTCPAY_BTCRPCPASSWORD=archipelago123 \ -e BTCPAY_POSTGRES='User ID=btcpay;Password=btcpaypass;Host=archy-btcpay-db;Port=5432;Database=btcpay;Include Error Detail=true' \ docker.io/btcpayserver/btcpayserver:1.13.5 fi " 2>&1 | sed 's/^/ /' || true # Tor: global hidden services - each service gets its own .onion address echo " Setting up Tor (hidden services for each app)..." TARGET_IP="$(echo "$TARGET_HOST" | cut -d@ -f2)" sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" " DOCKER=podman command -v podman >/dev/null 2>&1 || DOCKER=docker TARGET_IP='$TARGET_IP' sudo mkdir -p /var/lib/archipelago/tor # Deploy torrc from repo (or create if missing) if [ -f $TARGET_DIR/scripts/tor/torrc.template ]; then sudo cp $TARGET_DIR/scripts/tor/torrc.template /var/lib/archipelago/tor/torrc fi if [ ! -f /var/lib/archipelago/tor/torrc ]; then echo 'SocksPort 9050' | sudo tee /var/lib/archipelago/tor/torrc echo 'ControlPort 0' | sudo tee -a /var/lib/archipelago/tor/torrc echo 'DataDirectory /var/lib/archipelago/tor' | sudo tee -a /var/lib/archipelago/tor/torrc echo 'HiddenServiceDir /var/lib/archipelago/tor/hidden_service_archipelago/' | sudo tee -a /var/lib/archipelago/tor/torrc echo 'HiddenServicePort 80 127.0.0.1:80' | sudo tee -a /var/lib/archipelago/tor/torrc echo 'HiddenServiceDir /var/lib/archipelago/tor/hidden_service_lnd/' | sudo tee -a /var/lib/archipelago/tor/torrc echo 'HiddenServicePort 80 127.0.0.1:8081' | sudo tee -a /var/lib/archipelago/tor/torrc echo 'HiddenServiceDir /var/lib/archipelago/tor/hidden_service_btcpay/' | sudo tee -a /var/lib/archipelago/tor/torrc echo 'HiddenServicePort 80 127.0.0.1:23000' | sudo tee -a /var/lib/archipelago/tor/torrc echo 'HiddenServiceDir /var/lib/archipelago/tor/hidden_service_mempool/' | sudo tee -a /var/lib/archipelago/tor/torrc echo 'HiddenServicePort 80 127.0.0.1:4080' | sudo tee -a /var/lib/archipelago/tor/torrc echo 'HiddenServiceDir /var/lib/archipelago/tor/hidden_service_fedimint/' | sudo tee -a /var/lib/archipelago/tor/torrc echo 'HiddenServicePort 80 127.0.0.1:8175' | sudo tee -a /var/lib/archipelago/tor/torrc fi for c in \$(sudo \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -E 'archy-tor|^tor\$'); do sudo \$DOCKER stop \"\$c\" 2>/dev/null sudo \$DOCKER rm -f \"\$c\" 2>/dev/null done if ! sudo \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q archy-tor; then echo ' Creating Tor container (host network for hidden services)...' if sudo \$DOCKER run -d --name archy-tor --restart unless-stopped --network host \ -v /var/lib/archipelago/tor:/var/lib/archipelago/tor \ --entrypoint tor \ docker.io/andrius/alpine-tor:latest \ -f /var/lib/archipelago/tor/torrc 2>/dev/null; then echo ' Tor container started (andrius/alpine-tor)' else echo ' Tor container image failed, trying system tor...' sudo apt-get update -qq && sudo apt-get install -y -qq tor 2>/dev/null || true if command -v tor >/dev/null 2>&1; then sudo cp /var/lib/archipelago/tor/torrc /etc/tor/torrc 2>/dev/null || true sudo chown -R debian-tor:debian-tor /var/lib/archipelago/tor 2>/dev/null || true sudo systemctl enable tor 2>/dev/null sudo systemctl restart tor 2>/dev/null echo ' Using system Tor daemon' fi fi fi " 2>&1 | sed 's/^/ /' || true # Tor diagnostic: check if hostname files exist (may take 30-60s after Tor starts) echo " Checking Tor hostname files..." sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" " for svc in archipelago btcpay mempool lnd fedimint; do f=/var/lib/archipelago/tor/hidden_service_\${svc}/hostname if [ -f \"\$f\" ]; then echo \" ✓ \$svc: \$(cat \$f)\" else echo \" ✗ \$svc: hostname not yet generated (Tor may need 30-60s)\" fi done " 2>&1 | sed 's/^/ /' || true # Recreate Fedimint with FM_API_URL for Guardian UI (fixes "Api URL must be configured") echo " Fixing Fedimint API URL..." TARGET_IP="$(echo "$TARGET_HOST" | cut -d@ -f2)" sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" " DOCKER=podman command -v podman >/dev/null 2>&1 || DOCKER=docker for c in \$(sudo \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -E '^fedimint\$'); do echo ' Recreating fedimint with FM_API_URL...' sudo \$DOCKER stop \"\$c\" 2>/dev/null sudo \$DOCKER rm -f \"\$c\" 2>/dev/null sudo \$DOCKER run -d --name fedimint --restart unless-stopped \ -p 8173:8173 -p 8174:8174 -p 8175:8175 \ -v /var/lib/archipelago/fedimint:/data \ -e FM_DATA_DIR=/data \ -e FM_BITCOIND_USERNAME=archipelago \ -e FM_BITCOIND_PASSWORD=archipelago123 \ -e FM_BITCOIN_NETWORK=bitcoin \ -e FM_BIND_P2P=0.0.0.0:8173 \ -e FM_BIND_API=0.0.0.0:8174 \ -e FM_BIND_UI=0.0.0.0:8175 \ -e FM_P2P_URL=fedimint://$TARGET_IP:8173 \ -e FM_API_URL=ws://$TARGET_IP:8174 \ -e FM_BITCOIND_URL=http://$TARGET_IP:8332 \ docker.io/fedimint/fedimintd:v0.10.0 break done " 2>&1 | sed 's/^/ /' || true echo "" echo "✅ Deployed to live system!" echo " Backend: $(sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" 'sudo systemctl is-active archipelago')" echo " Web UI: http://$(echo $TARGET_HOST | cut -d@ -f2)" else echo "" echo "✅ Build complete!" echo "" echo "To test frontend dev server:" echo " ssh $TARGET_HOST" echo " cd ~/archy/neode-ui && npm run dev -- --host 0.0.0.0" echo " Then open: http://$(echo $TARGET_HOST | cut -d@ -f2):5173" echo "" echo "To deploy to live system:" echo " ./scripts/deploy-to-target.sh --live" fi