feat: deploy MEV Control GUI to mev.defi-oracle.io (nginx, NPM, Cloudflare)
- Add nginx site template + sync-mev-control-gui-defi-oracle.sh - NPM fleet: mev.defi-oracle.io + www.mev; Cloudflare set-mev-defi-oracle-dns.sh - ip-addresses + .env.master.example: MEV_ADMIN_API_* and web root vars - Runbook MEV_CONTROL_DEFI_ORACLE_IO_DEPLOYMENT.md; AGENTS, MASTER_INDEX, ALL_VMIDS Made-with: Cursor
This commit is contained in:
151
scripts/cloudflare/set-mev-defi-oracle-dns.sh
Executable file
151
scripts/cloudflare/set-mev-defi-oracle-dns.sh
Executable file
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env bash
|
||||
# Publish mev.defi-oracle.io and www.mev.defi-oracle.io (same edge mode as info.defi-oracle.io).
|
||||
# Requires CLOUDFLARE_ZONE_ID_DEFI_ORACLE_IO and Cloudflare credentials (see set-info-defi-oracle-dns-to-vmid2400-tunnel.sh).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
source "$PROJECT_ROOT/config/ip-addresses.conf" 2>/dev/null || true
|
||||
if [[ -f "$PROJECT_ROOT/.env" ]]; then
|
||||
set +u
|
||||
source "$PROJECT_ROOT/.env" 2>/dev/null || true
|
||||
set -u
|
||||
fi
|
||||
|
||||
TUNNEL_ID="${VMID2400_TUNNEL_ID:-${CLOUDFLARE_TUNNEL_ID_VMID2400:-26138c21-db00-4a02-95db-ec75c07bda5b}}"
|
||||
ZONE_ID="${CLOUDFLARE_ZONE_ID_DEFI_ORACLE_IO:-}"
|
||||
TUNNEL_TARGET="${TUNNEL_ID}.cfargotunnel.com"
|
||||
EDGE_MODE="${MEV_DEFI_ORACLE_EDGE_MODE:-${INFO_DEFI_ORACLE_EDGE_MODE:-auto}}"
|
||||
PUBLIC_EDGE_IP="${MEV_DEFI_ORACLE_PUBLIC_IP:-${INFO_DEFI_ORACLE_PUBLIC_IP:-${PUBLIC_IP:-}}}"
|
||||
ACCOUNT_ID="${CLOUDFLARE_ACCOUNT_ID:-}"
|
||||
HOSTS=( "mev.defi-oracle.io" "www.mev.defi-oracle.io" )
|
||||
|
||||
if [[ -z "$ZONE_ID" ]]; then
|
||||
echo "CLOUDFLARE_ZONE_ID_DEFI_ORACLE_IO is required." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cf_api() {
|
||||
local method="$1"
|
||||
local endpoint="$2"
|
||||
local data="${3:-}"
|
||||
local url="https://api.cloudflare.com/client/v4/zones/${ZONE_ID}${endpoint}"
|
||||
|
||||
if [[ -n "${CLOUDFLARE_API_TOKEN:-}" ]]; then
|
||||
if [[ -n "$data" ]]; then
|
||||
curl -s -X "$method" "$url" -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json" --data "$data"
|
||||
else
|
||||
curl -s -X "$method" "$url" -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json"
|
||||
fi
|
||||
elif [[ -n "${CLOUDFLARE_EMAIL:-}" && -n "${CLOUDFLARE_API_KEY:-}" ]]; then
|
||||
if [[ -n "$data" ]]; then
|
||||
curl -s -X "$method" "$url" -H "X-Auth-Email: $CLOUDFLARE_EMAIL" -H "X-Auth-Key: $CLOUDFLARE_API_KEY" -H "Content-Type: application/json" --data "$data"
|
||||
else
|
||||
curl -s -X "$method" "$url" -H "X-Auth-Email: $CLOUDFLARE_EMAIL" -H "X-Auth-Key: $CLOUDFLARE_API_KEY" -H "Content-Type: application/json"
|
||||
fi
|
||||
else
|
||||
echo "Cloudflare credentials are required." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
cf_global_api() {
|
||||
local method="$1"
|
||||
local url="$2"
|
||||
if [[ -n "${CLOUDFLARE_API_TOKEN:-}" ]]; then
|
||||
curl -s -X "$method" "$url" -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json"
|
||||
elif [[ -n "${CLOUDFLARE_EMAIL:-}" && -n "${CLOUDFLARE_API_KEY:-}" ]]; then
|
||||
curl -s -X "$method" "$url" -H "X-Auth-Email: $CLOUDFLARE_EMAIL" -H "X-Auth-Key: $CLOUDFLARE_API_KEY" -H "Content-Type: application/json"
|
||||
else
|
||||
echo "Cloudflare credentials are required." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
resolve_account_id() {
|
||||
if [[ -n "$ACCOUNT_ID" ]]; then
|
||||
printf '%s\n' "$ACCOUNT_ID"
|
||||
return 0
|
||||
fi
|
||||
local response
|
||||
response="$(cf_global_api GET "https://api.cloudflare.com/client/v4/accounts")"
|
||||
ACCOUNT_ID="$(printf '%s\n' "$response" | jq -r '.result[0].id // empty')"
|
||||
printf '%s\n' "$ACCOUNT_ID"
|
||||
}
|
||||
|
||||
tunnel_exists() {
|
||||
local account_id
|
||||
account_id="$(resolve_account_id)"
|
||||
[[ -z "$account_id" ]] && return 1
|
||||
local response
|
||||
response="$(cf_global_api GET "https://api.cloudflare.com/client/v4/accounts/${account_id}/cfd_tunnel/${TUNNEL_ID}")"
|
||||
printf '%s\n' "$response" | jq -e '.success == true' >/dev/null 2>&1
|
||||
}
|
||||
|
||||
delete_conflicting_records() {
|
||||
local name="$1"
|
||||
local existing
|
||||
existing="$(cf_api GET "/dns_records?name=${name}")"
|
||||
printf '%s\n' "$existing" | jq -r '.result[]? | "\(.id) \(.type)"' | while read -r record_id record_type; do
|
||||
[[ -z "$record_id" ]] && continue
|
||||
if [[ "$record_type" == "A" || "$record_type" == "AAAA" || "$record_type" == "CNAME" ]]; then
|
||||
cf_api DELETE "/dns_records/${record_id}" >/dev/null
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
create_cname() {
|
||||
local name="$1"
|
||||
local payload
|
||||
payload="$(jq -n --arg name "$name" --arg content "$TUNNEL_TARGET" '{type:"CNAME",name:$name,content:$content,proxied:true,ttl:1}')"
|
||||
cf_api POST "/dns_records" "$payload" | jq -e '.success == true' >/dev/null
|
||||
}
|
||||
|
||||
create_a_record() {
|
||||
local name="$1"
|
||||
local payload
|
||||
payload="$(jq -n --arg name "$name" --arg content "$PUBLIC_EDGE_IP" '{type:"A",name:$name,content:$content,proxied:true,ttl:1}')"
|
||||
cf_api POST "/dns_records" "$payload" | jq -e '.success == true' >/dev/null
|
||||
}
|
||||
|
||||
TARGET_MODE="$EDGE_MODE"
|
||||
if [[ "$TARGET_MODE" == "auto" ]]; then
|
||||
if tunnel_exists; then
|
||||
TARGET_MODE="tunnel"
|
||||
else
|
||||
TARGET_MODE="public_ip"
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$TARGET_MODE" in
|
||||
tunnel)
|
||||
echo "Publishing mev.defi-oracle.io hostnames to tunnel ${TUNNEL_TARGET}"
|
||||
;;
|
||||
public_ip|npmplus)
|
||||
echo "Publishing mev.defi-oracle.io hostnames to public edge ${PUBLIC_EDGE_IP}"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported EDGE_MODE=${TARGET_MODE}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "$TARGET_MODE" != "tunnel" ]] && [[ -z "$PUBLIC_EDGE_IP" ]]; then
|
||||
echo "Set PUBLIC_IP (or MEV_DEFI_ORACLE_PUBLIC_IP / INFO_DEFI_ORACLE_PUBLIC_IP) for A-record mode." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for host in "${HOSTS[@]}"; do
|
||||
delete_conflicting_records "$host"
|
||||
if [[ "$TARGET_MODE" == "tunnel" ]]; then
|
||||
create_cname "$host"
|
||||
else
|
||||
create_a_record "$host"
|
||||
fi
|
||||
echo " - ${host}"
|
||||
done
|
||||
|
||||
echo "Done."
|
||||
@@ -83,4 +83,5 @@ ssh $SSH_OPTS "root@${PROXMOX_HOST}" "pct exec ${VMID} -- bash -lc \"ln -sf '${S
|
||||
echo ""
|
||||
echo "✅ Dedicated web LXC ${VMID} ready at ${IP_CT}:80"
|
||||
echo " Next: bash scripts/deployment/sync-info-defi-oracle-to-vmid2400.sh"
|
||||
echo " MEV Control: bash scripts/deployment/sync-mev-control-gui-defi-oracle.sh (mev.defi-oracle.io; set MEV_ADMIN_API_HOST)"
|
||||
echo " NPM: point info.defi-oracle.io → http://${IP_CT}:80 (fleet: update-npmplus-proxy-hosts-api.sh)"
|
||||
|
||||
113
scripts/deployment/sync-mev-control-gui-defi-oracle.sh
Executable file
113
scripts/deployment/sync-mev-control-gui-defi-oracle.sh
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build MEV Control (Vite GUI from MEV_Bot) and sync to the defi-oracle.io nginx LXC.
|
||||
#
|
||||
# Default: VMID 2410 (same host as info.defi-oracle.io), /var/www/mev.defi-oracle.io/html
|
||||
# Nginx proxies /api/* to mev-admin-api (override MEV_ADMIN_API_HOST / MEV_ADMIN_API_PORT).
|
||||
#
|
||||
# Usage (repo root, LAN + SSH to Proxmox):
|
||||
# bash scripts/deployment/sync-mev-control-gui-defi-oracle.sh [--dry-run]
|
||||
# Then:
|
||||
# bash scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.sh
|
||||
# bash scripts/cloudflare/set-mev-defi-oracle-dns.sh # if using Cloudflare zone defi-oracle.io
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
source "$PROJECT_ROOT/config/ip-addresses.conf" 2>/dev/null || true
|
||||
if [[ -f "$PROJECT_ROOT/.env" ]]; then
|
||||
set +u
|
||||
# shellcheck source=/dev/null
|
||||
source "$PROJECT_ROOT/.env" 2>/dev/null || true
|
||||
set -u
|
||||
fi
|
||||
source "$PROJECT_ROOT/config/ip-addresses.conf" 2>/dev/null || true
|
||||
|
||||
PROXMOX_HOST="${PROXMOX_HOST:-${PROXMOX_HOST_R630_01:-192.168.11.11}}"
|
||||
VMID="${MEV_DEFI_ORACLE_WEB_VMID:-${INFO_DEFI_ORACLE_WEB_VMID:-2410}}"
|
||||
APP_DIR="${MEV_DEFI_ORACLE_WEB_ROOT:-/var/www/mev.defi-oracle.io/html}"
|
||||
SITE_AVAILABLE="${MEV_DEFI_ORACLE_NGINX_SITE:-/etc/nginx/sites-available/mev-defi-oracle}"
|
||||
NGINX_TEMPLATE="${PROJECT_ROOT}/config/nginx/mev-defi-oracle-io.site.conf.template"
|
||||
MEV_ADMIN_API_HOST="${MEV_ADMIN_API_HOST:-192.168.11.11}"
|
||||
MEV_ADMIN_API_PORT="${MEV_ADMIN_API_PORT:-9090}"
|
||||
MEV_ADMIN_UPSTREAM="http://${MEV_ADMIN_API_HOST}:${MEV_ADMIN_API_PORT}"
|
||||
|
||||
GUI_DIR="${PROJECT_ROOT}/MEV_Bot/mev-platform/gui"
|
||||
SSH_OPTS="-o BatchMode=yes -o ConnectTimeout=15 -o StrictHostKeyChecking=accept-new"
|
||||
DRY_RUN=false
|
||||
[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=true
|
||||
|
||||
if [[ ! -d "$GUI_DIR" ]]; then
|
||||
echo "ERROR: Missing $GUI_DIR (init MEV_Bot submodule: git submodule update --init MEV_Bot)" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "$NGINX_TEMPLATE" ]]; then
|
||||
echo "ERROR: Missing $NGINX_TEMPLATE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Sync MEV Control GUI → mev.defi-oracle.io (VMID ${VMID}) ==="
|
||||
echo "Proxmox: ${PROXMOX_HOST} Admin API upstream: ${MEV_ADMIN_UPSTREAM}"
|
||||
|
||||
TMP_NGINX="$(mktemp)"
|
||||
TMP_TGZ="${TMPDIR:-/tmp}/mev-gui-sync-$$.tgz"
|
||||
REMOTE_TGZ="/tmp/mev-gui-sync-$$.tgz"
|
||||
REMOTE_NGINX="/tmp/mev-defi-oracle-site-$$.conf"
|
||||
CT_TGZ="/tmp/mev-gui-sync.tgz"
|
||||
cleanup() { rm -f "$TMP_NGINX" "$TMP_TGZ"; }
|
||||
trap cleanup EXIT
|
||||
sed "s@__MEV_ADMIN_API_UPSTREAM__@${MEV_ADMIN_UPSTREAM}@g" "$NGINX_TEMPLATE" >"$TMP_NGINX"
|
||||
|
||||
if $DRY_RUN; then
|
||||
echo "[DRY-RUN] npm ci + npm run build in $GUI_DIR"
|
||||
echo "[DRY-RUN] tar dist -> tgz, scp, pct push ${VMID}, nginx site -> ${SITE_AVAILABLE}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! command -v npm >/dev/null 2>&1; then
|
||||
echo "ERROR: npm is required to build the GUI." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Building GUI..."
|
||||
(cd "$GUI_DIR" && npm ci && npm run build)
|
||||
|
||||
echo "Packaging dist/..."
|
||||
tar czf "$TMP_TGZ" -C "$GUI_DIR/dist" .
|
||||
|
||||
echo "Copying to Proxmox host ${PROXMOX_HOST}..."
|
||||
scp $SSH_OPTS "$TMP_TGZ" "root@${PROXMOX_HOST}:${REMOTE_TGZ}"
|
||||
scp $SSH_OPTS "$TMP_NGINX" "root@${PROXMOX_HOST}:${REMOTE_NGINX}"
|
||||
|
||||
echo "Installing into VMID ${VMID}..."
|
||||
ssh $SSH_OPTS "root@${PROXMOX_HOST}" bash -s "$VMID" "$REMOTE_TGZ" "$CT_TGZ" "$APP_DIR" "$SITE_AVAILABLE" "$REMOTE_NGINX" <<'REMOTE'
|
||||
set -euo pipefail
|
||||
VMID="$1"
|
||||
REMOTE_TGZ="$2"
|
||||
CT_TGZ="$3"
|
||||
APP_DIR="$4"
|
||||
SITE_FILE="$5"
|
||||
REMOTE_NGINX="$6"
|
||||
|
||||
pct push "$VMID" "$REMOTE_TGZ" "$CT_TGZ"
|
||||
rm -f "$REMOTE_TGZ"
|
||||
|
||||
pct exec "$VMID" -- bash -lc "mkdir -p '$APP_DIR' && rm -rf '${APP_DIR}'/* && tar xzf '$CT_TGZ' -C '$APP_DIR' && rm -f '$CT_TGZ'"
|
||||
|
||||
pct push "$VMID" "$REMOTE_NGINX" "$SITE_FILE"
|
||||
rm -f "$REMOTE_NGINX"
|
||||
|
||||
pct exec "$VMID" -- bash -lc "mkdir -p /var/www/mev.defi-oracle.io/html && ln -sf '$SITE_FILE' /etc/nginx/sites-enabled/mev-defi-oracle"
|
||||
pct exec "$VMID" -- nginx -t
|
||||
pct exec "$VMID" -- systemctl reload nginx
|
||||
sleep 1
|
||||
pct exec "$VMID" -- curl -fsS -H 'Host: mev.defi-oracle.io' "http://127.0.0.1/health" | grep -q mev-gui-healthy
|
||||
REMOTE
|
||||
|
||||
echo ""
|
||||
echo "✅ GUI synced. Next:"
|
||||
echo " NPM: bash scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.sh"
|
||||
echo " DNS: bash scripts/cloudflare/set-mev-defi-oracle-dns.sh"
|
||||
echo " Ensure mev-admin-api listens on ${MEV_ADMIN_UPSTREAM} (reachable from CT ${VMID})."
|
||||
@@ -403,6 +403,11 @@ INFO_DEFI_ORACLE_UPSTREAM_IP="${INFO_DEFI_ORACLE_UPSTREAM_IP:-${IP_INFO_DEFI_ORA
|
||||
INFO_DEFI_ORACLE_UPSTREAM_PORT="${INFO_DEFI_ORACLE_UPSTREAM_PORT:-80}"
|
||||
update_proxy_host "info.defi-oracle.io" "http://${INFO_DEFI_ORACLE_UPSTREAM_IP}:${INFO_DEFI_ORACLE_UPSTREAM_PORT}" false false && updated_count=$((updated_count + 1)) || { add_proxy_host "info.defi-oracle.io" "${INFO_DEFI_ORACLE_UPSTREAM_IP}" "${INFO_DEFI_ORACLE_UPSTREAM_PORT}" false false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
|
||||
update_proxy_host "www.info.defi-oracle.io" "http://${INFO_DEFI_ORACLE_UPSTREAM_IP}:${INFO_DEFI_ORACLE_UPSTREAM_PORT}" false false "https://info.defi-oracle.io" && updated_count=$((updated_count + 1)) || { add_proxy_host "www.info.defi-oracle.io" "${INFO_DEFI_ORACLE_UPSTREAM_IP}" "${INFO_DEFI_ORACLE_UPSTREAM_PORT}" false false "https://info.defi-oracle.io" && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
|
||||
# mev.defi-oracle.io — MEV Control GUI on same nginx LXC as info (default VMID 2410); nginx proxies /api to MEV_ADMIN_API_*.
|
||||
MEV_DEFI_ORACLE_UPSTREAM_IP="${MEV_DEFI_ORACLE_UPSTREAM_IP:-${IP_INFO_DEFI_ORACLE_WEB:-192.168.11.218}}"
|
||||
MEV_DEFI_ORACLE_UPSTREAM_PORT="${MEV_DEFI_ORACLE_UPSTREAM_PORT:-80}"
|
||||
update_proxy_host "mev.defi-oracle.io" "http://${MEV_DEFI_ORACLE_UPSTREAM_IP}:${MEV_DEFI_ORACLE_UPSTREAM_PORT}" false false && updated_count=$((updated_count + 1)) || { add_proxy_host "mev.defi-oracle.io" "${MEV_DEFI_ORACLE_UPSTREAM_IP}" "${MEV_DEFI_ORACLE_UPSTREAM_PORT}" false false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
|
||||
update_proxy_host "www.mev.defi-oracle.io" "http://${MEV_DEFI_ORACLE_UPSTREAM_IP}:${MEV_DEFI_ORACLE_UPSTREAM_PORT}" false false "https://mev.defi-oracle.io" && updated_count=$((updated_count + 1)) || { add_proxy_host "www.mev.defi-oracle.io" "${MEV_DEFI_ORACLE_UPSTREAM_IP}" "${MEV_DEFI_ORACLE_UPSTREAM_PORT}" false false "https://mev.defi-oracle.io" && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
|
||||
# Optional relay health publication for off-LAN monitors like Promod.
|
||||
# Set CCIP_RELAY_MAINNET_CW_PUBLIC_HOST in .env to publish the mainnet-cw health
|
||||
# endpoint through NPMplus without exposing a raw UDM Pro port-forward.
|
||||
|
||||
Reference in New Issue
Block a user