Files
explorer-monorepo/scripts/deploy-explorer-ai-to-vmid5000.sh
2026-03-27 17:11:16 -07:00

214 lines
8.6 KiB
Bash

#!/bin/bash
set -euo pipefail
VMID="${VMID:-5000}"
PROXMOX_HOST="${PROXMOX_HOST_R630_02:-192.168.11.12}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
BACKEND_DIR="$REPO_ROOT/explorer-monorepo/backend"
TMP_DIR="$(mktemp -d)"
JWT_SECRET_VALUE="${JWT_SECRET_VALUE:-}"
EXPLORER_AI_MODEL_VALUE="${EXPLORER_AI_MODEL_VALUE:-grok-3}"
EXPLORER_DATABASE_URL_VALUE="${EXPLORER_DATABASE_URL_VALUE:-}"
SECURE_AI_ENV_FILE="${SECURE_AI_ENV_FILE:-$HOME/.secure-secrets/explorer-ai.env}"
if [ -f "$SECURE_AI_ENV_FILE" ]; then
set -a
# Source the local secrets file so deploys do not depend on repo-stored API keys.
source "$SECURE_AI_ENV_FILE"
set +a
fi
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
echo "=========================================="
echo "Deploying Explorer AI Backend to VMID $VMID"
echo "=========================================="
echo "=== Step 1: Build explorer backend ==="
(
cd "$BACKEND_DIR"
go build -o "$TMP_DIR/explorer-config-api" ./api/rest/cmd
)
echo "✅ Backend built"
echo "=== Step 2: Prepare AI docs bundle ==="
mkdir -p "$TMP_DIR/explorer-ai-docs/docs/11-references" "$TMP_DIR/explorer-ai-docs/explorer-monorepo/docs"
cp "$REPO_ROOT/docs/11-references/ADDRESS_MATRIX_AND_STATUS.md" "$TMP_DIR/explorer-ai-docs/docs/11-references/"
cp "$REPO_ROOT/docs/11-references/LIQUIDITY_POOLS_MASTER_MAP.md" "$TMP_DIR/explorer-ai-docs/docs/11-references/"
cp "$REPO_ROOT/docs/11-references/DEPLOYED_TOKENS_BRIDGES_LPS_AND_ROUTING_STATUS.md" "$TMP_DIR/explorer-ai-docs/docs/11-references/"
cp "$REPO_ROOT/docs/11-references/EXPLORER_TOKEN_LIST_CROSSCHECK.md" "$TMP_DIR/explorer-ai-docs/docs/11-references/"
cp "$REPO_ROOT/explorer-monorepo/docs/EXPLORER_API_ACCESS.md" "$TMP_DIR/explorer-ai-docs/explorer-monorepo/docs/"
tar -C "$TMP_DIR" -czf "$TMP_DIR/explorer-ai-docs.tar.gz" explorer-ai-docs
echo "✅ Docs bundle prepared"
echo "=== Step 3: Upload artifacts ==="
scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$TMP_DIR/explorer-config-api" root@"$PROXMOX_HOST":/tmp/explorer-config-api
scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$TMP_DIR/explorer-ai-docs.tar.gz" root@"$PROXMOX_HOST":/tmp/explorer-ai-docs.tar.gz
echo "✅ Artifacts uploaded"
echo "=== Step 4: Install backend, refresh docs, and ensure env ==="
if [ -z "$JWT_SECRET_VALUE" ]; then
JWT_SECRET_VALUE="$(openssl rand -hex 32)"
fi
export JWT_SECRET_VALUE
export EXPLORER_AI_MODEL_VALUE
export XAI_API_KEY_VALUE="${XAI_API_KEY:-}"
export EXPLORER_DATABASE_URL_VALUE
ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@"$PROXMOX_HOST" \
"JWT_SECRET_VALUE='$JWT_SECRET_VALUE' EXPLORER_AI_MODEL_VALUE='$EXPLORER_AI_MODEL_VALUE' XAI_API_KEY_VALUE='$XAI_API_KEY_VALUE' EXPLORER_DATABASE_URL_VALUE='$EXPLORER_DATABASE_URL_VALUE' bash -s" <<'REMOTE'
set -euo pipefail
VMID=5000
DB_URL="$EXPLORER_DATABASE_URL_VALUE"
if [ -z "$DB_URL" ]; then
DB_CONTAINER_IP="$(pct exec "$VMID" -- docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' blockscout-postgres 2>/dev/null || true)"
if [ -n "$DB_CONTAINER_IP" ]; then
DB_URL="postgresql://blockscout:blockscout@${DB_CONTAINER_IP}:5432/blockscout?sslmode=disable"
fi
fi
pct exec "$VMID" -- bash -lc 'mkdir -p /opt/explorer-ai-docs /etc/systemd/system/explorer-config-api.service.d'
pct push "$VMID" /tmp/explorer-ai-docs.tar.gz /tmp/explorer-ai-docs.tar.gz --perms 0644
pct push "$VMID" /tmp/explorer-config-api /usr/local/bin/explorer-config-api.new --perms 0755
pct exec "$VMID" -- env \
DB_URL="$DB_URL" \
EXPLORER_AI_MODEL_VALUE="$EXPLORER_AI_MODEL_VALUE" \
JWT_SECRET_VALUE="$JWT_SECRET_VALUE" \
XAI_API_KEY_VALUE="$XAI_API_KEY_VALUE" \
bash -lc '
set -euo pipefail
rm -rf /opt/explorer-ai-docs/*
tar -xzf /tmp/explorer-ai-docs.tar.gz -C /opt
rm -f /tmp/explorer-ai-docs.tar.gz
mv /usr/local/bin/explorer-config-api.new /usr/local/bin/explorer-config-api
chmod 0755 /usr/local/bin/explorer-config-api
cat > /etc/systemd/system/explorer-config-api.service <<EOF
[Unit]
Description=Explorer Config API (MetaMask networks and token list)
After=network-online.target docker.service
Wants=network-online.target docker.service
[Service]
Type=simple
User=root
WorkingDirectory=/usr/local/bin
Environment=PORT=8081
Environment=CHAIN_ID=138
ExecStart=/usr/local/bin/explorer-config-api
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
EOF
cat > /etc/systemd/system/explorer-config-api.service.d/ai.conf <<EOF
[Service]
Environment=TOKEN_AGGREGATION_API_BASE=http://127.0.0.1:3001
Environment=EXPLORER_AI_WORKSPACE_ROOT=/opt/explorer-ai-docs
Environment=EXPLORER_AI_MODEL='"$EXPLORER_AI_MODEL_VALUE"'
EOF
cat > /etc/systemd/system/explorer-config-api.service.d/security.conf <<EOF
[Service]
Environment=JWT_SECRET='"$JWT_SECRET_VALUE"'
EOF
if [ -n "$DB_URL" ]; then
cat > /etc/systemd/system/explorer-config-api.service.d/database.conf <<EOF
[Service]
Environment=DATABASE_URL='"$DB_URL"'
EOF
chmod 600 /etc/systemd/system/explorer-config-api.service.d/database.conf
fi
rm -f /etc/systemd/system/explorer-config-api.service.d/openai.conf
if [ -n "'"$XAI_API_KEY_VALUE"'" ]; then
cat > /etc/systemd/system/explorer-config-api.service.d/xai.conf <<EOF
[Service]
Environment=XAI_API_KEY='"$XAI_API_KEY_VALUE"'
EOF
chmod 600 /etc/systemd/system/explorer-config-api.service.d/xai.conf
else
rm -f /etc/systemd/system/explorer-config-api.service.d/xai.conf
fi
systemctl daemon-reload
systemctl restart explorer-config-api
sleep 2
systemctl is-active explorer-config-api
'
REMOTE
echo "✅ Backend installed and service restarted"
echo "=== Step 5: Normalize nginx explorer backend prefix ==="
ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@"$PROXMOX_HOST" "VMID='$VMID' bash -s" <<'REMOTE'
set -euo pipefail
pct exec "$VMID" -- python3 - <<'PY'
from pathlib import Path
import re
path = Path('/etc/nginx/sites-available/blockscout')
text = path.read_text()
explorer_block = ''' # Explorer backend API (auth, features, AI, explorer-owned v1 helpers)
location /explorer-api/v1/ {
proxy_pass http://127.0.0.1:8081/api/v1/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
}
'''
escaped_explorer_block = explorer_block.replace('$', '\\$')
if escaped_explorer_block in text:
text = text.replace(escaped_explorer_block, explorer_block)
legacy_patterns = [
r"\n\s*# Explorer AI endpoints on the explorer backend service \(HTTP\)\n\s*location /api/v1/ai/ \{.*?\n\s*\}\n",
r"\n\s*location = /api/v1/features \{.*?\n\s*\}\n",
r"\n\s*# Explorer AI endpoints on the explorer backend service\n\s*location /api/v1/ai/ \{.*?\n\s*\}\n",
]
for pattern in legacy_patterns:
text = re.sub(pattern, "\n", text, flags=re.S)
http_needle = ' # Blockscout API endpoint - MUST come before the redirect location\n'
legacy_http_needle = ' # API endpoint - MUST come before the redirect location\n'
if explorer_block not in text:
if http_needle in text:
text = text.replace(http_needle, explorer_block + http_needle, 1)
elif legacy_http_needle in text:
text = text.replace(legacy_http_needle, explorer_block + ' # Blockscout API endpoint - MUST come before the redirect location\n', 1)
https_needle = ' # Token-aggregation API for the explorer SPA live route-tree and pool intelligence.\n'
if explorer_block not in text[text.find('# HTTPS server - Blockscout Explorer'):]:
text = text.replace(' # Token-aggregation API at /api/v1/ for the Snap site. Service runs on port 3001.\n location /api/v1/ {\n proxy_pass http://127.0.0.1:3001/api/v1/;\n proxy_http_version 1.1;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_read_timeout 60s;\n add_header Access-Control-Allow-Origin *;\n }\n\n', explorer_block, 1)
path.write_text(text)
PY
pct exec "$VMID" -- bash -lc 'nginx -t && nginx -s reload'
REMOTE
echo "✅ Nginx normalized"
echo "=== Step 6: Verify core explorer AI routes ==="
curl -fsS "https://explorer.d-bis.org/explorer-api/v1/features" >/dev/null
curl -fsS "https://explorer.d-bis.org/explorer-api/v1/ai/context?q=cUSDT" >/dev/null
echo "✅ Explorer AI routes respond publicly"
echo ""
echo "Deployment complete."