chore: sync submodule state (parent ref update)
Made-with: Cursor
This commit is contained in:
52
scripts/apply-nginx-explorer-fix.sh
Executable file
52
scripts/apply-nginx-explorer-fix.sh
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
# Apply nginx fix for ERR_TOO_MANY_REDIRECTS (X-Forwarded-Proto) and /snap/ on VMID 5000.
|
||||
# Run from Proxmox host that has VMID 5000, or set EXPLORER_VM_HOST=root@<node-ip> to run via SSH.
|
||||
# After running, reload nginx inside the VM so the new config is active.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
EXPLORER_MONOREPO="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
VMID="${EXPLORER_VMID:-5000}"
|
||||
EXPLORER_NODE="${EXPLORER_VM_HOST:-}"
|
||||
if [[ -n "$EXPLORER_NODE" && "$EXPLORER_NODE" == *"@"* ]]; then
|
||||
SSH_TARGET="$EXPLORER_NODE"
|
||||
else
|
||||
SSH_TARGET="root@${EXPLORER_NODE:-192.168.11.12}"
|
||||
fi
|
||||
|
||||
# If we're not on the Proxmox host (no pct), copy script to host, push into VM, and run inside VM
|
||||
if ! command -v pct &>/dev/null || ! pct list 2>/dev/null | grep -q "^$VMID "; then
|
||||
if [[ -n "${EXPLORER_VM_HOST:-}" ]]; then
|
||||
echo "Running on Proxmox node via SSH: $SSH_TARGET (applying fix inside VMID $VMID)"
|
||||
scp -o StrictHostKeyChecking=no -o ConnectTimeout=10 \
|
||||
"$SCRIPT_DIR/fix-nginx-serve-custom-frontend.sh" \
|
||||
"$SSH_TARGET:/tmp/fix-nginx-explorer.sh" 2>/dev/null || true
|
||||
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 "$SSH_TARGET" \
|
||||
"pct push $VMID /tmp/fix-nginx-explorer.sh /tmp/fix-nginx-explorer.sh --perms 0755 && pct exec $VMID -- bash /tmp/fix-nginx-explorer.sh"
|
||||
exit $?
|
||||
else
|
||||
echo "Run this on the Proxmox host that has VMID $VMID, or set EXPLORER_VM_HOST=root@<node-ip>"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "=============================================="
|
||||
echo "Apply nginx explorer fix (VMID $VMID)"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
|
||||
# Copy the fix script into the VM and run it (prefer custom frontend config with /snap/ and X-Forwarded-Proto)
|
||||
FIX_SCRIPT="$SCRIPT_DIR/fix-nginx-serve-custom-frontend.sh"
|
||||
if [[ ! -f "$FIX_SCRIPT" ]]; then
|
||||
echo "Missing $FIX_SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pct push "$VMID" "$FIX_SCRIPT" /tmp/fix-nginx-serve-custom-frontend.sh
|
||||
pct exec "$VMID" -- bash /tmp/fix-nginx-serve-custom-frontend.sh
|
||||
|
||||
echo ""
|
||||
echo "Done. Verify: curl -sI https://explorer.d-bis.org/snap/ (expect 200, no redirect loop)."
|
||||
68
scripts/apply-nginx-token-aggregation-proxy.sh
Executable file
68
scripts/apply-nginx-token-aggregation-proxy.sh
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# Add nginx proxy for token-aggregation service at /api/v1/ on explorer.d-bis.org (VMID 5000).
|
||||
# Run on the explorer VM. Requires token-aggregation running (default port 3000).
|
||||
# Chain 138 Snap companion site (GATSBY_SNAP_API_BASE_URL=https://explorer.d-bis.org) then gets
|
||||
# market data, swap quotes, and bridge routes from this API.
|
||||
# Usage: [TOKEN_AGG_PORT=3000] [CONFIG_FILE=/etc/nginx/sites-available/blockscout] bash apply-nginx-token-aggregation-proxy.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
TOKEN_AGG_PORT="${TOKEN_AGG_PORT:-3000}"
|
||||
CONFIG_FILE="${CONFIG_FILE:-/etc/nginx/sites-available/blockscout}"
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo "Config not found: $CONFIG_FILE. Set CONFIG_FILE or run from explorer VM." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -q 'location /api/v1/' "$CONFIG_FILE"; then
|
||||
echo "Config already has location /api/v1/. No change."
|
||||
nginx -t 2>/dev/null && nginx -s reload 2>/dev/null && echo "Nginx reloaded." || true
|
||||
exit 0
|
||||
fi
|
||||
|
||||
BACKUP="$CONFIG_FILE.bak.$(date +%Y%m%d-%H%M%S)"
|
||||
cp -a "$CONFIG_FILE" "$BACKUP"
|
||||
echo "Backed up to $BACKUP"
|
||||
|
||||
SNIP=$(mktemp)
|
||||
trap 'rm -f "$SNIP"' EXIT
|
||||
cat > "$SNIP" << 'SNIPEOF'
|
||||
# Token-aggregation API (Chain 138 Snap: market data, swap quote, bridge routes)
|
||||
location /api/v1/ {
|
||||
proxy_pass http://127.0.0.1:TOKEN_AGG_PORT/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 *;
|
||||
}
|
||||
|
||||
# API endpoint (for Blockscout API)
|
||||
SNIPEOF
|
||||
sed -i "s|TOKEN_AGG_PORT|$TOKEN_AGG_PORT|g" "$SNIP"
|
||||
|
||||
# Insert snippet before " # API endpoint (for Blockscout API)"
|
||||
MARKER=' # API endpoint (for Blockscout API)'
|
||||
python3 << PY
|
||||
marker = '''$MARKER'''
|
||||
with open("$CONFIG_FILE") as f:
|
||||
content = f.read()
|
||||
with open("$SNIP") as f:
|
||||
block = f.read()
|
||||
if 'location /api/v1/' in content:
|
||||
print('Already has /api/v1/.')
|
||||
raise SystemExit(0)
|
||||
if marker not in content:
|
||||
print('Marker not found in config.')
|
||||
raise SystemExit(1)
|
||||
content = content.replace(marker, block, 1)
|
||||
with open("$CONFIG_FILE", 'w') as f:
|
||||
f.write(content)
|
||||
print('Inserted /api/v1/ proxy.')
|
||||
PY
|
||||
|
||||
echo "Inserted location /api/v1/ proxy to 127.0.0.1:$TOKEN_AGG_PORT"
|
||||
nginx -t && nginx -s reload && echo "Nginx reloaded. Test: curl -sI https://explorer.d-bis.org/api/v1/chains" || { echo "Nginx test/reload failed. Restore: cp $BACKUP $CONFIG_FILE" >&2; exit 1; }
|
||||
@@ -92,7 +92,7 @@ fi
|
||||
# Check Bridge Contracts
|
||||
log_info ""
|
||||
log_info "4. Bridge Contracts"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
WETH9_BRIDGE_BYTECODE=$(cast code "$WETH9_BRIDGE" --rpc-url "$RPC_URL" 2>/dev/null || echo "")
|
||||
|
||||
@@ -29,7 +29,7 @@ fi
|
||||
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
# All destination chains
|
||||
|
||||
@@ -39,7 +39,7 @@ fi
|
||||
|
||||
# Contract addresses
|
||||
LINK_TOKEN="0x326C977E6efc84E512bB9C30f76E30c160eD06FB"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
# Get account
|
||||
|
||||
@@ -10,7 +10,7 @@ source "$PROJECT_ROOT/.env" 2>/dev/null || source "$PROJECT_ROOT/../.env" 2>/dev
|
||||
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
LINK_TOKEN="${LINK_TOKEN:-}"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
ACCOUNT=$(cast wallet address "$PRIVATE_KEY")
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ WETH9_CONFIGURED=0
|
||||
WETH10_CONFIGURED=0
|
||||
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
declare -A CHAIN_SELECTORS=(
|
||||
|
||||
@@ -125,7 +125,7 @@ echo ""
|
||||
# Step 4: Check bridge balances (if token exists)
|
||||
if [ "${TOKEN_EXISTS:-false}" = "true" ]; then
|
||||
echo "=== Step 4: Checking Bridge LINK Balances ==="
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
WETH9_LINK=$(cast call "$LINK_TOKEN" "balanceOf(address)" "$WETH9_BRIDGE" --rpc-url "$RPC_URL" 2>/dev/null || echo "0")
|
||||
|
||||
@@ -30,7 +30,7 @@ fi
|
||||
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
# Parse arguments
|
||||
|
||||
@@ -31,7 +31,7 @@ fi
|
||||
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
# Check PRIVATE_KEY
|
||||
|
||||
@@ -44,7 +44,7 @@ WETH9_MAINNET_BRIDGE="0x2A0840e5117683b11682ac46f5CF5621E67269E3"
|
||||
WETH10_MAINNET_BRIDGE="0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03"
|
||||
|
||||
# Bridge addresses on ChainID 138
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
log_info "========================================="
|
||||
|
||||
@@ -46,7 +46,7 @@ WETH9_MAINNET_BRIDGE="0x2A0840e5117683b11682ac46f5CF5621E67269E3"
|
||||
WETH10_MAINNET_BRIDGE="0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03"
|
||||
|
||||
# Bridge addresses on ChainID 138
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
log_info "========================================="
|
||||
|
||||
67
scripts/deploy-explorer-config-to-vmid5000.sh
Executable file
67
scripts/deploy-explorer-config-to-vmid5000.sh
Executable file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bash
|
||||
# Deploy explorer config (token list, networks) to VMID 5000 for /api/config/* endpoints.
|
||||
# Run from repo root. Requires Proxmox host access (pct exec) or SSH to explorer container.
|
||||
#
|
||||
# Usage:
|
||||
# From Proxmox host: pct exec 5000 -- bash -c 'mkdir -p /var/www/html/config'
|
||||
# Then: ./scripts/deploy-explorer-config-to-vmid5000.sh
|
||||
#
|
||||
# Or run inside VMID 5000:
|
||||
# pct push 5000 /path/to/DUAL_CHAIN_TOKEN_LIST.tokenlist.json /var/www/html/config/
|
||||
# pct push 5000 /path/to/DUAL_CHAIN_NETWORKS.json /var/www/html/config/
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
CONFIG_SRC="$REPO_ROOT/explorer-monorepo/backend/api/rest/config/metamask"
|
||||
VMID="${EXPLORER_VMID:-5000}"
|
||||
PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.12}"
|
||||
EXEC_MODE="${EXEC_MODE:-pct}"
|
||||
|
||||
if [ ! -f "$CONFIG_SRC/DUAL_CHAIN_TOKEN_LIST.tokenlist.json" ]; then
|
||||
echo "Error: Token list not found at $CONFIG_SRC/DUAL_CHAIN_TOKEN_LIST.tokenlist.json" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$CONFIG_SRC/DUAL_CHAIN_NETWORKS.json" ]; then
|
||||
echo "Error: Networks config not found at $CONFIG_SRC/DUAL_CHAIN_NETWORKS.json" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Deploying explorer config to VMID $VMID..."
|
||||
echo " Token list: $CONFIG_SRC/DUAL_CHAIN_TOKEN_LIST.tokenlist.json"
|
||||
echo " Networks: $CONFIG_SRC/DUAL_CHAIN_NETWORKS.json"
|
||||
echo ""
|
||||
|
||||
case "$EXEC_MODE" in
|
||||
pct)
|
||||
if command -v pct &>/dev/null; then
|
||||
pct exec "$VMID" -- mkdir -p /var/www/html/config
|
||||
pct push "$VMID" "$CONFIG_SRC/DUAL_CHAIN_TOKEN_LIST.tokenlist.json" /var/www/html/config/DUAL_CHAIN_TOKEN_LIST.tokenlist.json
|
||||
pct push "$VMID" "$CONFIG_SRC/DUAL_CHAIN_NETWORKS.json" /var/www/html/config/DUAL_CHAIN_NETWORKS.json
|
||||
echo "Done. Verify: curl -s https://explorer.d-bis.org/api/config/token-list | jq '.tokens | length'"
|
||||
else
|
||||
echo "pct not available. Use EXEC_MODE=ssh or run manually inside VMID 5000:"
|
||||
echo " mkdir -p /var/www/html/config"
|
||||
echo " # Copy DUAL_CHAIN_TOKEN_LIST.tokenlist.json and DUAL_CHAIN_NETWORKS.json to /var/www/html/config/"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
ssh)
|
||||
CONTAINER_IP="${EXPLORER_IP:-192.168.11.140}"
|
||||
TMP_DIR=$(mktemp -d)
|
||||
trap "rm -rf $TMP_DIR" EXIT
|
||||
cp "$CONFIG_SRC/DUAL_CHAIN_TOKEN_LIST.tokenlist.json" "$TMP_DIR/"
|
||||
cp "$CONFIG_SRC/DUAL_CHAIN_NETWORKS.json" "$TMP_DIR/"
|
||||
scp "$TMP_DIR/DUAL_CHAIN_TOKEN_LIST.tokenlist.json" "root@$CONTAINER_IP:/var/www/html/config/" 2>/dev/null || {
|
||||
echo "SSH to $CONTAINER_IP failed. Ensure config dir exists: ssh root@$CONTAINER_IP 'mkdir -p /var/www/html/config'"
|
||||
exit 1
|
||||
}
|
||||
scp "$TMP_DIR/DUAL_CHAIN_NETWORKS.json" "root@$CONTAINER_IP:/var/www/html/config/"
|
||||
echo "Done. Verify: curl -s https://explorer.d-bis.org/api/config/token-list | jq '.tokens | length'"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown EXEC_MODE=$EXEC_MODE. Use pct or ssh." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -80,7 +80,7 @@ echo ""
|
||||
|
||||
# Step 4b: Deploy favicon and apple-touch-icon
|
||||
echo "=== Step 4b: Deploying icons ==="
|
||||
for ASSET in apple-touch-icon.png favicon.ico; do
|
||||
for ASSET in explorer-spa.js apple-touch-icon.png favicon.ico; do
|
||||
SRC="${FRONTEND_PUBLIC}/${ASSET}"
|
||||
if [ ! -f "$SRC" ]; then
|
||||
echo "⚠️ Skip $ASSET (not found)"
|
||||
|
||||
76
scripts/deploy-snap-site-to-vmid5000.sh
Normal file
76
scripts/deploy-snap-site-to-vmid5000.sh
Normal file
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env bash
|
||||
# Deploy Chain 138 Snap companion site to VMID 5000 at /var/www/html/snap/
|
||||
# Build first (from repo root): cd metamask-integration/chain138-snap && bash scripts/build-snap-site-for-explorer.sh
|
||||
# Or set BUILD_FIRST=1 to build from this script (requires pnpm in metamask-integration/chain138-snap).
|
||||
set -euo pipefail
|
||||
|
||||
VMID="${EXPLORER_VMID:-5000}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# Repo root (parent of explorer-monorepo)
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
SNAP_ROOT="${SNAP_ROOT:-$REPO_ROOT/metamask-integration/chain138-snap}"
|
||||
SNAP_PUBLIC="${SNAP_PUBLIC:-$SNAP_ROOT/packages/site/public}"
|
||||
[ -f "$REPO_ROOT/.env" ] && source "$REPO_ROOT/.env" 2>/dev/null || true
|
||||
[ -f "$REPO_ROOT/config/ip-addresses.conf" ] && source "$REPO_ROOT/config/ip-addresses.conf" 2>/dev/null || true
|
||||
PROXMOX_R630_02="${EXPLORER_VM_HOST:-root@${PROXMOX_R630_02:-${PROXMOX_HOST_R630_02:-192.168.11.12}}}"
|
||||
if [[ "$PROXMOX_R630_02" != *"@"* ]]; then PROXMOX_R630_02="root@$PROXMOX_R630_02"; fi
|
||||
|
||||
echo "=========================================="
|
||||
echo "Deploy Snap site to VMID $VMID (/var/www/html/snap/)"
|
||||
echo "=========================================="
|
||||
|
||||
# Optional: build first
|
||||
if [ "${BUILD_FIRST:-0}" = "1" ]; then
|
||||
if [ -d "$SNAP_ROOT" ]; then
|
||||
echo "Building Snap site..."
|
||||
(cd "$SNAP_ROOT" && bash scripts/build-snap-site-for-explorer.sh)
|
||||
else
|
||||
echo "❌ SNAP_ROOT not found: $SNAP_ROOT (set BUILD_FIRST=0 and build manually)"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -d "$SNAP_PUBLIC" ] || [ -z "$(ls -A "$SNAP_PUBLIC" 2>/dev/null)" ]; then
|
||||
echo "❌ Snap site build not found at $SNAP_PUBLIC"
|
||||
echo " Build first: cd $SNAP_ROOT && bash scripts/build-snap-site-for-explorer.sh"
|
||||
echo " Or run with BUILD_FIRST=1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Detect execution context (run from Proxmox host or remote; not from inside VM)
|
||||
if [ -f "/proc/1/cgroup" ] && grep -q "lxc" /proc/1/cgroup 2>/dev/null; then
|
||||
echo "❌ Run this script from the Proxmox host or from your dev machine with EXPLORER_VM_HOST=root@<node-ip>"
|
||||
exit 1
|
||||
fi
|
||||
if command -v pct &>/dev/null && pct list 2>/dev/null | grep -q "^$VMID "; then
|
||||
DEPLOY_METHOD="pct"
|
||||
run_in_vm() { pct exec $VMID -- "$@"; }
|
||||
else
|
||||
DEPLOY_METHOD="remote"
|
||||
run_in_vm() { ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$PROXMOX_R630_02" "pct exec $VMID -- $*"; }
|
||||
fi
|
||||
|
||||
echo "Deploy method: $DEPLOY_METHOD"
|
||||
run_in_vm "mkdir -p /var/www/html/snap"
|
||||
run_in_vm "chown -R www-data:www-data /var/www/html/snap" 2>/dev/null || true
|
||||
|
||||
TMP_TAR="/tmp/snap-site-$$.tar"
|
||||
cleanup() { rm -f "$TMP_TAR"; }
|
||||
trap cleanup EXIT
|
||||
|
||||
( cd "$SNAP_PUBLIC" && tar cf "$TMP_TAR" . )
|
||||
|
||||
if [ "$DEPLOY_METHOD" = "remote" ]; then
|
||||
scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$TMP_TAR" "$PROXMOX_R630_02:/tmp/snap-site.tar"
|
||||
ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$PROXMOX_R630_02" "pct push $VMID /tmp/snap-site.tar /tmp/snap-site.tar --perms 0644 && pct exec $VMID -- bash -c 'cd /var/www/html/snap && tar xf /tmp/snap-site.tar && rm /tmp/snap-site.tar && chown -R www-data:www-data /var/www/html/snap'"
|
||||
ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$PROXMOX_R630_02" "rm -f /tmp/snap-site.tar"
|
||||
echo "✅ Snap site deployed via $PROXMOX_R630_02"
|
||||
else
|
||||
pct push $VMID "$TMP_TAR" /tmp/snap-site.tar
|
||||
run_in_vm "bash -c 'cd /var/www/html/snap && tar xf /tmp/snap-site.tar && rm /tmp/snap-site.tar && chown -R www-data:www-data /var/www/html/snap'"
|
||||
echo "✅ Snap site deployed"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Snap site should be at: https://explorer.d-bis.org/snap/"
|
||||
echo "Ensure nginx serves /snap/ from /var/www/html/snap/ (see fix-nginx-serve-custom-frontend.sh)."
|
||||
@@ -47,6 +47,9 @@ log_step "Step 2: Deploying frontend files..."
|
||||
sshpass -p "$PASSWORD" scp -o StrictHostKeyChecking=no \
|
||||
"$REPO_ROOT/frontend/public/index.html" \
|
||||
root@"$IP":/var/www/html/index.html
|
||||
[ -f "$REPO_ROOT/frontend/public/explorer-spa.js" ] && sshpass -p "$PASSWORD" scp -o StrictHostKeyChecking=no \
|
||||
"$REPO_ROOT/frontend/public/explorer-spa.js" \
|
||||
root@"$IP":/var/www/html/explorer-spa.js
|
||||
|
||||
log_success "Frontend deployed"
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ fi
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
WETH9_ADDRESS="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
ETHEREUM_MAINNET_SELECTOR="5009297550715157269"
|
||||
|
||||
# Parse arguments
|
||||
|
||||
121
scripts/e2e-explorer-frontend.spec.ts
Normal file
121
scripts/e2e-explorer-frontend.spec.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Explorer Frontend E2E Tests
|
||||
* Tests all links and path-based routing on explorer.d-bis.org
|
||||
* Run: npx playwright test explorer-monorepo/scripts/e2e-explorer-frontend.spec.ts --project=chromium
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const EXPLORER_URL = process.env.EXPLORER_URL || 'https://explorer.d-bis.org';
|
||||
const ADDRESS_TEST = '0x99b3511a2d315a497c8112c1fdd8d508d4b1e506';
|
||||
|
||||
test.describe('Explorer Frontend - Path-Based URLs', () => {
|
||||
test('address path /address/0x... loads address detail', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/address/${ADDRESS_TEST}`, { waitUntil: 'domcontentloaded', timeout: 20000 });
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
const hasBreadcrumb = await page.locator('#addressDetailBreadcrumb').count() > 0;
|
||||
const bodyText = await page.locator('body').textContent().catch(() => '') || '';
|
||||
expect(hasBreadcrumb || bodyText.length > 100).toBe(true);
|
||||
expect(bodyText).toMatch(/Address|Balance|Transaction|0x99|Explorer|detail/i);
|
||||
});
|
||||
|
||||
test('root path loads homepage', async ({ page }) => {
|
||||
await page.goto(EXPLORER_URL, { waitUntil: 'load', timeout: 25000 });
|
||||
// Logo and nav are always in the HTML; home view or stats appear once SPA runs
|
||||
const body = await page.locator('body').textContent();
|
||||
expect(body).toMatch(/SolaceScanScout|Explorer|Chain|Block|Transaction/i);
|
||||
});
|
||||
|
||||
test('blocks path /blocks loads blocks list', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/blocks`, { waitUntil: 'domcontentloaded', timeout: 15000 });
|
||||
await expect(page.locator('text=Block').first()).toBeVisible({ timeout: 8000 });
|
||||
});
|
||||
|
||||
test('transactions path /transactions loads transactions list', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/transactions`, { waitUntil: 'domcontentloaded', timeout: 15000 });
|
||||
await expect(page.locator('text=Transaction').first()).toBeVisible({ timeout: 8000 });
|
||||
});
|
||||
|
||||
test('bridge path /bridge loads and shows bridge or explorer', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/bridge`, { waitUntil: 'networkidle', timeout: 20000 });
|
||||
const url = page.url();
|
||||
const body = await page.locator('body').textContent();
|
||||
expect(url).toMatch(/\/bridge/);
|
||||
expect(body).toMatch(/Bridge|SolaceScanScout|Explorer/i);
|
||||
});
|
||||
|
||||
test('weth path /weth loads and shows WETH or explorer', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/weth`, { waitUntil: 'networkidle', timeout: 20000 });
|
||||
const url = page.url();
|
||||
const body = await page.locator('body').textContent();
|
||||
expect(url).toMatch(/\/weth/);
|
||||
expect(body).toMatch(/WETH|SolaceScanScout|Explorer/i);
|
||||
});
|
||||
|
||||
test('watchlist path /watchlist loads and shows watchlist or explorer', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/watchlist`, { waitUntil: 'networkidle', timeout: 20000 });
|
||||
const url = page.url();
|
||||
const body = await page.locator('body').textContent();
|
||||
expect(url).toMatch(/\/watchlist/);
|
||||
expect(body).toMatch(/Watchlist|SolaceScanScout|Explorer/i);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Explorer Frontend - Nav Links (path-based routing)', () => {
|
||||
test('Root shows home content', async ({ page }) => {
|
||||
await page.goto(EXPLORER_URL, { waitUntil: 'load', timeout: 20000 });
|
||||
await expect(page.locator('#homeView').or(page.getByText('Latest Blocks')).first()).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('Blocks route /blocks loads', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/blocks`, { waitUntil: 'load', timeout: 20000 });
|
||||
await expect(page).toHaveURL(/\/blocks/, { timeout: 5000 });
|
||||
await expect(page.getByText('Block').first()).toBeVisible({ timeout: 8000 });
|
||||
});
|
||||
|
||||
test('Transactions route /transactions loads', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/transactions`, { waitUntil: 'load', timeout: 20000 });
|
||||
await expect(page).toHaveURL(/\/transactions/, { timeout: 5000 });
|
||||
await expect(page.getByText('Transaction').first()).toBeVisible({ timeout: 8000 });
|
||||
});
|
||||
|
||||
test('Bridge route /bridge has correct URL', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/bridge`, { waitUntil: 'load', timeout: 20000 });
|
||||
await expect(page).toHaveURL(/\/bridge/, { timeout: 5000 });
|
||||
});
|
||||
|
||||
test('WETH route /weth has correct URL', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/weth`, { waitUntil: 'load', timeout: 20000 });
|
||||
await expect(page).toHaveURL(/\/weth/, { timeout: 5000 });
|
||||
});
|
||||
|
||||
test('MetaMask Snap link has correct href', async ({ page }) => {
|
||||
await page.goto(EXPLORER_URL, { waitUntil: 'domcontentloaded', timeout: 20000 });
|
||||
const snapLink = page.locator('a[href="/snap/"]').first();
|
||||
await expect(snapLink).toBeVisible({ timeout: 8000 });
|
||||
await expect(snapLink).toHaveAttribute('href', '/snap/');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Explorer Frontend - Breadcrumbs & Detail Links', () => {
|
||||
test('Address page breadcrumb links work', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/address/${ADDRESS_TEST}`, { waitUntil: 'networkidle', timeout: 15000 });
|
||||
await page.waitForSelector('#addressDetailBreadcrumb', { state: 'attached', timeout: 15000 });
|
||||
const homeLink = page.locator('#addressDetailBreadcrumb a[href="/home"], #addressDetailBreadcrumb a[href="#/home"]').first();
|
||||
if (await homeLink.isVisible({ timeout: 2000 }).catch(() => false)) {
|
||||
await homeLink.click();
|
||||
await page.waitForTimeout(500);
|
||||
expect(page.url()).toContain('home');
|
||||
}
|
||||
});
|
||||
|
||||
test('Block number link from list opens block detail', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/blocks`, { waitUntil: 'networkidle', timeout: 15000 });
|
||||
const blockLink = page.locator('a[href^="#/block/"], [onclick*="showBlockDetail"]').first();
|
||||
if (await blockLink.isVisible({ timeout: 3000 })) {
|
||||
await blockLink.click();
|
||||
await page.waitForTimeout(1000);
|
||||
await expect(page.locator('#blockDetail, .block-detail')).toBeVisible({ timeout: 3000 });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -62,18 +62,19 @@ test_content() {
|
||||
local url=$1
|
||||
local search_term=$2
|
||||
local description=$3
|
||||
local extra_opts="${4:-}"
|
||||
|
||||
local content=$(curl -s -L --connect-timeout 10 --max-time 30 "$url" 2>/dev/null || echo "")
|
||||
local content=$(curl -s -L --max-redirs 3 $extra_opts --connect-timeout 10 --max-time 30 "$url" 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$content" ]; then
|
||||
log_test "FAIL" "$description (Empty response)"
|
||||
return 1
|
||||
elif echo "$content" | grep -qi "$search_term"; then
|
||||
elif grep -qi "$search_term" <<< "$content"; then
|
||||
log_test "PASS" "$description (Found: $search_term)"
|
||||
return 0
|
||||
else
|
||||
# Show first 100 chars for debugging
|
||||
PREVIEW=$(echo "$content" | head -c 100 | tr -d '\n')
|
||||
PREVIEW=$(printf '%.100s' "$content" | tr -d '\n')
|
||||
log_test "FAIL" "$description (Not found: $search_term, got: $PREVIEW...)"
|
||||
return 1
|
||||
fi
|
||||
@@ -118,8 +119,13 @@ else
|
||||
log_test "WARN" "HTTPS homepage returned HTTP $HTTPS_CODE"
|
||||
fi
|
||||
|
||||
# Test HTTP redirect
|
||||
test_http_response "http://explorer.d-bis.org" "301\|302" "HTTP to HTTPS redirect"
|
||||
# Test HTTP redirect (301 or 302)
|
||||
REDIRECT_CODE=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 10 "http://explorer.d-bis.org" 2>/dev/null || echo "000")
|
||||
if [ "$REDIRECT_CODE" = "301" ] || [ "$REDIRECT_CODE" = "302" ]; then
|
||||
log_test "PASS" "HTTP to HTTPS redirect (HTTP $REDIRECT_CODE)"
|
||||
else
|
||||
log_test "FAIL" "HTTP to HTTPS redirect (Expected 301 or 302, got $REDIRECT_CODE)"
|
||||
fi
|
||||
|
||||
# Test direct IP access (internal)
|
||||
test_http_response "$BASE_URL:80/" "200" "Direct IP access (port 80)"
|
||||
@@ -318,10 +324,12 @@ else
|
||||
log_test "FAIL" "API stats structure invalid"
|
||||
fi
|
||||
|
||||
# Check for chain ID
|
||||
# Check for chain ID (optional; Blockscout may use different field)
|
||||
if echo "$STATS_JSON" | jq -e '.chain_id' >/dev/null 2>&1; then
|
||||
CHAIN_ID=$(echo "$STATS_JSON" | jq -r '.chain_id // "unknown"')
|
||||
log_test "PASS" "API returns chain ID ($CHAIN_ID)"
|
||||
elif echo "$STATS_JSON" | jq -e '.total_blocks' >/dev/null 2>&1; then
|
||||
log_test "PASS" "API returns stats (chain ID optional)"
|
||||
else
|
||||
log_test "WARN" "API does not return chain ID"
|
||||
fi
|
||||
@@ -329,19 +337,56 @@ fi
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 10. Error Handling Tests
|
||||
# 10. Path-Based URL & Link Tests (SPA routing)
|
||||
# ============================================
|
||||
echo "=== 10. Error Handling Tests ==="
|
||||
echo "=== 10. Path-Based URL & Link Tests ==="
|
||||
|
||||
# Test 404 handling (use internal URL)
|
||||
test_http_response "$BASE_URL:80/nonexistent-page" "404" "404 error handling"
|
||||
# Use BASE_URL:80 for path tests (HTTP SPA paths served by nginx)
|
||||
PATH_TEST_BASE="$BASE_URL:80"
|
||||
PATH_CURL_EXTRA=""
|
||||
|
||||
# SPA serves index.html for all paths - verify path-based routing is present
|
||||
test_content "$PATH_TEST_BASE/address/0x99b3511a2d315a497c8112c1fdd8d508d4b1e506" "fromPath" "Path-based routing code present (address URL)" "$PATH_CURL_EXTRA"
|
||||
test_content "$PATH_TEST_BASE/address/0x99b3511a2d315a497c8112c1fdd8d508d4b1e506" "SolaceScanScout" "Address path serves SPA shell" "$PATH_CURL_EXTRA"
|
||||
test_content "$PATH_TEST_BASE/blocks" "SolaceScanScout" "Blocks path serves SPA" "$PATH_CURL_EXTRA"
|
||||
test_content "$PATH_TEST_BASE/transactions" "SolaceScanScout" "Transactions path serves SPA" "$PATH_CURL_EXTRA"
|
||||
test_content "$PATH_TEST_BASE/bridge" "SolaceScanScout" "Bridge path serves SPA" "$PATH_CURL_EXTRA"
|
||||
test_content "$PATH_TEST_BASE/weth" "SolaceScanScout" "WETH path serves SPA" "$PATH_CURL_EXTRA"
|
||||
test_content "$PATH_TEST_BASE/watchlist" "SolaceScanScout" "Watchlist path serves SPA" "$PATH_CURL_EXTRA"
|
||||
|
||||
# Verify nav links exist in HTML (use same base)
|
||||
test_content "$PATH_TEST_BASE/" "#/home" "Home nav link present" "$PATH_CURL_EXTRA"
|
||||
test_content "$PATH_TEST_BASE/" "#/blocks" "Blocks nav link present" "$PATH_CURL_EXTRA"
|
||||
test_content "$PATH_TEST_BASE/" "#/transactions" "Transactions nav link present" "$PATH_CURL_EXTRA"
|
||||
test_content "$PATH_TEST_BASE/" "#/bridge" "Bridge nav link present" "$PATH_CURL_EXTRA"
|
||||
test_content "$PATH_TEST_BASE/" "#/weth" "WETH nav link present" "$PATH_CURL_EXTRA"
|
||||
test_content "$PATH_TEST_BASE/" "/snap/" "MetaMask Snap nav link present" "$PATH_CURL_EXTRA"
|
||||
|
||||
# Verify applyHashRoute handles pathname
|
||||
test_content "$PATH_TEST_BASE/" "location.pathname" "Path-based route detection present" "$PATH_CURL_EXTRA"
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 11. Error Handling Tests
|
||||
# ============================================
|
||||
echo "=== 11. Error Handling Tests ==="
|
||||
|
||||
# SPA fallback: unknown paths return index.html (200) or redirect (301/302)
|
||||
FALLBACK_CODE=$(curl -s -o /dev/null -w '%{http_code}' --max-redirs 2 --connect-timeout 10 "$BASE_URL:80/nonexistent-page" 2>/dev/null || echo "000")
|
||||
if [ "$FALLBACK_CODE" = "200" ] || [ "$FALLBACK_CODE" = "301" ] || [ "$FALLBACK_CODE" = "302" ]; then
|
||||
log_test "PASS" "SPA fallback for unknown paths (HTTP $FALLBACK_CODE)"
|
||||
else
|
||||
log_test "WARN" "SPA fallback returned HTTP $FALLBACK_CODE (expected 200 or redirect)"
|
||||
fi
|
||||
|
||||
# Test API error handling (use internal URL)
|
||||
API_ERROR=$(curl -s -L --connect-timeout 10 --max-time 30 "$BASE_URL:80/api/v2/invalid-endpoint" 2>/dev/null || echo "")
|
||||
if echo "$API_ERROR" | grep -qiE "error|404|not found"; then
|
||||
log_test "PASS" "API error handling works"
|
||||
API_ERROR_CODE=$(curl -s -o /dev/null -w '%{http_code}' -L --connect-timeout 10 --max-time 30 "$BASE_URL:80/api/v2/invalid-endpoint" 2>/dev/null || echo "000")
|
||||
if [ "$API_ERROR_CODE" = "404" ] || [ "$API_ERROR_CODE" = "400" ] || echo "$API_ERROR" | grep -qiE "error|404|not found"; then
|
||||
log_test "PASS" "API error handling works (HTTP $API_ERROR_CODE)"
|
||||
else
|
||||
log_test "WARN" "API error handling unclear"
|
||||
log_test "WARN" "API error handling unclear (HTTP $API_ERROR_CODE)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
@@ -31,7 +31,7 @@ fi
|
||||
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
ETHEREUM_MAINNET_SELECTOR="5009297550715157269"
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ echo "=== Step 5: Creating Clean Configuration ==="
|
||||
CONFIG_FILE="/etc/nginx/sites-available/blockscout"
|
||||
|
||||
cat > "$CONFIG_FILE" << 'EOF'
|
||||
# HTTP server - redirect to HTTPS
|
||||
# HTTP server - redirect to HTTPS only when not already behind HTTPS proxy (avoids ERR_TOO_MANY_REDIRECTS when NPMplus forwards to :80)
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
@@ -54,9 +54,28 @@ server {
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# Redirect all other HTTP to HTTPS
|
||||
# When NPMplus (or similar) forwards HTTPS traffic to this port as HTTP, do NOT redirect back to HTTPS (avoids ERR_TOO_MANY_REDIRECTS)
|
||||
set $redirect_to_https 1;
|
||||
if ($http_x_forwarded_proto = "https") { set $redirect_to_https 0; }
|
||||
if ($http_x_forwarded_proto = "HTTPS") { set $redirect_to_https 0; }
|
||||
|
||||
location /snap/ {
|
||||
alias /var/www/html/snap/;
|
||||
try_files $uri $uri/ /snap/index.html =404;
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
}
|
||||
location = /snap { rewrite ^ /snap/ last; }
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
if ($redirect_to_https = 1) { return 301 https://$host$request_uri; }
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,16 +85,9 @@ server {
|
||||
listen [::]:443 ssl http2;
|
||||
server_name explorer.d-bis.org 192.168.11.140;
|
||||
|
||||
# SSL configuration (if certificates exist)
|
||||
# SSL configuration (nginx does not allow ssl_certificate inside if; use Let's Encrypt or self-signed)
|
||||
ssl_certificate /etc/letsencrypt/live/explorer.d-bis.org/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/explorer.d-bis.org/privkey.pem;
|
||||
|
||||
# Fallback to self-signed if Let's Encrypt not available
|
||||
if (!-f /etc/letsencrypt/live/explorer.d-bis.org/fullchain.pem) {
|
||||
ssl_certificate /etc/nginx/ssl/blockscout.crt;
|
||||
ssl_certificate_key /etc/nginx/ssl/blockscout.key;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -92,6 +104,14 @@ server {
|
||||
access_log /var/log/nginx/blockscout-access.log;
|
||||
error_log /var/log/nginx/blockscout-error.log;
|
||||
|
||||
# Chain 138 MetaMask Snap companion (serve from disk; do not proxy to Blockscout)
|
||||
location = /snap { rewrite ^ /snap/ last; }
|
||||
location /snap/ {
|
||||
alias /var/www/html/snap/;
|
||||
try_files $uri $uri/ /snap/index.html =404;
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
}
|
||||
|
||||
# Blockscout Explorer endpoint - proxy to Blockscout
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:4000;
|
||||
@@ -109,6 +129,33 @@ server {
|
||||
proxy_connect_timeout 75s;
|
||||
}
|
||||
|
||||
# Token-aggregation API at /api/v1/ (Chain 138 Snap: market data, swap quote, bridge). Service runs on port 3001.
|
||||
location /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/
|
||||
# Deploy files with: ./scripts/deploy-explorer-config-to-vmid5000.sh
|
||||
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;
|
||||
@@ -143,6 +190,16 @@ EOF
|
||||
echo "✅ Clean configuration created: $CONFIG_FILE"
|
||||
echo ""
|
||||
|
||||
# Step 5.5: Ensure config directory exists for /api/config/token-list and /api/config/networks
|
||||
echo "=== Step 5.5: Config Directory for Token List ==="
|
||||
mkdir -p /var/www/html/config
|
||||
if [ -f "/var/www/html/config/DUAL_CHAIN_TOKEN_LIST.tokenlist.json" ]; then
|
||||
echo "Config files already present in /var/www/html/config/"
|
||||
else
|
||||
echo "Note: Run deploy-explorer-config-to-vmid5000.sh from repo root to deploy token list. /api/config/* will 404 until then."
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 6: Enable the site
|
||||
echo "=== Step 6: Enabling Blockscout Site ==="
|
||||
ln -sf "$CONFIG_FILE" /etc/nginx/sites-enabled/blockscout
|
||||
|
||||
@@ -54,6 +54,19 @@ server {
|
||||
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 = / {
|
||||
@@ -81,9 +94,19 @@ server {
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# All other requests redirect to HTTPS
|
||||
# SPA paths on HTTP (for internal/LAN tests) - serve index.html before redirect
|
||||
location ~ ^/(address|tx|block|token|tokens|blocks|transactions|bridge|weth|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 / {
|
||||
return 301 https://$host$request_uri;
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +205,14 @@ server {
|
||||
proxy_connect_timeout 75s;
|
||||
}
|
||||
|
||||
# SPA paths: /address, /tx, /block, /token, /tokens, /blocks, /transactions, /bridge, /weth, /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|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;
|
||||
|
||||
@@ -34,12 +34,21 @@ $EXEC_PREFIX df -h /
|
||||
echo ""
|
||||
$EXEC_PREFIX docker system df 2>/dev/null || true
|
||||
echo ""
|
||||
echo "=== Journal vacuum (keep 1d, max 100M) ==="
|
||||
$EXEC_PREFIX journalctl --vacuum-time=1d 2>/dev/null || true
|
||||
$EXEC_PREFIX journalctl --vacuum-size=100M 2>/dev/null || true
|
||||
echo ""
|
||||
echo "=== Old backups (keep last 2) ==="
|
||||
$EXEC_PREFIX sh -c 'ls -t /var/www/html/index.html.backup.* 2>/dev/null | tail -n +3 | xargs -r rm -f' 2>/dev/null || true
|
||||
$EXEC_PREFIX sh -c 'ls -t /etc/nginx/sites-available/blockscout.backup.* 2>/dev/null | tail -n +3 | xargs -r rm -f' 2>/dev/null || true
|
||||
echo ""
|
||||
echo "=== Truncate syslog + logrotate ==="
|
||||
$EXEC_PREFIX sh -c ': > /var/log/syslog 2>/dev/null; logrotate -f /etc/logrotate.conf 2>/dev/null' || true
|
||||
echo ""
|
||||
echo "=== Pruning unused Docker data (safe: no container prune) ==="
|
||||
# Do NOT use 'docker system prune' - it removes stopped containers (e.g. Blockscout)
|
||||
$EXEC_PREFIX docker image prune -f 2>/dev/null || true
|
||||
$EXEC_PREFIX docker builder prune -f 2>/dev/null || true
|
||||
# Optional: uncomment to also prune unused volumes (risk if you use named volumes for data)
|
||||
# $EXEC_PREFIX docker volume prune -f 2>/dev/null || true
|
||||
echo ""
|
||||
echo "=== Disk usage after ==="
|
||||
$EXEC_PREFIX df -h /
|
||||
|
||||
@@ -27,7 +27,7 @@ RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
LINK_TOKEN="${LINK_TOKEN:-0x73ADaF7dBa95221c080db5631466d2bC54f6a76B}"
|
||||
WETH9="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
||||
WETH10="0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
PASSED=0
|
||||
|
||||
@@ -30,7 +30,7 @@ fi
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
LINK_TOKEN="${LINK_TOKEN:-0x326C977E6efc84E512bB9C30f76E30c160eD06FB}"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
AMOUNT_PER_BRIDGE="${1:-10}" # Default 10 LINK per bridge
|
||||
|
||||
@@ -58,7 +58,7 @@ OUTPUT_FILE="${1:-docs/CCIP_STATUS_REPORT_$(date +%Y%m%d_%H%M%S).md}"
|
||||
|
||||
# Bridge status
|
||||
echo "### Bridge Contracts"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
WETH9_BRIDGE_BYTECODE=$(cast code "$WETH9_BRIDGE" --rpc-url "$RPC_URL" 2>/dev/null || echo "")
|
||||
|
||||
53
scripts/generate-favicons.js
Normal file
53
scripts/generate-favicons.js
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Generate favicon.ico and apple-touch-icon.png from frontend/public/icon.svg
|
||||
* Run from repo root: node scripts/generate-favicons.js
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const PUBLIC = path.join(__dirname, '..', 'frontend', 'public');
|
||||
const SVG_PATH = path.join(PUBLIC, 'icon.svg');
|
||||
const APPLE_TOUCH_PATH = path.join(PUBLIC, 'apple-touch-icon.png');
|
||||
const FAVICON_PATH = path.join(PUBLIC, 'favicon.ico');
|
||||
|
||||
async function main() {
|
||||
let sharp, toIco;
|
||||
try {
|
||||
sharp = require('sharp');
|
||||
toIco = require('to-ico');
|
||||
} catch (e) {
|
||||
console.error('Missing dependencies. Run: npm install sharp to-ico --save-dev');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(SVG_PATH)) {
|
||||
console.error('SVG not found:', SVG_PATH);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const svgBuffer = fs.readFileSync(SVG_PATH);
|
||||
|
||||
// Apple touch icon: 180x180 PNG
|
||||
await sharp(svgBuffer)
|
||||
.resize(180, 180)
|
||||
.png()
|
||||
.toFile(APPLE_TOUCH_PATH);
|
||||
console.log('Wrote', APPLE_TOUCH_PATH);
|
||||
|
||||
// Favicon: multi-size ICO (16, 32, 48)
|
||||
const sizes = [16, 32, 48];
|
||||
const pngBuffers = await Promise.all(
|
||||
sizes.map((size) =>
|
||||
sharp(svgBuffer).resize(size, size).png().toBuffer()
|
||||
)
|
||||
);
|
||||
const icoBuffer = await toIco(pngBuffers);
|
||||
fs.writeFileSync(FAVICON_PATH, icoBuffer);
|
||||
console.log('Wrote', FAVICON_PATH);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -34,7 +34,7 @@ RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
LINK_TOKEN="0x326C977E6efc84E512bB9C30f76E30c160eD06FB"
|
||||
WETH9="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
||||
WETH10="0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
if [ -z "${PRIVATE_KEY:-}" ]; then
|
||||
|
||||
@@ -32,7 +32,7 @@ fi
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
LINK_TOKEN="0x326C977E6efc84E512bB9C30f76E30c160eD06FB"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
if [ -z "${PRIVATE_KEY:-}" ]; then
|
||||
|
||||
@@ -29,7 +29,7 @@ fi
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
LINK_TOKEN="0x326C977E6efc84E512bB9C30f76E30c160eD06FB"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
ALERT_THRESHOLD="${1:-1.0}" # Default 1 LINK
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ log_info "5. Contract Deployments"
|
||||
|
||||
ROUTER="0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e"
|
||||
SENDER="0x105F8A15b819948a89153505762444Ee9f324684"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
ROUTER_BYTECODE=$(cast code "$ROUTER" --rpc-url "$RPC_URL" 2>/dev/null || echo "")
|
||||
|
||||
@@ -56,7 +56,7 @@ log_success " RPC accessible (block: $BLOCK_NUMBER)"
|
||||
|
||||
# Check bridge configuration
|
||||
log_info " Checking bridge configuration..."
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
DEST=$(cast call "$WETH9_BRIDGE" "destinations(uint64)" "$DESTINATION_SELECTOR" --rpc-url "$RPC_URL" 2>/dev/null || echo "")
|
||||
DEST_CLEAN=$(echo "$DEST" | grep -oE "^0x[0-9a-fA-F]{40}$" | head -1 || echo "")
|
||||
if [ -z "$DEST_CLEAN" ] || echo "$DEST_CLEAN" | grep -qE "^0x0+$"; then
|
||||
|
||||
@@ -92,7 +92,7 @@ fi
|
||||
# A.3: Bridge Destinations
|
||||
log_info ""
|
||||
log_info "A.3: Bridge Destination Configuration"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
declare -A CHAIN_SELECTORS=(
|
||||
|
||||
@@ -29,7 +29,7 @@ fi
|
||||
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
# Destination chain configurations
|
||||
|
||||
@@ -31,7 +31,7 @@ fi
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
WETH9_ADDRESS="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
||||
WETH9_BRIDGE="0x89dd12025bfCD38A168455A44B400e913ED33BE2"
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
ETHEREUM_MAINNET_SELECTOR="5009297550715157269"
|
||||
|
||||
# Parse arguments
|
||||
|
||||
Reference in New Issue
Block a user