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)"
|
||||
|
||||
@@ -670,4 +670,13 @@ for c in bitcoin-knots lnd btcpay-server fedimint homeassistant grafana uptime-k
|
||||
done
|
||||
log "Post-boot validation: $RUNNING/$TOTAL core containers running"
|
||||
|
||||
# 12. 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
|
||||
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
|
||||
|
||||
log "First-boot container creation complete"
|
||||
|
||||
233
scripts/fix-indeedhub-containers.sh
Executable file
233
scripts/fix-indeedhub-containers.sh
Executable file
@@ -0,0 +1,233 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Fix corrupted IndeedHub containers + SearXNG
|
||||
# All images were exported as the same (wrong) image during multi-node deploy.
|
||||
# This script: stops broken containers, removes them, recreates with correct images.
|
||||
|
||||
echo "=== IndeedHub Container Fix Script ==="
|
||||
|
||||
# Detect node IP (Tailscale or LAN)
|
||||
NODE_IP=$(hostname -I | awk '{for(i=1;i<=NF;i++) if($i ~ /^100\./) print $i}')
|
||||
if [ -z "$NODE_IP" ]; then
|
||||
NODE_IP=$(hostname -I | awk '{print $1}')
|
||||
fi
|
||||
echo "Node IP: $NODE_IP"
|
||||
|
||||
NETWORK="indeedhub-build_indeedhub-network"
|
||||
|
||||
# Load custom images if tar exists
|
||||
if [ -f /tmp/indeedhub-images.tar ]; then
|
||||
echo "Loading custom images from tar..."
|
||||
sudo podman load < /tmp/indeedhub-images.tar 2>&1 | tail -5
|
||||
fi
|
||||
|
||||
# Verify correct images are available
|
||||
echo "Verifying images..."
|
||||
for img in "docker.io/library/redis:7-alpine" "docker.io/minio/minio:latest" "docker.io/library/postgres:16-alpine" "docker.io/scsibug/nostr-rs-relay:latest" "docker.io/searxng/searxng:latest" "localhost/indeedhub:latest" "localhost/indeedhub-build_api:latest" "localhost/indeedhub-build_ffmpeg-worker:latest"; do
|
||||
if ! sudo podman image exists "$img" 2>/dev/null; then
|
||||
echo "ERROR: Missing image $img"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo "All images verified."
|
||||
|
||||
# Ensure network exists
|
||||
if ! sudo podman network exists "$NETWORK" 2>/dev/null; then
|
||||
echo "Creating network $NETWORK..."
|
||||
sudo podman network create "$NETWORK" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Stop all affected containers
|
||||
echo "Stopping containers..."
|
||||
for c in indeedhub indeedhub-build_api_1 indeedhub-build_ffmpeg-worker_1 indeedhub-relay indeedhub-redis indeedhub-minio indeedhub-postgres searxng; do
|
||||
sudo podman stop "$c" 2>/dev/null || true
|
||||
done
|
||||
|
||||
# Remove all affected containers
|
||||
echo "Removing containers..."
|
||||
for c in indeedhub indeedhub-build_api_1 indeedhub-build_ffmpeg-worker_1 indeedhub-relay indeedhub-redis indeedhub-minio indeedhub-postgres searxng; do
|
||||
sudo podman rm -f "$c" 2>/dev/null || true
|
||||
done
|
||||
|
||||
# 1. PostgreSQL (must start first — others depend on it)
|
||||
echo "Creating postgres..."
|
||||
sudo podman run -d --name indeedhub-postgres \
|
||||
--restart unless-stopped \
|
||||
--network "$NETWORK" --network-alias postgres \
|
||||
-v indeedhub-postgres-data:/var/lib/postgresql/data \
|
||||
-e POSTGRES_USER=indeedhub \
|
||||
-e POSTGRES_PASSWORD=indeehhub-archy-2026 \
|
||||
-e POSTGRES_DB=indeedhub \
|
||||
docker.io/library/postgres:16-alpine
|
||||
|
||||
# Wait for postgres to be ready
|
||||
echo "Waiting for postgres..."
|
||||
for i in $(seq 1 15); do
|
||||
if sudo podman exec indeedhub-postgres pg_isready -U indeedhub 2>/dev/null; then
|
||||
echo "Postgres ready."
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# 2. Redis
|
||||
echo "Creating redis..."
|
||||
sudo podman run -d --name indeedhub-redis \
|
||||
--restart unless-stopped \
|
||||
--network "$NETWORK" --network-alias redis \
|
||||
-v indeedhub-redis-data:/data \
|
||||
docker.io/library/redis:7-alpine \
|
||||
redis-server --appendonly yes
|
||||
|
||||
# 3. MinIO
|
||||
echo "Creating minio..."
|
||||
sudo podman run -d --name indeedhub-minio \
|
||||
--restart unless-stopped \
|
||||
--network "$NETWORK" --network-alias minio \
|
||||
-v indeedhub-minio-data:/data \
|
||||
-e MINIO_ROOT_USER=indeeadmin \
|
||||
-e MINIO_ROOT_PASSWORD=indeeadmin2026 \
|
||||
docker.io/minio/minio:latest \
|
||||
server /data --console-address ":9001"
|
||||
|
||||
# 4. Nostr Relay
|
||||
echo "Creating relay..."
|
||||
sudo podman run -d --name indeedhub-relay \
|
||||
--restart unless-stopped \
|
||||
--network "$NETWORK" --network-alias relay \
|
||||
-v indeedhub-relay-data:/usr/src/app/db \
|
||||
docker.io/scsibug/nostr-rs-relay:latest
|
||||
|
||||
# 5. API
|
||||
echo "Creating api..."
|
||||
sudo podman run -d --name indeedhub-build_api_1 \
|
||||
--restart unless-stopped \
|
||||
--network "$NETWORK" --network-alias api \
|
||||
-e ENVIRONMENT=production \
|
||||
-e PORT=4000 \
|
||||
-e DOMAIN="$NODE_IP" \
|
||||
-e FRONTEND_URL="http://$NODE_IP" \
|
||||
-e DATABASE_HOST=postgres \
|
||||
-e DATABASE_PORT=5432 \
|
||||
-e DATABASE_USER=indeedhub \
|
||||
-e DATABASE_PASSWORD=indeehhub-archy-2026 \
|
||||
-e DATABASE_NAME=indeedhub \
|
||||
-e QUEUE_HOST=redis \
|
||||
-e QUEUE_PORT=6379 \
|
||||
-e "QUEUE_PASSWORD=" \
|
||||
-e S3_ENDPOINT=http://minio:9000 \
|
||||
-e AWS_REGION=us-east-1 \
|
||||
-e AWS_ACCESS_KEY=indeeadmin \
|
||||
-e AWS_SECRET_KEY=indeeadmin2026 \
|
||||
-e S3_PRIVATE_BUCKET_NAME=indeedhub-private \
|
||||
-e S3_PUBLIC_BUCKET_NAME=indeedhub-public \
|
||||
-e S3_PUBLIC_BUCKET_URL=/storage \
|
||||
-e "BTCPAY_URL=" \
|
||||
-e "BTCPAY_API_KEY=" \
|
||||
-e "BTCPAY_STORE_ID=" \
|
||||
-e "BTCPAY_WEBHOOK_SECRET=" \
|
||||
-e NOSTR_JWT_SECRET=archipelago-indeehhub-jwt-secret-2026 \
|
||||
-e NOSTR_JWT_EXPIRES_IN=7d \
|
||||
-e AES_MASTER_SECRET=0123456789abcdef0123456789abcdef \
|
||||
-e "ADMIN_API_KEY=" \
|
||||
-e NODE_OPTIONS=--max-old-space-size=1024 \
|
||||
--health-cmd "wget --no-verbose --tries=1 --spider http://localhost:4000/nostr-auth/health || exit 1" \
|
||||
--health-interval 60s \
|
||||
--health-timeout 30s \
|
||||
--health-retries 5 \
|
||||
--health-start-period 60s \
|
||||
localhost/indeedhub-build_api:latest \
|
||||
sh -c "echo 'Running database migrations...' && npx typeorm migration:run -d dist/database/ormconfig.js && echo 'Migrations complete.' && npm run start:prod"
|
||||
|
||||
# 6. FFmpeg Worker
|
||||
echo "Creating ffmpeg-worker..."
|
||||
sudo podman run -d --name indeedhub-build_ffmpeg-worker_1 \
|
||||
--restart unless-stopped \
|
||||
--network "$NETWORK" --network-alias ffmpeg-worker \
|
||||
-e ENVIRONMENT=production \
|
||||
-e DATABASE_HOST=postgres \
|
||||
-e DATABASE_PORT=5432 \
|
||||
-e DATABASE_USER=indeedhub \
|
||||
-e DATABASE_PASSWORD=indeehhub-archy-2026 \
|
||||
-e DATABASE_NAME=indeedhub \
|
||||
-e QUEUE_HOST=redis \
|
||||
-e QUEUE_PORT=6379 \
|
||||
-e "QUEUE_PASSWORD=" \
|
||||
-e S3_ENDPOINT=http://minio:9000 \
|
||||
-e AWS_REGION=us-east-1 \
|
||||
-e AWS_ACCESS_KEY=indeeadmin \
|
||||
-e AWS_SECRET_KEY=indeeadmin2026 \
|
||||
-e S3_PRIVATE_BUCKET_NAME=indeedhub-private \
|
||||
-e S3_PUBLIC_BUCKET_NAME=indeedhub-public \
|
||||
-e S3_PUBLIC_BUCKET_URL=/storage \
|
||||
-e AES_MASTER_SECRET=0123456789abcdef0123456789abcdef \
|
||||
localhost/indeedhub-build_ffmpeg-worker:latest
|
||||
|
||||
# 7. IndeedHub Frontend
|
||||
echo "Creating indeedhub frontend..."
|
||||
sudo podman run -d --name indeedhub \
|
||||
--restart unless-stopped \
|
||||
--network "$NETWORK" \
|
||||
-p 7777:7777 \
|
||||
--label "com.archipelago.app=indeedhub" \
|
||||
--label "com.archipelago.title=IndeedHub" \
|
||||
--label "com.archipelago.version=0.1.0" \
|
||||
--label "com.archipelago.category=media" \
|
||||
--label "com.archipelago.port=7777" \
|
||||
localhost/indeedhub:latest
|
||||
|
||||
# Fix IndeedHub for iframe: remove X-Frame-Options, inject nostr-provider, hardcode container IPs
|
||||
sleep 3
|
||||
if sudo podman ps --format '{{.Names}}' 2>/dev/null | grep -q "^indeedhub$"; then
|
||||
sudo podman exec indeedhub sed -i "/X-Frame-Options/d" /etc/nginx/conf.d/default.conf 2>/dev/null || true
|
||||
|
||||
# Inject nostr-provider.js if available
|
||||
if [ -f /opt/archipelago/web-ui/nostr-provider.js ]; then
|
||||
sudo podman cp /opt/archipelago/web-ui/nostr-provider.js indeedhub:/usr/share/nginx/html/nostr-provider.js 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Add nostr-provider location block + sub_filter
|
||||
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 "/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
|
||||
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 || true
|
||||
rm -f /tmp/ih-nginx.conf
|
||||
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
|
||||
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 || true
|
||||
rm -f /tmp/ih-nginx.conf
|
||||
echo "Patched IndeedHub nginx with container IPs (API=$API_IP MINIO=$MINIO_IP RELAY=$RELAY_IP)"
|
||||
fi
|
||||
|
||||
sudo podman exec indeedhub nginx -s reload 2>/dev/null || true
|
||||
echo "Applied IndeedHub iframe fix."
|
||||
fi
|
||||
|
||||
# 8. SearXNG (standalone — no cap-drop ALL, searxng needs write access to /etc/searxng/)
|
||||
echo "Creating searxng..."
|
||||
sudo podman run -d --name searxng \
|
||||
--restart unless-stopped \
|
||||
-p 8888:8080 \
|
||||
docker.io/searxng/searxng:latest
|
||||
|
||||
echo ""
|
||||
echo "=== Verifying container status ==="
|
||||
sleep 5
|
||||
sudo podman ps -a --filter name=indeedhub --filter name=searxng --format "table {{.Names}}\t{{.Status}}" 2>&1
|
||||
echo ""
|
||||
echo "=== FIX COMPLETE ==="
|
||||
Reference in New Issue
Block a user