#!/bin/bash # # Setup AIUI + Claude API proxy + FileBrowser on any Archipelago server # # Usage: # ./scripts/setup-aiui-server.sh # ./scripts/setup-aiui-server.sh archipelago@192.168.1.198 # ./scripts/setup-aiui-server.sh archipelago@192.168.1.228 # # What it does: # 1. Deploys AIUI files (from local build) # 2. Configures nginx Claude API proxy (direct to Anthropic with API key) # 3. Fixes FileBrowser container (removes read-only root if needed) # 4. Reloads nginx # # Prerequisites: # - AIUI must be built locally first: cd AIUI/packages/app && VITE_BASE_PATH=/aiui/ npx vite build # - SSH key access to target server set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" SSH_KEY="${ARCHIPELAGO_SSH_KEY:-$HOME/.ssh/archipelago-deploy}" SSH_OPTS="-o StrictHostKeyChecking=no -i $SSH_KEY" # Anthropic API key — used by all servers for AIUI Claude chat ANTHROPIC_API_KEY="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" TARGET_HOST="$1" if [ -z "$TARGET_HOST" ]; then echo "Usage: $0 " echo " e.g. $0 archipelago@192.168.1.198" exit 1 fi AIUI_DIST="$PROJECT_DIR/../AIUI/packages/app/dist" if [ ! -f "$AIUI_DIST/index.html" ]; then echo "ERROR: AIUI build not found at $AIUI_DIST" echo "Build it first: cd ../AIUI/packages/app && VITE_BASE_PATH=/aiui/ npx vite build" exit 1 fi timestamp() { echo "[$(date +%H:%M:%S)]"; } echo "╔════════════════════════════════════════════════════════════╗" echo "║ Archipelago AIUI + Claude API Setup ║" echo "║ Target: $TARGET_HOST" echo "╚════════════════════════════════════════════════════════════╝" # --- Step 1: Deploy AIUI files --- echo "" echo "$(timestamp) 📦 Deploying AIUI files..." # Check if rsync is available on remote if ssh $SSH_OPTS "$TARGET_HOST" "which rsync" &>/dev/null; then rsync -avz --delete -e "ssh $SSH_OPTS" "$AIUI_DIST/" "$TARGET_HOST:/opt/archipelago/web-ui/aiui/" 2>&1 | tail -3 else echo " rsync not available, using tar+scp..." TMPTAR=$(mktemp /tmp/aiui-dist-XXXXX.tar.gz) (cd "$AIUI_DIST" && tar czf "$TMPTAR" .) scp $SSH_OPTS "$TMPTAR" "$TARGET_HOST:/tmp/aiui-dist.tar.gz" ssh $SSH_OPTS "$TARGET_HOST" "sudo mkdir -p /opt/archipelago/web-ui/aiui && cd /opt/archipelago/web-ui/aiui && sudo tar xzf /tmp/aiui-dist.tar.gz --overwrite" rm -f "$TMPTAR" fi echo " AIUI deployed." # --- Step 2: Configure nginx Claude API proxy --- echo "" echo "$(timestamp) 🔧 Configuring nginx Claude API proxy..." # Create a Python script to patch nginx config cat << 'PYSCRIPT' > /tmp/patch-nginx-claude.py import sys import re API_KEY = sys.argv[1] with open("/etc/nginx/sites-available/archipelago") as f: content = f.read() # The new Claude API proxy block new_block = '''location /aiui/api/claude/ { if ($cookie_session = "") { return 401 '{"error":"Unauthorized"}'; } proxy_pass https://api.anthropic.com/; proxy_http_version 1.1; proxy_set_header Host api.anthropic.com; proxy_set_header x-api-key "''' + API_KEY + '''"; proxy_set_header anthropic-version "2023-06-01"; proxy_set_header anthropic-dangerous-direct-browser-access "true"; proxy_ssl_server_name on; proxy_set_header X-Real-IP $remote_addr; proxy_buffering off; proxy_cache off; proxy_connect_timeout 120s; proxy_read_timeout 300s; proxy_send_timeout 120s; }''' # Replace existing Claude API proxy blocks (handles both old proxy and direct patterns) pattern = r'location /aiui/api/claude/ \{[^}]*(?:\{[^}]*\}[^}]*)*\}' content = re.sub(pattern, new_block, content) with open("/etc/nginx/sites-available/archipelago", "w") as f: f.write(content) # Verify count = content.count("api.anthropic.com") print(f" Patched {count // 2} Claude API proxy blocks (HTTP + HTTPS)") PYSCRIPT scp $SSH_OPTS /tmp/patch-nginx-claude.py "$TARGET_HOST:/tmp/patch-nginx-claude.py" ssh $SSH_OPTS "$TARGET_HOST" "sudo python3 /tmp/patch-nginx-claude.py '$ANTHROPIC_API_KEY'" # Test and reload nginx echo " Testing nginx config..." ssh $SSH_OPTS "$TARGET_HOST" "sudo nginx -t 2>&1 && sudo systemctl reload nginx && echo ' Nginx reloaded OK'" || { echo " ERROR: nginx config test failed!" exit 1 } # --- Step 3: Fix FileBrowser container --- echo "" echo "$(timestamp) 📁 Checking FileBrowser..." FB_STATUS=$(ssh $SSH_OPTS "$TARGET_HOST" "podman inspect filebrowser 2>/dev/null | grep -oP '\"ReadonlyRootfs\":\s*\K\w+'" 2>/dev/null || echo "not_found") if [ "$FB_STATUS" = "true" ]; then echo " FileBrowser has read-only root — recreating..." ssh $SSH_OPTS "$TARGET_HOST" " podman stop filebrowser 2>/dev/null podman rm filebrowser 2>/dev/null sudo mkdir -p /var/lib/archipelago/filebrowser podman run -d --name filebrowser --restart=always \ -p 8083:80 \ -v /var/lib/archipelago/filebrowser:/srv \ filebrowser/filebrowser:v2.27.0 " 2>&1 | tail -2 echo " FileBrowser recreated." elif [ "$FB_STATUS" = "not_found" ]; then echo " FileBrowser not found — creating..." ssh $SSH_OPTS "$TARGET_HOST" " sudo mkdir -p /var/lib/archipelago/filebrowser podman run -d --name filebrowser --restart=always \ -p 8083:80 \ -v /var/lib/archipelago/filebrowser:/srv \ filebrowser/filebrowser:v2.27.0 " 2>&1 | tail -2 echo " FileBrowser created." else echo " FileBrowser OK (ReadonlyRootfs: $FB_STATUS)" fi # --- Step 4: Verify --- echo "" echo "$(timestamp) ✅ Verification..." ssh $SSH_OPTS "$TARGET_HOST" " echo \" AIUI index: \$(ls -la /opt/archipelago/web-ui/aiui/index.html 2>/dev/null | awk '{print \$6,\$7,\$8}')\" echo \" FileBrowser: \$(podman ps --format '{{.Names}} {{.Status}}' | grep filebrowser)\" echo \" Nginx: \$(systemctl is-active nginx)\" echo \" Backend: \$(systemctl is-active archipelago)\" echo \" Claude API test: \$(curl -s -o /dev/null -w '%{http_code}' -X POST http://localhost/aiui/api/claude/v1/messages -H 'Content-Type: application/json' -H 'Cookie: session=test' -d '{\"model\":\"claude-sonnet-4-20250514\",\"max_tokens\":5,\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}')\" " echo "" echo "$(timestamp) Done! Server configured." echo " Access: http://$(echo $TARGET_HOST | cut -d@ -f2)"