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>
This commit is contained in:
@@ -96,8 +96,52 @@ section_end() {
|
||||
echo " (${elapsed}s)"
|
||||
}
|
||||
|
||||
# ── Progress bar ──────────────────────────────────────────────
|
||||
CURRENT_STEP=0
|
||||
BAR_WIDTH=30
|
||||
|
||||
calculate_total_steps() {
|
||||
local total=4 # SSH, prereqs, health, git state
|
||||
|
||||
if [[ "$QUICK" == "true" ]]; then
|
||||
total=$((total + 1)) # sync only
|
||||
echo $total; return
|
||||
fi
|
||||
|
||||
total=$((total + 1)) # sync code
|
||||
total=$((total + 1)) # frontend build
|
||||
|
||||
if [[ "$FRONTEND_ONLY" != "true" ]]; then
|
||||
total=$((total + 1)) # backend build
|
||||
fi
|
||||
|
||||
if [[ "$LIVE" == "true" ]]; then
|
||||
total=$((total + 14)) # rollback, frontend, AIUI, nginx, systemd, claude proxy, dev mode, data dirs, nostr-provider, filebrowser, manifest, restart, HTTPS, health check
|
||||
if [[ "$FRONTEND_ONLY" != "true" ]]; then
|
||||
total=$((total + 1)) # deploy backend binary
|
||||
total=$((total + 16)) # container rebuilds
|
||||
fi
|
||||
total=$((total + 3)) # UFW, IndeedHub fix, container doctor
|
||||
fi
|
||||
|
||||
echo $total
|
||||
}
|
||||
|
||||
TOTAL_STEPS=$(calculate_total_steps)
|
||||
|
||||
progress() {
|
||||
CURRENT_STEP=$((CURRENT_STEP + 1))
|
||||
local pct=$((CURRENT_STEP * 100 / TOTAL_STEPS))
|
||||
local filled=$((pct * BAR_WIDTH / 100))
|
||||
local empty=$((BAR_WIDTH - filled))
|
||||
local bar
|
||||
bar=$(printf '%*s' "$filled" '' | tr ' ' '█')$(printf '%*s' "$empty" '' | tr ' ' '░')
|
||||
printf "\033[1;36m━━━ [%s] %3d%% (%d/%d)\033[0m %s\n" "$bar" "$pct" "$CURRENT_STEP" "$TOTAL_STEPS" "$1"
|
||||
}
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
# SSH connectivity pre-flight check
|
||||
echo "$(timestamp) Checking SSH connectivity..."
|
||||
progress "Checking SSH connectivity"
|
||||
if ! ssh $SSH_OPTS -o ConnectTimeout=5 "$TARGET_HOST" "echo ok" >/dev/null 2>&1; then
|
||||
echo " ERROR: Cannot connect to $TARGET_HOST"
|
||||
echo " Check that the server is on and reachable."
|
||||
@@ -106,7 +150,7 @@ fi
|
||||
echo " Connected."
|
||||
|
||||
# Install prerequisites if missing (rsync for code sync, python3 for Claude API proxy)
|
||||
echo "$(timestamp) Checking prerequisites..."
|
||||
progress "Checking prerequisites"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
NEED_INSTALL=""
|
||||
command -v rsync >/dev/null 2>&1 || NEED_INSTALL="$NEED_INSTALL rsync"
|
||||
@@ -125,6 +169,7 @@ ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
' 2>&1
|
||||
|
||||
# Pre-deploy health check (informational — warns but does not block)
|
||||
progress "Pre-deploy health check"
|
||||
TARGET_IP_ONLY="$(echo "$TARGET_HOST" | cut -d@ -f2)"
|
||||
PRE_HEALTH=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 "http://$TARGET_IP_ONLY/health" 2>/dev/null || echo "000")
|
||||
if [ "$PRE_HEALTH" = "200" ]; then
|
||||
@@ -134,6 +179,30 @@ else
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Git state check — detect uncommitted changes and record deploy version
|
||||
progress "Checking git state"
|
||||
DEPLOY_COMMIT=$(git -C "$PROJECT_DIR" rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||
DEPLOY_COMMIT_FULL=$(git -C "$PROJECT_DIR" rev-parse HEAD 2>/dev/null || echo "unknown")
|
||||
DEPLOY_BRANCH=$(git -C "$PROJECT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
||||
DIRTY_FILES=$(git -C "$PROJECT_DIR" status --porcelain 2>/dev/null | grep -v '^??' | grep -v '\.claude/memory/' || true)
|
||||
DEPLOY_DIRTY=false
|
||||
|
||||
echo "$(timestamp) Git state: $DEPLOY_BRANCH @ $DEPLOY_COMMIT"
|
||||
if [ -n "$DIRTY_FILES" ]; then
|
||||
DEPLOY_DIRTY=true
|
||||
DIRTY_COUNT=$(echo "$DIRTY_FILES" | wc -l | tr -d ' ')
|
||||
echo " ⚠️ WARNING: $DIRTY_COUNT uncommitted change(s) — deploying working directory, NOT last commit"
|
||||
echo "$DIRTY_FILES" | head -10 | sed 's/^/ /'
|
||||
[ "$DIRTY_COUNT" -gt 10 ] && echo " ... and $((DIRTY_COUNT - 10)) more"
|
||||
echo ""
|
||||
echo " To deploy clean: commit or stash changes first"
|
||||
echo " Continuing in 3 seconds... (Ctrl+C to abort)"
|
||||
sleep 3
|
||||
else
|
||||
echo " Working tree clean — deploying commit $DEPLOY_COMMIT"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# When --canary: deploy to 198 first, verify health, then deploy to 228
|
||||
if [ "$CANARY" = true ]; then
|
||||
echo "🐤 Canary deploy: .198 first, then .228 if healthy..."
|
||||
@@ -264,8 +333,26 @@ if [ "$BOTH" = true ]; then
|
||||
fi
|
||||
' 2>/dev/null || true
|
||||
|
||||
# Write deploy manifest to .198
|
||||
DEPLOY_TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
ssh $SSH_OPTS "$TARGET_198" "sudo tee /opt/archipelago/deploy-manifest.json > /dev/null" << MANIFEST_198_EOF
|
||||
{
|
||||
"commit": "$DEPLOY_COMMIT_FULL",
|
||||
"commit_short": "$DEPLOY_COMMIT",
|
||||
"branch": "$DEPLOY_BRANCH",
|
||||
"dirty": $DEPLOY_DIRTY,
|
||||
"deployed_at": "$DEPLOY_TS",
|
||||
"deployed_from": "$(hostname)",
|
||||
"target": "$TARGET_198"
|
||||
}
|
||||
MANIFEST_198_EOF
|
||||
|
||||
ssh $SSH_OPTS "$TARGET_198" "sudo systemctl start archipelago && sudo systemctl restart nginx"
|
||||
|
||||
# Run container doctor on .198
|
||||
echo " Running container doctor on .198..."
|
||||
"$SCRIPT_DIR/container-doctor.sh" "$TARGET_198" 2>&1 | sed 's/^/ /' || true
|
||||
|
||||
# Post-deploy health check on .198
|
||||
echo " Checking .198 health..."
|
||||
HEALTH_198="fail"
|
||||
@@ -286,7 +373,7 @@ fi
|
||||
|
||||
# Sync code
|
||||
section_start
|
||||
echo "$(timestamp) 📦 Syncing code..."
|
||||
progress "Syncing code"
|
||||
rsync -avz --delete \
|
||||
-e "ssh $SSH_OPTS" \
|
||||
--exclude 'node_modules' \
|
||||
@@ -306,20 +393,17 @@ fi
|
||||
|
||||
# Build on target
|
||||
echo ""
|
||||
echo "$(timestamp) 🔨 Building on target..."
|
||||
|
||||
# Frontend
|
||||
progress "Building frontend"
|
||||
section_start
|
||||
echo "$(timestamp) Building frontend (vue-tsc + vite)..."
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "cd $TARGET_DIR/neode-ui && npm install --silent && npm run build" 2>&1 | sed 's/^/ /'
|
||||
section_end
|
||||
|
||||
# Backend (if Rust is installed) — skip with --frontend-only
|
||||
if [ "$FRONTEND_ONLY" = true ]; then
|
||||
echo "$(timestamp) Skipping backend build (--frontend-only)"
|
||||
echo " Skipping backend build (--frontend-only)"
|
||||
elif ssh $SSH_OPTS "$TARGET_HOST" "source ~/.cargo/env 2>/dev/null && command -v cargo" >/dev/null 2>&1; then
|
||||
progress "Building backend (Rust release)"
|
||||
section_start
|
||||
echo "$(timestamp) Building backend (Rust release — this takes 1-2 min)..."
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "source ~/.cargo/env && cd $TARGET_DIR/core && cargo build --release 2>&1" | sed 's/^/ /'
|
||||
section_end
|
||||
else
|
||||
@@ -327,11 +411,9 @@ else
|
||||
fi
|
||||
|
||||
if [ "$LIVE" = true ]; then
|
||||
echo ""
|
||||
echo "$(timestamp) 🚀 Deploying to live system..."
|
||||
|
||||
# Create rollback backup before deploying
|
||||
echo "$(timestamp) Creating rollback backup..."
|
||||
progress "Creating rollback backup"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
sudo mkdir -p /opt/archipelago/rollback
|
||||
[ -f /usr/local/bin/archipelago ] && sudo cp /usr/local/bin/archipelago /opt/archipelago/rollback/archipelago.bak 2>/dev/null || true
|
||||
@@ -340,20 +422,21 @@ if [ "$LIVE" = true ]; then
|
||||
|
||||
# Deploy backend (check if binary exists) — skip with --frontend-only
|
||||
if [ "$FRONTEND_ONLY" = true ]; then
|
||||
echo "$(timestamp) Skipping backend deploy (--frontend-only)"
|
||||
echo " Skipping backend deploy (--frontend-only)"
|
||||
elif ssh $SSH_OPTS "$TARGET_HOST" "[ -f $TARGET_DIR/core/target/release/archipelago ]" 2>/dev/null; then
|
||||
echo "$(timestamp) Deploying backend binary..."
|
||||
progress "Deploying backend binary"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "sudo systemctl stop archipelago"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "sudo cp $TARGET_DIR/core/target/release/archipelago /usr/local/bin/"
|
||||
fi
|
||||
|
||||
# Deploy frontend (preserve aiui/ and claude-login.html — they are NOT part of the neode-ui build)
|
||||
echo "$(timestamp) Deploying frontend..."
|
||||
progress "Deploying frontend"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "sudo find /opt/archipelago/web-ui -mindepth 1 -maxdepth 1 ! -name 'aiui' ! -name 'claude-login.html' -exec rm -rf {} +"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "sudo cp -rf $TARGET_DIR/web/dist/neode-ui/* /opt/archipelago/web-ui/"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "sudo chown -R 1000:1000 /opt/archipelago/web-ui"
|
||||
|
||||
# Build and deploy AIUI (non-fatal — never delete existing AIUI on failure)
|
||||
progress "Building & deploying AIUI"
|
||||
AIUI_DIR="$PROJECT_DIR/../AIUI"
|
||||
AIUI_DIST="$AIUI_DIR/packages/app/dist"
|
||||
# Auto-build AIUI if dist is missing or older than source
|
||||
@@ -378,10 +461,10 @@ if [ "$LIVE" = true ]; then
|
||||
fi
|
||||
|
||||
# Sync nginx config from image-recipe (single source of truth)
|
||||
progress "Syncing nginx configuration"
|
||||
NGINX_CFG="$PROJECT_DIR/image-recipe/configs/nginx-archipelago.conf"
|
||||
SNIPPETS_DIR="$PROJECT_DIR/image-recipe/configs/snippets"
|
||||
if [ -f "$NGINX_CFG" ]; then
|
||||
echo "$(timestamp) Syncing nginx config..."
|
||||
scp $SSH_OPTS "$NGINX_CFG" "$TARGET_HOST:/tmp/nginx-archipelago.conf" 2>/dev/null || true
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
sudo cp /tmp/nginx-archipelago.conf /etc/nginx/sites-available/archipelago
|
||||
@@ -391,7 +474,6 @@ if [ "$LIVE" = true ]; then
|
||||
|
||||
# Sync nginx snippet files (HTTPS app proxies, PWA headers — included by main config)
|
||||
if [ -d "$SNIPPETS_DIR" ]; then
|
||||
echo "$(timestamp) Syncing nginx snippets..."
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "sudo mkdir -p /etc/nginx/snippets" 2>/dev/null || true
|
||||
for f in "$SNIPPETS_DIR"/*.conf; do
|
||||
[ -f "$f" ] && scp $SSH_OPTS "$f" "$TARGET_HOST:/tmp/nginx-snippet-$(basename $f)" 2>/dev/null || true
|
||||
@@ -413,9 +495,9 @@ if [ "$LIVE" = true ]; then
|
||||
ssh $SSH_OPTS "$TARGET_HOST" 'sudo nginx -t 2>&1 && echo " nginx config OK" || echo " ⚠️ nginx config test failed"' 2>/dev/null || true
|
||||
|
||||
# Sync systemd service file (single source of truth: image-recipe/configs/)
|
||||
progress "Syncing systemd service"
|
||||
SERVICE_FILE="$PROJECT_DIR/image-recipe/configs/archipelago.service"
|
||||
if [ -f "$SERVICE_FILE" ]; then
|
||||
echo "$(timestamp) Syncing systemd service file..."
|
||||
scp $SSH_OPTS "$SERVICE_FILE" "$TARGET_HOST:/tmp/archipelago.service" 2>/dev/null || true
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
if ! diff -q /tmp/archipelago.service /etc/systemd/system/archipelago.service >/dev/null 2>&1; then
|
||||
@@ -430,7 +512,7 @@ if [ "$LIVE" = true ]; then
|
||||
fi
|
||||
|
||||
# Deploy Claude API proxy (auto-install if missing)
|
||||
echo "$(timestamp) Setting up Claude API proxy..."
|
||||
progress "Setting up Claude API proxy"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
echo " Updating Claude API proxy on port 3142..."
|
||||
# Check for API key in existing service or setup-aiui-server.sh
|
||||
@@ -510,7 +592,7 @@ PYEOF
|
||||
' 2>/dev/null || true
|
||||
|
||||
# Dev mode for Tailscale HTTP access (cookies need Secure flag disabled over plain HTTP)
|
||||
echo "$(timestamp) Checking dev mode..."
|
||||
progress "Configuring dev mode"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
if [ -f /etc/systemd/system/archipelago.service.d/override.conf ] && grep -q "ARCHIPELAGO_DEV_MODE=true" /etc/systemd/system/archipelago.service.d/override.conf 2>/dev/null; then
|
||||
echo " Dev mode already enabled"
|
||||
@@ -524,7 +606,7 @@ PYEOF
|
||||
' 2>/dev/null || true
|
||||
|
||||
# Create data directories for DWN, content sharing, federation, identities
|
||||
echo "$(timestamp) Ensuring data directories for DWN, content, federation..."
|
||||
progress "Creating data directories"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
sudo mkdir -p /var/lib/archipelago/dwn/messages
|
||||
sudo mkdir -p /var/lib/archipelago/dwn/protocols
|
||||
@@ -537,12 +619,11 @@ PYEOF
|
||||
' 2>/dev/null || true
|
||||
|
||||
# Deploy nostr-provider.js for NIP-07 iframe signing (window.nostr support)
|
||||
echo "$(timestamp) Deploying nostr-provider.js..."
|
||||
progress "Deploying nostr-provider.js"
|
||||
scp $SSH_OPTS "$PROJECT_DIR/neode-ui/public/nostr-provider.js" "$TARGET_HOST:/tmp/nostr-provider.js" 2>/dev/null && \
|
||||
ssh $SSH_OPTS "$TARGET_HOST" 'sudo cp /tmp/nostr-provider.js /opt/archipelago/web-ui/nostr-provider.js && echo " nostr-provider.js deployed"' 2>/dev/null || echo " (nostr-provider.js not found, skipping)"
|
||||
|
||||
# Sync nginx config (includes all app proxies, NIP-07 sub_filter, AIUI proxy, external URL proxies)
|
||||
echo "$(timestamp) Syncing nginx config..."
|
||||
# Sync nginx config (second pass — includes HTTPS snippets)
|
||||
scp $SSH_OPTS "$PROJECT_DIR/image-recipe/configs/nginx-archipelago.conf" "$TARGET_HOST:/tmp/nginx-archipelago.conf" 2>/dev/null && \
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
sudo cp /tmp/nginx-archipelago.conf /etc/nginx/sites-available/archipelago
|
||||
@@ -557,7 +638,7 @@ PYEOF
|
||||
fi
|
||||
|
||||
# Fix FileBrowser — recreate if read-only root, create if missing
|
||||
echo "$(timestamp) Checking FileBrowser..."
|
||||
progress "Checking FileBrowser"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
DOCKER=podman
|
||||
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||
@@ -582,19 +663,34 @@ PYEOF
|
||||
fi
|
||||
' 2>/dev/null || true
|
||||
|
||||
# Write deploy manifest — stamps the server with exactly what was deployed
|
||||
progress "Writing deploy manifest"
|
||||
DEPLOY_TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "sudo tee /opt/archipelago/deploy-manifest.json > /dev/null" << MANIFEST_EOF
|
||||
{
|
||||
"commit": "$DEPLOY_COMMIT_FULL",
|
||||
"commit_short": "$DEPLOY_COMMIT",
|
||||
"branch": "$DEPLOY_BRANCH",
|
||||
"dirty": $DEPLOY_DIRTY,
|
||||
"deployed_at": "$DEPLOY_TS",
|
||||
"deployed_from": "$(hostname)",
|
||||
"target": "$TARGET_HOST"
|
||||
}
|
||||
MANIFEST_EOF
|
||||
|
||||
# Restart services
|
||||
echo "$(timestamp) Restarting services..."
|
||||
progress "Restarting services"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "sudo systemctl start archipelago && sudo systemctl restart nginx"
|
||||
|
||||
# Set up HTTPS for PWA installability (browsers require secure context)
|
||||
echo "$(timestamp) Setting up HTTPS for PWA install..."
|
||||
progress "Setting up HTTPS"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "sudo bash $TARGET_DIR/scripts/setup-https-dev.sh" 2>&1 | sed 's/^/ /' || true
|
||||
|
||||
if [ "$FRONTEND_ONLY" = true ]; then
|
||||
echo "$(timestamp) Skipping container rebuilds (--frontend-only)"
|
||||
echo " Skipping container rebuilds (--frontend-only)"
|
||||
else
|
||||
# Rebuild and recreate LND UI container (port 8081 so Launch from UI and http://host:8081 both work)
|
||||
echo "$(timestamp) Rebuilding LND UI..."
|
||||
progress "Rebuilding LND UI"
|
||||
if 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)..."
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
@@ -608,7 +704,7 @@ PYEOF
|
||||
fi
|
||||
|
||||
# Rebuild and recreate ElectrumX UI container (port 50002)
|
||||
echo "$(timestamp) Rebuilding ElectrumX UI..."
|
||||
progress "Rebuilding ElectrumX UI"
|
||||
if 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 ElectrumX UI container (port 50002, host network)..."
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
@@ -623,7 +719,7 @@ PYEOF
|
||||
|
||||
# Rebuild and recreate Bitcoin UI container (host network, port 8334 in nginx.conf)
|
||||
# Host network required: bitcoin-ui proxies Bitcoin RPC at 127.0.0.1:8332
|
||||
echo "$(timestamp) Rebuilding Bitcoin UI..."
|
||||
progress "Rebuilding Bitcoin UI"
|
||||
if ssh $SSH_OPTS "$TARGET_HOST" "cd $TARGET_DIR/docker/bitcoin-ui && (command -v podman >/dev/null 2>&1 && sudo podman build --no-cache -t bitcoin-ui:latest . || sudo docker build --no-cache -t bitcoin-ui:latest .)" 2>&1 | tail -12 | sed 's/^/ /'; then
|
||||
echo " Recreating Bitcoin UI container (port 8334, host network)..."
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
@@ -638,7 +734,7 @@ PYEOF
|
||||
|
||||
# Bitcoin Knots: required for Mempool, ElectrumX, BTCPay, Fedimint
|
||||
TARGET_IP="$(echo "$TARGET_HOST" | cut -d@ -f2)"
|
||||
echo "$(timestamp) Ensuring Bitcoin Knots..."
|
||||
progress "Ensuring Bitcoin Knots"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
DOCKER=podman
|
||||
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||
@@ -672,7 +768,7 @@ PYEOF
|
||||
" 2>&1 | sed 's/^/ /' || true
|
||||
|
||||
# Fix Mempool: clean duplicates, ensure full stack - mysql, backend (8999), frontend (4080)
|
||||
echo "$(timestamp) Fixing Mempool stack..."
|
||||
progress "Fixing Mempool stack"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
DOCKER=podman
|
||||
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||
@@ -770,7 +866,7 @@ PYEOF
|
||||
" 2>&1 | sed 's/^/ /' || true
|
||||
|
||||
# Fix BTCPay Server: requires PostgreSQL + NBXplorer (BTCPay needs NBXplorer for block indexing)
|
||||
echo "$(timestamp) Fixing BTCPay Server stack..."
|
||||
progress "Fixing BTCPay stack"
|
||||
TARGET_IP="$(echo "$TARGET_HOST" | cut -d@ -f2)"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
DOCKER=podman
|
||||
@@ -847,7 +943,7 @@ PYEOF
|
||||
" 2>&1 | sed 's/^/ /' || true
|
||||
|
||||
# Ensure Immich stack (postgres + redis + server) - creates if missing
|
||||
echo "$(timestamp) Ensuring Immich stack..."
|
||||
progress "Ensuring Immich stack"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
DOCKER=podman
|
||||
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||
@@ -889,7 +985,7 @@ PYEOF
|
||||
" 2>&1 | sed 's/^/ /' || true
|
||||
|
||||
# Tor: global hidden services - each service gets its own .onion address
|
||||
echo "$(timestamp) Setting up Tor..."
|
||||
progress "Setting up Tor"
|
||||
TARGET_IP="$(echo "$TARGET_HOST" | cut -d@ -f2)"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
DOCKER=podman
|
||||
@@ -983,7 +1079,7 @@ print("torrc generated with %d services" % (len(lines) // 3))
|
||||
|
||||
# Recreate Fedimint with FM_API_URL for Guardian UI (fixes "Api URL must be configured")
|
||||
section_start
|
||||
echo "$(timestamp) Fixing Fedimint API URL..."
|
||||
progress "Fixing Fedimint"
|
||||
TARGET_IP="$(echo "$TARGET_HOST" | cut -d@ -f2)"
|
||||
TIMEOUT_CMD=""
|
||||
command -v timeout >/dev/null 2>&1 && TIMEOUT_CMD="timeout 90"
|
||||
@@ -1055,7 +1151,7 @@ print("torrc generated with %d services" % (len(lines) // 3))
|
||||
section_end
|
||||
|
||||
# LND: Lightning Network Daemon (requires bitcoin-knots on archy-net)
|
||||
echo "$(timestamp) Ensuring LND..."
|
||||
progress "Ensuring LND"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
DOCKER=podman
|
||||
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||
@@ -1106,7 +1202,7 @@ LNDCONF
|
||||
' 2>&1 | sed 's/^/ /' || true
|
||||
|
||||
# Home Assistant
|
||||
echo "$(timestamp) Ensuring Home Assistant..."
|
||||
progress "Ensuring Home Assistant"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
DOCKER=podman
|
||||
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||
@@ -1129,7 +1225,7 @@ LNDCONF
|
||||
' 2>&1 | sed 's/^/ /' || true
|
||||
|
||||
# Grafana
|
||||
echo "$(timestamp) Ensuring Grafana..."
|
||||
progress "Ensuring Grafana"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
DOCKER=podman
|
||||
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||
@@ -1153,7 +1249,7 @@ LNDCONF
|
||||
' 2>&1 | sed 's/^/ /' || true
|
||||
|
||||
# Jellyfin
|
||||
echo "$(timestamp) Ensuring Jellyfin..."
|
||||
progress "Ensuring Jellyfin"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
DOCKER=podman
|
||||
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||
@@ -1176,7 +1272,7 @@ LNDCONF
|
||||
' 2>&1 | sed 's/^/ /' || true
|
||||
|
||||
# Vaultwarden
|
||||
echo "$(timestamp) Ensuring Vaultwarden..."
|
||||
progress "Ensuring Vaultwarden"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
DOCKER=podman
|
||||
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||
@@ -1198,7 +1294,7 @@ LNDCONF
|
||||
' 2>&1 | sed 's/^/ /' || true
|
||||
|
||||
# SearXNG (privacy search engine — used by AIUI web search)
|
||||
echo "$(timestamp) Ensuring SearXNG..."
|
||||
progress "Ensuring SearXNG"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
DOCKER=podman
|
||||
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||
@@ -1218,7 +1314,7 @@ LNDCONF
|
||||
' 2>&1 | sed 's/^/ /' || true
|
||||
|
||||
# Ollama (local LLM inference — used by AIUI)
|
||||
echo "$(timestamp) Ensuring Ollama..."
|
||||
progress "Ensuring Ollama"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
DOCKER=podman
|
||||
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||
@@ -1241,6 +1337,7 @@ LNDCONF
|
||||
fi # end FRONTEND_ONLY guard
|
||||
|
||||
# Ensure UFW allows forwarded traffic (required for podman container port access from LAN)
|
||||
progress "Fixing UFW forward policy"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
if grep -q "DEFAULT_FORWARD_POLICY=\"DROP\"" /etc/default/ufw 2>/dev/null; then
|
||||
sudo sed -i "s/DEFAULT_FORWARD_POLICY=\"DROP\"/DEFAULT_FORWARD_POLICY=\"ACCEPT\"/" /etc/default/ufw
|
||||
@@ -1249,38 +1346,73 @@ LNDCONF
|
||||
fi
|
||||
' 2>&1 | sed 's/^/ /' || true
|
||||
|
||||
# Fix IndeedHub for iframe + NIP-07: remove X-Frame-Options, inject nostr-provider.js
|
||||
# Fix IndeedHub for iframe + NIP-07: remove X-Frame-Options, inject nostr-provider.js,
|
||||
# resolve container IPs for nginx proxy (DNS resolver 127.0.0.11 is unreliable in podman)
|
||||
progress "Fixing IndeedHub for NIP-07"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
if sudo podman ps --format "{{.Names}}" 2>/dev/null | grep -q "^indeedhub$"; then
|
||||
CHANGED=false
|
||||
NETWORK=$(sudo podman inspect indeedhub --format "{{range \$k, \$v := .NetworkSettings.Networks}}{{\$k}}{{end}}" 2>/dev/null)
|
||||
|
||||
# Remove X-Frame-Options so iframe works
|
||||
if sudo podman exec indeedhub grep -q "X-Frame-Options" /etc/nginx/conf.d/default.conf 2>/dev/null; then
|
||||
sudo podman exec indeedhub sed -i "/X-Frame-Options/d" /etc/nginx/conf.d/default.conf
|
||||
CHANGED=true
|
||||
echo " Removed X-Frame-Options from IndeedHub"
|
||||
fi
|
||||
|
||||
# Inject nostr-provider.js for NIP-07 signing
|
||||
if ! sudo podman exec indeedhub test -f /usr/share/nginx/html/nostr-provider.js 2>/dev/null; then
|
||||
sudo podman cp /opt/archipelago/web-ui/nostr-provider.js indeedhub:/usr/share/nginx/html/nostr-provider.js 2>/dev/null
|
||||
echo " Copied nostr-provider.js into IndeedHub"
|
||||
fi
|
||||
|
||||
# Add nostr-provider.js + sub_filter to nginx config
|
||||
if ! sudo podman exec indeedhub grep -q "nostr-provider" /etc/nginx/conf.d/default.conf 2>/dev/null; then
|
||||
sudo podman exec indeedhub cat /etc/nginx/conf.d/default.conf > /tmp/ih-nginx.conf 2>/dev/null
|
||||
sed -i "/try_files.*index.html/i\\ sub_filter_once on;\n sub_filter '"'"'</head>'"'"' '"'"'<script src=\"/nostr-provider.js\"></script></head>'"'"';" /tmp/ih-nginx.conf
|
||||
# Add nostr-provider location block before sw.js block
|
||||
sed -i "/location = \/sw.js {/i\\ location = /nostr-provider.js {\n add_header Cache-Control \"no-cache, no-store, must-revalidate\";\n expires off;\n }\n" /tmp/ih-nginx.conf
|
||||
# Add sub_filter for nostr-provider injection
|
||||
sed -i "/try_files.*index.html/a\\ sub_filter_once on;\n sub_filter '"'"'</head>'"'"' '"'"'<script src=\"/nostr-provider.js\"></script></head>'"'"';" /tmp/ih-nginx.conf
|
||||
sudo podman cp /tmp/ih-nginx.conf indeedhub:/etc/nginx/conf.d/default.conf 2>/dev/null
|
||||
rm -f /tmp/ih-nginx.conf
|
||||
CHANGED=true
|
||||
echo " Injected nostr-provider.js into IndeedHub nginx"
|
||||
fi
|
||||
|
||||
# Replace DNS-based upstream resolution with hardcoded container IPs
|
||||
# (podman DNS resolver 127.0.0.11 is unreliable, causing 502 errors)
|
||||
API_IP=$(sudo podman inspect indeedhub-build_api_1 --format "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" 2>/dev/null)
|
||||
MINIO_IP=$(sudo podman inspect indeedhub-minio --format "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" 2>/dev/null)
|
||||
RELAY_IP=$(sudo podman inspect indeedhub-relay --format "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" 2>/dev/null)
|
||||
|
||||
if [ -n "$API_IP" ] && [ -n "$MINIO_IP" ] && [ -n "$RELAY_IP" ]; then
|
||||
sudo podman exec indeedhub cat /etc/nginx/conf.d/default.conf > /tmp/ih-nginx.conf 2>/dev/null
|
||||
# Remove DNS resolver lines and replace upstream variables with hardcoded IPs
|
||||
sed -i "s|resolver 127.0.0.11 valid=30s ipv6=off;||g" /tmp/ih-nginx.conf
|
||||
sed -i "s|set \$api_upstream http://api:4000;|set \$api_upstream http://$API_IP:4000;|g" /tmp/ih-nginx.conf
|
||||
sed -i "s|set \$minio_upstream http://minio:9000;|set \$minio_upstream http://$MINIO_IP:9000;|g" /tmp/ih-nginx.conf
|
||||
sed -i "s|set \$relay_upstream http://relay:8080;|set \$relay_upstream http://$RELAY_IP:8080;|g" /tmp/ih-nginx.conf
|
||||
sed -i "s|proxy_set_header Host \$host;|proxy_set_header Host \$http_host;|g" /tmp/ih-nginx.conf
|
||||
sudo podman cp /tmp/ih-nginx.conf indeedhub:/etc/nginx/conf.d/default.conf 2>/dev/null
|
||||
rm -f /tmp/ih-nginx.conf
|
||||
CHANGED=true
|
||||
echo " Patched IndeedHub nginx with container IPs (API=$API_IP MINIO=$MINIO_IP RELAY=$RELAY_IP)"
|
||||
fi
|
||||
|
||||
if [ "$CHANGED" = true ]; then
|
||||
sudo podman exec indeedhub nginx -s reload 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
' 2>&1 | sed 's/^/ /' || true
|
||||
|
||||
# Run container doctor — auto-fix common container health issues
|
||||
progress "Running container doctor"
|
||||
"$SCRIPT_DIR/container-doctor.sh" "$TARGET_HOST" 2>&1 | sed 's/^/ /' || true
|
||||
|
||||
# Post-deploy health check — wait up to 60s for server to come healthy
|
||||
echo ""
|
||||
echo "$(timestamp) 🩺 Post-deploy health check..."
|
||||
progress "Post-deploy health check"
|
||||
HEALTH_OK=false
|
||||
for i in $(seq 1 12); do
|
||||
POST_HEALTH=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 "http://$TARGET_IP_ONLY/health" 2>/dev/null || echo "000")
|
||||
@@ -1320,8 +1452,14 @@ LNDCONF
|
||||
|
||||
DEPLOY_END=$(date +%s)
|
||||
DEPLOY_ELAPSED=$((DEPLOY_END - DEPLOY_START))
|
||||
|
||||
# Append to local deploy history log (gitignored)
|
||||
DEPLOY_LOG="$PROJECT_DIR/scripts/deploy-history.log"
|
||||
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | $DEPLOY_BRANCH@$DEPLOY_COMMIT | dirty=$DEPLOY_DIRTY | target=$TARGET_HOST | ${DEPLOY_ELAPSED}s" >> "$DEPLOY_LOG"
|
||||
|
||||
echo ""
|
||||
echo "$(timestamp) ✅ Deployed to live system! (${DEPLOY_ELAPSED}s total)"
|
||||
echo " Commit: $DEPLOY_BRANCH @ $DEPLOY_COMMIT (dirty=$DEPLOY_DIRTY)"
|
||||
echo " Backend: $(ssh $SSH_OPTS "$TARGET_HOST" 'sudo systemctl is-active archipelago')"
|
||||
echo " Web UI: http://$TARGET_IP_ONLY"
|
||||
echo " PWA install: https://$TARGET_IP_ONLY (use HTTPS, accept cert once, then Install app)"
|
||||
|
||||
Reference in New Issue
Block a user