393 lines
15 KiB
Bash
Executable File
393 lines
15 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Fix nginx to serve custom frontend from /var/www/html/
|
|
# Run this in VMID 5000
|
|
|
|
set -euo pipefail
|
|
|
|
CONFIG_FILE="/etc/nginx/sites-available/blockscout"
|
|
|
|
echo "=========================================="
|
|
echo "Updating Nginx to Serve Custom Frontend"
|
|
echo "=========================================="
|
|
echo ""
|
|
|
|
# Step 1: Backup current config
|
|
echo "=== Step 1: Backing up nginx config ==="
|
|
cp "$CONFIG_FILE" "${CONFIG_FILE}.backup.$(date +%Y%m%d_%H%M%S)"
|
|
echo "✅ Backup created"
|
|
echo ""
|
|
|
|
# Step 2: Create new config that serves custom frontend
|
|
echo "=== Step 2: Creating new nginx configuration ==="
|
|
cat > "$CONFIG_FILE" << 'NGINX_EOF'
|
|
# HTTP server
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name explorer.d-bis.org 192.168.11.140;
|
|
|
|
location /.well-known/acme-challenge/ {
|
|
root /var/www/html;
|
|
try_files $uri =404;
|
|
}
|
|
|
|
# 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;
|
|
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 300s;
|
|
proxy_connect_timeout 75s;
|
|
add_header Access-Control-Allow-Origin *;
|
|
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
|
|
add_header Access-Control-Allow-Headers "Content-Type";
|
|
}
|
|
|
|
# Token-aggregation API for live route-tree, quotes, and market data
|
|
location /token-aggregation/api/v1/ {
|
|
proxy_pass http://127.0.0.1:3001/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";
|
|
}
|
|
|
|
# Explorer config API (token list, networks) - serve from /var/www/html/config/
|
|
location = /api/config/token-list {
|
|
default_type application/json;
|
|
add_header Access-Control-Allow-Origin *;
|
|
add_header Cache-Control "public, max-age=3600";
|
|
alias /var/www/html/config/DUAL_CHAIN_TOKEN_LIST.tokenlist.json;
|
|
}
|
|
location = /api/config/networks {
|
|
default_type application/json;
|
|
add_header Access-Control-Allow-Origin *;
|
|
add_header Cache-Control "public, max-age=3600";
|
|
alias /var/www/html/config/DUAL_CHAIN_NETWORKS.json;
|
|
}
|
|
|
|
location /health {
|
|
access_log off;
|
|
proxy_pass http://127.0.0.1:4000/api/v2/status;
|
|
proxy_set_header Host $host;
|
|
add_header Content-Type application/json;
|
|
}
|
|
|
|
# When NPMplus forwards HTTPS to this port as HTTP, do NOT redirect to HTTPS (avoids ERR_TOO_MANY_REDIRECTS)
|
|
set $redirect_http_to_https 1;
|
|
if ($http_x_forwarded_proto = "https") { set $redirect_http_to_https 0; }
|
|
if ($http_x_forwarded_proto = "HTTPS") { set $redirect_http_to_https 0; }
|
|
|
|
# Snap companion (must be before catch-all so /snap/ is served from disk)
|
|
location = /snap { rewrite ^ /snap/ last; }
|
|
location /snap/ {
|
|
alias /var/www/html/snap/;
|
|
try_files $uri $uri/ /snap/index.html;
|
|
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
|
}
|
|
|
|
# Serve custom frontend for root path (no-cache so fixes show after refresh)
|
|
# CSP with unsafe-eval required by ethers.js v5 (NPM proxies to port 80)
|
|
location = / {
|
|
root /var/www/html;
|
|
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://unpkg.com https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; img-src 'self' data: https:; font-src 'self' https://cdnjs.cloudflare.com; connect-src 'self' https://explorer.d-bis.org wss://explorer.d-bis.org https://rpc-http-pub.d-bis.org wss://rpc-ws-pub.d-bis.org http://192.168.11.221:8545 ws://192.168.11.221:8546;" always;
|
|
try_files /index.html =404;
|
|
}
|
|
|
|
location = /favicon.ico {
|
|
root /var/www/html;
|
|
try_files /favicon.ico =404;
|
|
add_header Cache-Control "public, max-age=86400";
|
|
}
|
|
location = /apple-touch-icon.png {
|
|
root /var/www/html;
|
|
try_files /apple-touch-icon.png =404;
|
|
add_header Cache-Control "public, max-age=86400";
|
|
}
|
|
|
|
# Serve static assets
|
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
|
root /var/www/html;
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable";
|
|
}
|
|
|
|
# SPA paths on HTTP (for internal/LAN tests) - serve index.html before redirect
|
|
location ~ ^/(address|tx|block|token|tokens|blocks|transactions|bridge|weth|liquidity|watchlist|nft|home|analytics|operator)(/|$) {
|
|
root /var/www/html;
|
|
try_files /index.html =404;
|
|
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
|
}
|
|
|
|
# All other requests: redirect to HTTPS only when not already behind HTTPS proxy
|
|
location / {
|
|
if ($redirect_http_to_https = 1) { return 301 https://$host$request_uri; }
|
|
root /var/www/html;
|
|
try_files $uri $uri/ /index.html;
|
|
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
|
}
|
|
}
|
|
|
|
# HTTPS server - Blockscout Explorer
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name explorer.d-bis.org 192.168.11.140;
|
|
|
|
# SSL configuration
|
|
ssl_certificate /etc/letsencrypt/live/explorer.d-bis.org/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/explorer.d-bis.org/privkey.pem;
|
|
|
|
ssl_protocols TLSv1.2 TLSv1.3;
|
|
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
|
|
ssl_prefer_server_ciphers off;
|
|
ssl_session_cache shared:SSL:10m;
|
|
ssl_session_timeout 10m;
|
|
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header X-XSS-Protection "1; mode=block" always;
|
|
|
|
access_log /var/log/nginx/blockscout-access.log;
|
|
error_log /var/log/nginx/blockscout-error.log;
|
|
|
|
# Serve custom frontend for root path (no-cache so fixes show after refresh)
|
|
location = / {
|
|
root /var/www/html;
|
|
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
|
try_files /index.html =404;
|
|
}
|
|
|
|
# Chain 138 MetaMask Snap companion site (SPA at /snap/)
|
|
# /snap (no trailing slash) -> internal redirect so client gets 200 with content
|
|
location = /snap {
|
|
rewrite ^ /snap/ last;
|
|
}
|
|
location /snap/ {
|
|
alias /var/www/html/snap/;
|
|
try_files $uri $uri/ /snap/index.html;
|
|
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
|
}
|
|
|
|
# Icons (exact match to avoid 404s)
|
|
location = /favicon.ico {
|
|
root /var/www/html;
|
|
try_files /favicon.ico =404;
|
|
add_header Cache-Control "public, max-age=86400";
|
|
}
|
|
location = /apple-touch-icon.png {
|
|
root /var/www/html;
|
|
try_files /apple-touch-icon.png =404;
|
|
add_header Cache-Control "public, max-age=86400";
|
|
}
|
|
|
|
# Serve static assets
|
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
|
root /var/www/html;
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable";
|
|
}
|
|
|
|
# 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";
|
|
}
|
|
|
|
# Token-aggregation API for the explorer SPA live route-tree and pool intelligence.
|
|
location /token-aggregation/api/v1/ {
|
|
proxy_pass http://127.0.0.1:3001/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 *;
|
|
}
|
|
|
|
# Explorer config API (token list, networks) - serve from /var/www/html/config/
|
|
location = /api/config/token-list {
|
|
default_type application/json;
|
|
add_header Access-Control-Allow-Origin *;
|
|
add_header Cache-Control "public, max-age=3600";
|
|
alias /var/www/html/config/DUAL_CHAIN_TOKEN_LIST.tokenlist.json;
|
|
}
|
|
location = /api/config/networks {
|
|
default_type application/json;
|
|
add_header Access-Control-Allow-Origin *;
|
|
add_header Cache-Control "public, max-age=3600";
|
|
alias /var/www/html/config/DUAL_CHAIN_NETWORKS.json;
|
|
}
|
|
|
|
# API endpoint (for Blockscout API)
|
|
location /api/ {
|
|
proxy_pass http://127.0.0.1:4000;
|
|
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 300s;
|
|
proxy_connect_timeout 75s;
|
|
add_header Access-Control-Allow-Origin *;
|
|
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
|
|
add_header Access-Control-Allow-Headers "Content-Type";
|
|
}
|
|
|
|
location /health {
|
|
access_log off;
|
|
proxy_pass http://127.0.0.1:4000/api/v2/status;
|
|
proxy_set_header Host $host;
|
|
add_header Content-Type application/json;
|
|
}
|
|
|
|
# Proxy Blockscout UI paths (if needed)
|
|
location /blockscout/ {
|
|
proxy_pass http://127.0.0.1:4000/;
|
|
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 300s;
|
|
proxy_connect_timeout 75s;
|
|
}
|
|
|
|
# SPA paths: /address, /tx, /block, /token, /tokens, /blocks, /transactions, /bridge, /weth, /liquidity, /watchlist, /nft, /home, /analytics, /operator
|
|
# Must serve index.html so path-based routing works (regex takes precedence over proxy)
|
|
location ~ ^/(address|tx|block|token|tokens|blocks|transactions|bridge|weth|liquidity|watchlist|nft|home|analytics|operator)(/|$) {
|
|
root /var/www/html;
|
|
try_files /index.html =404;
|
|
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
|
}
|
|
|
|
# All other paths serve custom frontend (SPA fallback via try_files)
|
|
location / {
|
|
root /var/www/html;
|
|
try_files $uri $uri/ /index.html;
|
|
}
|
|
}
|
|
|
|
map $http_upgrade $connection_upgrade {
|
|
default upgrade;
|
|
'' close;
|
|
}
|
|
NGINX_EOF
|
|
|
|
echo "✅ Configuration updated"
|
|
echo ""
|
|
|
|
# Step 3: Handle SSL certs if missing
|
|
echo "=== Step 3: Checking SSL certificates ==="
|
|
if [ ! -f /etc/letsencrypt/live/explorer.d-bis.org/fullchain.pem ]; then
|
|
echo "⚠️ Let's Encrypt certificate not found, creating self-signed..."
|
|
mkdir -p /etc/nginx/ssl
|
|
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
|
-keyout /etc/nginx/ssl/blockscout.key \
|
|
-out /etc/nginx/ssl/blockscout.crt \
|
|
-subj "/CN=explorer.d-bis.org" 2>/dev/null
|
|
sed -i 's|ssl_certificate /etc/letsencrypt/live/explorer.d-bis.org/fullchain.pem;|ssl_certificate /etc/nginx/ssl/blockscout.crt;|' "$CONFIG_FILE"
|
|
sed -i 's|ssl_certificate_key /etc/letsencrypt/live/explorer.d-bis.org/privkey.pem;|ssl_certificate_key /etc/nginx/ssl/blockscout.key;|' "$CONFIG_FILE"
|
|
echo "✅ Self-signed certificate created"
|
|
else
|
|
echo "✅ Let's Encrypt certificate found"
|
|
fi
|
|
echo ""
|
|
|
|
# Step 4: Ensure /var/www/html exists and has correct permissions
|
|
echo "=== Step 4: Preparing frontend directory ==="
|
|
mkdir -p /var/www/html
|
|
chown -R www-data:www-data /var/www/html 2>/dev/null || true
|
|
echo "✅ Directory prepared"
|
|
echo ""
|
|
|
|
# Step 5: Test and restart nginx
|
|
echo "=== Step 5: Testing and restarting nginx ==="
|
|
if nginx -t; then
|
|
echo "✅ Configuration valid"
|
|
systemctl restart nginx
|
|
echo "✅ Nginx restarted"
|
|
else
|
|
echo "❌ Configuration has errors"
|
|
echo "Restoring backup..."
|
|
cp "${CONFIG_FILE}.backup."* "$CONFIG_FILE" 2>/dev/null || true
|
|
exit 1
|
|
fi
|
|
echo ""
|
|
|
|
# Step 6: Verify
|
|
echo "=== Step 6: Verifying deployment ==="
|
|
sleep 2
|
|
|
|
# Check if custom frontend exists
|
|
if [ -f /var/www/html/index.html ]; then
|
|
echo "✅ Custom frontend file exists"
|
|
|
|
if grep -q "SolaceScanScout" /var/www/html/index.html; then
|
|
echo "✅ Custom frontend content verified"
|
|
else
|
|
echo "⚠️ Frontend file exists but may not be the custom one"
|
|
echo " Deploy the custom frontend using:"
|
|
echo " ./scripts/deploy-frontend-to-vmid5000.sh"
|
|
fi
|
|
else
|
|
echo "⚠️ Custom frontend not found at /var/www/html/index.html"
|
|
echo " Deploy the custom frontend using:"
|
|
echo " ./scripts/deploy-frontend-to-vmid5000.sh"
|
|
fi
|
|
|
|
# Test HTTP endpoint (non-fatal: do not exit on curl/grep failure)
|
|
echo ""
|
|
echo "Testing HTTP endpoint:"
|
|
HTTP_RESPONSE=$(curl -s --max-time 5 http://localhost/ 2>/dev/null | head -5) || true
|
|
if echo "$HTTP_RESPONSE" | grep -q "SolaceScanScout\|<!DOCTYPE html"; then
|
|
echo "✅ Custom frontend is accessible via HTTP"
|
|
else
|
|
echo "⚠️ Frontend may not be accessible (check if file exists)"
|
|
echo "Response preview: $HTTP_RESPONSE"
|
|
fi
|
|
|
|
echo ""
|
|
echo "=========================================="
|
|
echo "Nginx Configuration Updated!"
|
|
echo "=========================================="
|
|
echo ""
|
|
echo "Next steps:"
|
|
echo "1. Deploy custom frontend: ./scripts/deploy-frontend-to-vmid5000.sh"
|
|
echo "2. Or manually copy: cp explorer-monorepo/frontend/public/index.html /var/www/html/index.html"
|
|
echo ""
|