feat: demo deployment with AIUI chat, SSH key auth, Quick Start fix
- Add AIUI pre-built dist to demo/ for Portainer deployment - Add nginx-demo.conf with Claude API proxy (envsubst for API key) - Add docker-entrypoint.sh for runtime API key injection - Update Dockerfile.web to include AIUI and Claude proxy - Update docker-compose.demo.yml with ANTHROPIC_API_KEY env var - Switch deploy script from sshpass to SSH key auth - Fix Quick Start Goals animating before other cards (stagger 5, opacity guard) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -21,10 +21,8 @@ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
# 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"
|
||||
SSH_KEY="${ARCHIPELAGO_SSH_KEY:-$HOME/.ssh/archipelago-deploy}"
|
||||
SSH_OPTS="-o StrictHostKeyChecking=no -i $SSH_KEY"
|
||||
|
||||
DEPLOY_START=$(date +%s)
|
||||
timestamp() { echo "[$(date +%H:%M:%S)]"; }
|
||||
@@ -59,7 +57,7 @@ section_end() {
|
||||
|
||||
# SSH connectivity pre-flight check
|
||||
echo "$(timestamp) Checking SSH connectivity..."
|
||||
if ! sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS -o ConnectTimeout=5 "$TARGET_HOST" "echo ok" >/dev/null 2>&1; then
|
||||
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."
|
||||
exit 1
|
||||
@@ -73,10 +71,10 @@ if [ "$BOTH" = true ]; then
|
||||
"$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 '
|
||||
scp $SSH_OPTS archipelago@192.168.1.228:$TARGET_DIR/core/target/release/archipelago /tmp/archipelago-both 2>/dev/null || true
|
||||
scp $SSH_OPTS /tmp/archipelago-both archipelago@192.168.1.198:/tmp/archipelago-new
|
||||
ssh $SSH_OPTS archipelago@192.168.1.228 "cd $TARGET_DIR && tar cf - web/dist/neode-ui 2>/dev/null" | ssh $SSH_OPTS archipelago@192.168.1.198 "mkdir -p /tmp/web-deploy && cd /tmp/web-deploy && tar xf -"
|
||||
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
|
||||
@@ -95,7 +93,7 @@ fi
|
||||
# Sync code
|
||||
section_start
|
||||
echo "$(timestamp) 📦 Syncing code..."
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" rsync -avz --delete \
|
||||
rsync -avz --delete \
|
||||
-e "ssh $SSH_OPTS" \
|
||||
--exclude 'node_modules' \
|
||||
--exclude 'target' \
|
||||
@@ -119,16 +117,16 @@ echo "$(timestamp) 🔨 Building on target..."
|
||||
# Frontend
|
||||
section_start
|
||||
echo "$(timestamp) Building frontend (vue-tsc + vite)..."
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "cd $TARGET_DIR/neode-ui && npm install --silent && npm run build" 2>&1 | sed 's/^/ /'
|
||||
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)"
|
||||
elif sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "source ~/.cargo/env 2>/dev/null && command -v cargo" >/dev/null 2>&1; then
|
||||
elif ssh $SSH_OPTS "$TARGET_HOST" "source ~/.cargo/env 2>/dev/null && command -v cargo" >/dev/null 2>&1; then
|
||||
section_start
|
||||
echo "$(timestamp) Building backend (Rust release — this takes 1-2 min)..."
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "source ~/.cargo/env && cd $TARGET_DIR/core && cargo build --release 2>&1" | sed 's/^/ /'
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "source ~/.cargo/env && cd $TARGET_DIR/core && cargo build --release 2>&1" | sed 's/^/ /'
|
||||
section_end
|
||||
else
|
||||
echo " ⚠️ Rust not installed on target, skipping backend build"
|
||||
@@ -141,17 +139,17 @@ 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)"
|
||||
elif sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "[ -f $TARGET_DIR/core/target/release/archipelago ]" 2>/dev/null; then
|
||||
elif ssh $SSH_OPTS "$TARGET_HOST" "[ -f $TARGET_DIR/core/target/release/archipelago ]" 2>/dev/null; then
|
||||
echo "$(timestamp) 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/"
|
||||
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..."
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "sudo find /opt/archipelago/web-ui -mindepth 1 -maxdepth 1 ! -name 'aiui' ! -name 'claude-login.html' -exec rm -rf {} +"
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "sudo cp -rf $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"
|
||||
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)
|
||||
AIUI_DIR="$PROJECT_DIR/../AIUI"
|
||||
@@ -165,11 +163,11 @@ if [ "$LIVE" = true ]; then
|
||||
cd "$PROJECT_DIR"
|
||||
if [ "$AIUI_BUILD_OK" = true ] && [ -d "$AIUI_DIST" ] && [ -f "$AIUI_DIST/index.html" ]; then
|
||||
echo "$(timestamp) Deploying AIUI..."
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "sudo mkdir -p /opt/archipelago/web-ui/aiui"
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "sudo rm -rf /opt/archipelago/web-ui/aiui/*"
|
||||
cd "$AIUI_DIST" && tar cf - . | sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "sudo tar xf - -C /opt/archipelago/web-ui/aiui/"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "sudo mkdir -p /opt/archipelago/web-ui/aiui"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "sudo rm -rf /opt/archipelago/web-ui/aiui/*"
|
||||
cd "$AIUI_DIST" && tar cf - . | ssh $SSH_OPTS "$TARGET_HOST" "sudo tar xf - -C /opt/archipelago/web-ui/aiui/"
|
||||
cd "$PROJECT_DIR"
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "sudo chown -R 1000:1000 /opt/archipelago/web-ui/aiui && sudo chmod 755 /opt/archipelago/web-ui/aiui && sudo find /opt/archipelago/web-ui/aiui -type d -exec chmod 755 {} \;"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "sudo chown -R 1000:1000 /opt/archipelago/web-ui/aiui && sudo chmod 755 /opt/archipelago/web-ui/aiui && sudo find /opt/archipelago/web-ui/aiui -type d -exec chmod 755 {} \;"
|
||||
else
|
||||
echo "$(timestamp) ⚠️ AIUI build failed — keeping existing AIUI on server"
|
||||
fi
|
||||
@@ -181,8 +179,8 @@ if [ "$LIVE" = true ]; then
|
||||
NGINX_CFG="$PROJECT_DIR/image-recipe/configs/nginx-archipelago.conf"
|
||||
if [ -f "$NGINX_CFG" ]; then
|
||||
echo "$(timestamp) Syncing nginx config..."
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" scp $SSH_OPTS "$NGINX_CFG" "$TARGET_HOST:/tmp/nginx-archipelago.conf" 2>/dev/null || true
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
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
|
||||
sudo nginx -t 2>&1 && echo " nginx config OK" || echo " ⚠️ nginx config test failed, keeping old config"
|
||||
rm -f /tmp/nginx-archipelago.conf
|
||||
@@ -191,20 +189,20 @@ if [ "$LIVE" = true ]; then
|
||||
|
||||
# Restart services
|
||||
echo "$(timestamp) Restarting services..."
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "sudo systemctl start archipelago && sudo systemctl restart nginx"
|
||||
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..."
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "sudo bash $TARGET_DIR/scripts/setup-https-dev.sh" 2>&1 | sed 's/^/ /' || true
|
||||
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)"
|
||||
else
|
||||
# Rebuild and recreate LND UI container (port 8081 so Launch from UI and http://host:8081 both work)
|
||||
echo "$(timestamp) 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
|
||||
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)..."
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
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
|
||||
@@ -216,9 +214,9 @@ if [ "$LIVE" = true ]; then
|
||||
|
||||
# Rebuild and recreate Electrs UI container (port 50002)
|
||||
echo "$(timestamp) 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
|
||||
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 Electrs UI container (port 50002, host network)..."
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" '
|
||||
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
|
||||
@@ -231,7 +229,7 @@ if [ "$LIVE" = true ]; then
|
||||
# Bitcoin Knots: required for Mempool, Electrs, BTCPay, Fedimint
|
||||
TARGET_IP="$(echo "$TARGET_HOST" | cut -d@ -f2)"
|
||||
echo "$(timestamp) Ensuring Bitcoin Knots..."
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
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
|
||||
@@ -255,7 +253,7 @@ if [ "$LIVE" = true ]; then
|
||||
|
||||
# Fix Mempool: clean duplicates, ensure full stack - mysql, backend (8999), frontend (4080)
|
||||
echo "$(timestamp) Fixing Mempool stack..."
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
DOCKER=podman
|
||||
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||
TARGET_IP='$TARGET_IP'
|
||||
@@ -360,7 +358,7 @@ if [ "$LIVE" = true ]; then
|
||||
# Fix BTCPay Server: requires PostgreSQL + NBXplorer (BTCPay needs NBXplorer for block indexing)
|
||||
echo "$(timestamp) Fixing BTCPay Server stack..."
|
||||
TARGET_IP="$(echo "$TARGET_HOST" | cut -d@ -f2)"
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
DOCKER=podman
|
||||
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||
TARGET_IP='$TARGET_IP'
|
||||
@@ -434,7 +432,7 @@ if [ "$LIVE" = true ]; then
|
||||
|
||||
# Ensure Immich stack (postgres + redis + server) - creates if missing
|
||||
echo "$(timestamp) Ensuring Immich stack..."
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
DOCKER=podman
|
||||
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||
# Remove old single-container 'immich' if present (wrong port mapping, conflicts with immich_server)
|
||||
@@ -477,7 +475,7 @@ if [ "$LIVE" = true ]; then
|
||||
# Tor: global hidden services - each service gets its own .onion address
|
||||
echo "$(timestamp) Setting up Tor..."
|
||||
TARGET_IP="$(echo "$TARGET_HOST" | cut -d@ -f2)"
|
||||
sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
DOCKER=podman
|
||||
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||
TARGET_IP='$TARGET_IP'
|
||||
@@ -529,7 +527,7 @@ if [ "$LIVE" = true ]; then
|
||||
|
||||
# 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" "
|
||||
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
|
||||
@@ -547,7 +545,7 @@ if [ "$LIVE" = true ]; then
|
||||
TIMEOUT_CMD=""
|
||||
command -v timeout >/dev/null 2>&1 && TIMEOUT_CMD="timeout 90"
|
||||
command -v gtimeout >/dev/null 2>&1 && TIMEOUT_CMD="gtimeout 90"
|
||||
($TIMEOUT_CMD sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
($TIMEOUT_CMD 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
|
||||
@@ -579,7 +577,7 @@ if [ "$LIVE" = true ]; then
|
||||
DEPLOY_ELAPSED=$((DEPLOY_END - DEPLOY_START))
|
||||
echo ""
|
||||
echo "$(timestamp) ✅ Deployed to live system! (${DEPLOY_ELAPSED}s total)"
|
||||
echo " Backend: $(sshpass -p "$ARCHIPELAGO_PASSWORD" ssh $SSH_OPTS "$TARGET_HOST" 'sudo systemctl is-active archipelago')"
|
||||
echo " Backend: $(ssh $SSH_OPTS "$TARGET_HOST" 'sudo systemctl is-active archipelago')"
|
||||
echo " Web UI: http://$(echo $TARGET_HOST | cut -d@ -f2)"
|
||||
echo " PWA install: https://$(echo $TARGET_HOST | cut -d@ -f2) (use HTTPS, accept cert once, then Install app)"
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user