Harden explorer AI runtime and API ownership
This commit is contained in:
@@ -36,6 +36,8 @@ checks = [
|
||||
"/api/v2/stats",
|
||||
"/api/config/token-list",
|
||||
"/api/config/networks",
|
||||
"/explorer-api/v1/features",
|
||||
"/explorer-api/v1/ai/context?q=cUSDT",
|
||||
"/token-aggregation/api/v1/routes/tree?chainId=138&tokenIn=0x93E66202A11B1772E55407B32B44e5Cd8eda7f22&tokenOut=0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1&amountIn=1000000",
|
||||
"/token-aggregation/api/v1/routes/matrix",
|
||||
"/token-aggregation/api/v1/routes/ingestion?fromChainId=138&routeType=swap",
|
||||
|
||||
172
scripts/deploy-explorer-ai-to-vmid5000.sh
Normal file
172
scripts/deploy-explorer-ai-to-vmid5000.sh
Normal file
@@ -0,0 +1,172 @@
|
||||
#!/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:-gpt-5.4-mini}"
|
||||
EXPLORER_DATABASE_URL_VALUE="${EXPLORER_DATABASE_URL_VALUE:-}"
|
||||
|
||||
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 OPENAI_API_KEY_VALUE="${OPENAI_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' OPENAI_API_KEY_VALUE='$OPENAI_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" \
|
||||
OPENAI_API_KEY_VALUE="$OPENAI_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.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
|
||||
|
||||
if [ -n "'"$OPENAI_API_KEY_VALUE"'" ]; then
|
||||
cat > /etc/systemd/system/explorer-config-api.service.d/openai.conf <<EOF
|
||||
[Service]
|
||||
Environment=OPENAI_API_KEY='"$OPENAI_API_KEY_VALUE"'
|
||||
EOF
|
||||
chmod 600 /etc/systemd/system/explorer-config-api.service.d/openai.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
|
||||
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)
|
||||
|
||||
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."
|
||||
@@ -32,7 +32,21 @@ server {
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# API endpoint - MUST come before the redirect location
|
||||
# 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";
|
||||
}
|
||||
|
||||
# Blockscout API endpoint - MUST come before the redirect location
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:4000;
|
||||
proxy_http_version 1.1;
|
||||
@@ -199,9 +213,9 @@ server {
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Token-aggregation API at /api/v1/ for the Snap site. Service runs on port 3001.
|
||||
location /api/v1/ {
|
||||
proxy_pass http://127.0.0.1:3001/api/v1/;
|
||||
# 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;
|
||||
@@ -209,6 +223,8 @@ server {
|
||||
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";
|
||||
}
|
||||
|
||||
# Token-aggregation API for the explorer SPA live route-tree and pool intelligence.
|
||||
|
||||
Reference in New Issue
Block a user