Sync workspace: config, docs, scripts, CI, operator rules, and submodule pointers.

- Update dbis_core, cross-chain-pmm-lps, explorer-monorepo, metamask-integration, pr-workspace/chains
- Omit embedded publish git dirs and empty placeholders from index

Made-with: Cursor
This commit is contained in:
defiQUG
2026-04-12 06:12:20 -07:00
parent 6fb6bd3993
commit dbd517b279
2935 changed files with 327972 additions and 5533 deletions

View File

@@ -18,19 +18,75 @@ NPM_URL="${NPM_URL:-https://192.168.11.167:81}"
[ -z "$NPM_PASSWORD" ] && echo "Set NPM_PASSWORD (e.g. source .env)" && exit 1
# Remote script: no jq required (Proxmox host may not have it)
# Remote script: use Python's JSON parser so reruns are idempotent and preserve cert / Force SSL state.
REMOTE_SCRIPT='
set -euo pipefail
[ -z "${NPM_PASSWORD:-}" ] && echo "NPM_PASSWORD not set on remote" && exit 1
AUTH_JSON="{\"identity\":\"$NPM_EMAIL\",\"secret\":\"$NPM_PASSWORD\"}"
TOKEN_RESP=$(curl -sk -X POST "$NPM_URL/api/tokens" -H "Content-Type: application/json" -d "$AUTH_JSON")
TOKEN=$(echo "$TOKEN_RESP" | sed -n "s/.*\"token\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" | head -1)
[ -z "$TOKEN" ] && TOKEN=$(echo "$TOKEN_RESP" | sed -n "s/.*\"accessToken\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" | head -1)
[ -z "$TOKEN" ] && echo "Auth failed. Response: $TOKEN_RESP" && exit 1
BODY="{\"domain_names\":[\"dapp.d-bis.org\"],\"forward_scheme\":\"http\",\"forward_host\":\"$DAPP_IP\",\"forward_port\":80,\"allow_websocket_upgrade\":true,\"certificate_id\":null,\"ssl_forced\":false}"
resp=$(curl -sk -X POST "$NPM_URL/api/nginx/proxy-hosts" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "$BODY")
if echo "$resp" | grep -q "\"id\""; then echo "Added: dapp.d-bis.org -> $DAPP_IP:80"; else echo "Create failed: $resp"; exit 1; fi
echo "Request SSL in NPMplus UI for dapp.d-bis.org and enable Force SSL."
python3 - <<'"'"'PY'"'"'
import json
import os
import subprocess
import sys
def curl(*args):
return subprocess.check_output(["curl", "-skS", *args], text=True)
npm_password = os.environ.get("NPM_PASSWORD", "")
if not npm_password:
print("NPM_PASSWORD not set on remote")
sys.exit(1)
auth_payload = json.dumps({
"identity": os.environ.get("NPM_EMAIL", "admin@example.org"),
"secret": npm_password,
})
token_resp = json.loads(curl("-X", "POST", f"{os.environ['NPM_URL']}/api/tokens", "-H", "Content-Type: application/json", "-d", auth_payload))
token = token_resp.get("token") or token_resp.get("accessToken") or (token_resp.get("data") or {}).get("token")
if not token:
print(f"Auth failed. Response: {json.dumps(token_resp)}")
sys.exit(1)
auth_header = ["-H", f"Authorization: Bearer {token}"]
hosts_resp = json.loads(curl("-X", "GET", f"{os.environ['NPM_URL']}/api/nginx/proxy-hosts", *auth_header))
if isinstance(hosts_resp, dict):
hosts = hosts_resp.get("data") or hosts_resp.get("proxy_hosts") or []
else:
hosts = hosts_resp
existing = None
for host in hosts:
if "dapp.d-bis.org" in (host.get("domain_names") or []):
existing = host
break
payload = {
"domain_names": ["dapp.d-bis.org"],
"forward_scheme": "http",
"forward_host": os.environ["DAPP_IP"],
"forward_port": 80,
"allow_websocket_upgrade": True,
"block_exploits": False,
}
if existing:
payload["certificate_id"] = existing.get("certificate_id")
payload["ssl_forced"] = existing.get("ssl_forced", False)
resp = json.loads(curl("-X", "PUT", f"{os.environ['NPM_URL']}/api/nginx/proxy-hosts/{existing['id']}", *auth_header, "-H", "Content-Type: application/json", "-d", json.dumps(payload)))
if resp.get("id"):
print(f"Updated: dapp.d-bis.org -> {os.environ['DAPP_IP']}:80")
sys.exit(0)
print(f"Update failed: {json.dumps(resp)}")
sys.exit(1)
payload["certificate_id"] = None
payload["ssl_forced"] = False
resp = json.loads(curl("-X", "POST", f"{os.environ['NPM_URL']}/api/nginx/proxy-hosts", *auth_header, "-H", "Content-Type: application/json", "-d", json.dumps(payload)))
if resp.get("id"):
print(f"Added: dapp.d-bis.org -> {os.environ['DAPP_IP']}:80")
print("Request SSL in NPMplus UI for dapp.d-bis.org and enable Force SSL if this is a new host.")
sys.exit(0)
print(f"Create failed: {json.dumps(resp)}")
sys.exit(1)
PY
'
echo "Running NPM add proxy host from Proxmox host $PROXMOX_HOST (must be on same LAN as NPMplus 192.168.11.167)..."
@@ -39,7 +95,7 @@ PASS_ESC="${NPM_PASSWORD//\'/\'\\\'\'}"
OUTPUT=$(ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new root@"$PROXMOX_HOST" \
"export NPM_EMAIL='${NPM_EMAIL//\'/\'\\\'\'}' NPM_PASSWORD='$PASS_ESC' NPM_URL='$NPM_URL' DAPP_IP='$DAPP_IP'; bash -s" <<< "$REMOTE_SCRIPT" 2>&1) || true
echo "$OUTPUT"
if ! echo "$OUTPUT" | grep -q "Added: dapp.d-bis.org"; then
if ! echo "$OUTPUT" | grep -qE "Added: dapp.d-bis.org|Updated: dapp.d-bis.org"; then
echo "Failed. Ensure this machine can SSH to $PROXMOX_HOST and that host can reach $NPM_URL (same LAN)." >&2
exit 1
fi

View File

@@ -17,9 +17,27 @@ AUTH_JSON=$(jq -n --arg identity "$NPM_EMAIL" --arg secret "$NPM_PASSWORD" '{ide
TOKEN_RESP=$(curl -sk -X POST "$NPM_URL/api/tokens" -H "Content-Type: application/json" -d "$AUTH_JSON" -c "$COOKIE_JAR")
TOKEN=$(echo "$TOKEN_RESP" | jq -r '.token // .accessToken // .data.token // empty')
if [ -z "$TOKEN" ]; then echo "Auth failed"; echo "$TOKEN_RESP" | jq . 2>/dev/null; exit 1; fi
PROXY_HOSTS_JSON=$(curl -sk -X GET "$NPM_URL/api/nginx/proxy-hosts" -H "Authorization: Bearer $TOKEN")
HOSTS_ARRAY=$(echo "$PROXY_HOSTS_JSON" | jq -c 'if type == "array" then . elif .data != null then .data elif .proxy_hosts != null then .proxy_hosts else [] end' 2>/dev/null)
EXISTING_ID=$(echo "$HOSTS_ARRAY" | jq -r '.[] | select(.domain_names[]? == "dapp.d-bis.org") | .id' 2>/dev/null | head -1)
EXISTING_HOST_JSON=$(echo "$HOSTS_ARRAY" | jq -c '.[] | select(.domain_names[]? == "dapp.d-bis.org")' 2>/dev/null | head -1)
if [ -n "$EXISTING_ID" ] && [ "$EXISTING_ID" != "null" ]; then
PRESERVED_CERTIFICATE_ID=$(printf '%s' "$EXISTING_HOST_JSON" | jq -c '.certificate_id // null' 2>/dev/null || echo "null")
PRESERVED_SSL_FORCED=$(printf '%s' "$EXISTING_HOST_JSON" | jq -c '.ssl_forced // false' 2>/dev/null || echo "false")
BODY=$(jq -n --arg domain "dapp.d-bis.org" --arg host "$DAPP_IP" --argjson port 80 --argjson certificate_id "$PRESERVED_CERTIFICATE_ID" --argjson ssl_forced "$PRESERVED_SSL_FORCED" \
'{domain_names:[$domain],forward_scheme:"http",forward_host:$host,forward_port:$port,allow_websocket_upgrade:true,block_exploits:false,certificate_id:$certificate_id,ssl_forced:$ssl_forced}')
resp=$(curl -sk -X PUT "$NPM_URL/api/nginx/proxy-hosts/$EXISTING_ID" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "$BODY")
id=$(echo "$resp" | jq -r '.id // empty')
if [ -n "$id" ] && [ "$id" != "null" ]; then
echo "Updated: dapp.d-bis.org -> $DAPP_IP:80"
exit 0
fi
echo "$resp" | jq . 2>/dev/null
exit 1
fi
BODY=$(jq -n --arg domain "dapp.d-bis.org" --arg host "$DAPP_IP" --argjson port 80 \
'{domain_names:[$domain],forward_scheme:"http",forward_host:$host,forward_port:$port,allow_websocket_upgrade:true,certificate_id:null,ssl_forced:false}')
'{domain_names:[$domain],forward_scheme:"http",forward_host:$host,forward_port:$port,allow_websocket_upgrade:true,block_exploits:false,certificate_id:null,ssl_forced:false}')
resp=$(curl -sk -X POST "$NPM_URL/api/nginx/proxy-hosts" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "$BODY")
id=$(echo "$resp" | jq -r '.id // empty')
if [ -n "$id" ]; then echo "Added: dapp.d-bis.org -> $DAPP_IP:80"; else echo "$resp" | jq . 2>/dev/null; exit 1; fi
echo "Request SSL in NPMplus UI for dapp.d-bis.org and enable Force SSL."
echo "Request SSL in NPMplus UI for dapp.d-bis.org and enable Force SSL if this is a new host."

View File

@@ -57,6 +57,27 @@ curl_auth() {
fi
}
fetch_proxy_hosts_json() {
curl_auth -X GET "$NPM_URL/api/nginx/proxy-hosts"
}
resolve_proxy_host_id() {
local domain=$1
local hosts_json=${2:-}
[ -z "$hosts_json" ] && hosts_json=$(fetch_proxy_hosts_json)
echo "$hosts_json" | jq -r --arg dom "$domain" '
if type == "array" then .
elif .data != null then .data
elif .proxy_hosts != null then .proxy_hosts
else []
end
| .[]
| select(.domain_names | type == "array")
| select(.domain_names[] == $dom)
| .id
' 2>/dev/null | head -n1
}
add_proxy_host() {
local domain=$1
local fwd_port=$2
@@ -90,11 +111,96 @@ add_proxy_host() {
fi
}
update_proxy_host() {
local domain=$1
local fwd_port=$2
local hosts_json
hosts_json=$(fetch_proxy_hosts_json)
local arr
arr=$(echo "$hosts_json" | jq -c '
if type == "array" then .
elif .data != null then .data
elif .proxy_hosts != null then .proxy_hosts
else []
end
' 2>/dev/null)
[ -z "$arr" ] && return 1
local id
id=$(echo "$arr" | jq -r --arg dom "$domain" '
.[]
| select(.domain_names | type == "array")
| select(.domain_names[] == $dom)
| .id
' 2>/dev/null | head -n1)
if [ -z "$id" ] || [ "$id" = "null" ]; then
return 1
fi
local payload
payload=$(jq -n \
--arg host "$IP_GOV_PORTALS_DEV" \
--argjson port "$fwd_port" \
'{ forward_scheme: "http", forward_host: $host, forward_port: $port, allow_websocket_upgrade: false, block_exploits: false }')
local resp
resp=$(curl_auth -X PUT "$NPM_URL/api/nginx/proxy-hosts/$id" -H "Content-Type: application/json" -d "$payload")
local out_id
out_id=$(echo "$resp" | jq -r '.id // empty' 2>/dev/null)
if [ -n "$out_id" ] && [ "$out_id" != "null" ]; then
echo " Updated: $domain -> $IP_GOV_PORTALS_DEV:$fwd_port"
return 0
fi
local host_obj
host_obj=$(echo "$arr" | jq -c --arg dom "$domain" '
.[]
| select(.domain_names | type == "array")
| select(.domain_names[] == $dom)
' 2>/dev/null | head -n1)
if [ -n "$host_obj" ]; then
payload=$(echo "$host_obj" | jq -c --arg host "$IP_GOV_PORTALS_DEV" --argjson port "$fwd_port" '
{
domain_names,
forward_scheme,
forward_host: $host,
forward_port: $port,
allow_websocket_upgrade,
block_exploits,
certificate_id,
ssl_forced,
caching_enabled,
advanced_config,
access_list_id,
enabled,
http2_support,
hsts_enabled,
hsts_subdomains
}
' 2>/dev/null)
if [ -n "$payload" ]; then
resp=$(curl_auth -X PUT "$NPM_URL/api/nginx/proxy-hosts/$id" -H "Content-Type: application/json" -d "$payload")
out_id=$(echo "$resp" | jq -r '.id // empty' 2>/dev/null)
if [ -n "$out_id" ] && [ "$out_id" != "null" ]; then
echo " Updated: $domain -> $IP_GOV_PORTALS_DEV:$fwd_port"
return 0
fi
fi
fi
echo " Warning: could not update $domain via API."
return 1
}
add_or_update_proxy_host() {
local domain=$1
local fwd_port=$2
if add_proxy_host "$domain" "$fwd_port"; then
return 0
fi
update_proxy_host "$domain" "$fwd_port"
}
# Four portals on xom-dev.phoenix.sankofa.nexus
add_proxy_host "dbis.xom-dev.phoenix.sankofa.nexus" 3001 || true
add_proxy_host "iccc.xom-dev.phoenix.sankofa.nexus" 3002 || true
add_proxy_host "omnl.xom-dev.phoenix.sankofa.nexus" 3003 || true
add_proxy_host "xom.xom-dev.phoenix.sankofa.nexus" 3004 || true
add_or_update_proxy_host "dbis.xom-dev.phoenix.sankofa.nexus" 3001 || true
add_or_update_proxy_host "iccc.xom-dev.phoenix.sankofa.nexus" 3002 || true
add_or_update_proxy_host "omnl.xom-dev.phoenix.sankofa.nexus" 3003 || true
add_or_update_proxy_host "xom.xom-dev.phoenix.sankofa.nexus" 3004 || true
echo ""
echo "Done. Request Let's Encrypt certs in NPMplus UI for: dbis/iccc/omnl/xom.xom-dev.phoenix.sankofa.nexus"

View File

@@ -40,13 +40,18 @@ fi
PROXY_HOSTS_JSON=$(curl -s -k -X GET "$NPM_URL/api/nginx/proxy-hosts" -H "Authorization: Bearer $TOKEN")
EXISTING_ID=$(echo "$PROXY_HOSTS_JSON" | jq -r ".[] | select(.domain_names[]? == \"$DOMAIN\") | .id" 2>/dev/null | head -1)
EXISTING_HOST_JSON=$(echo "$PROXY_HOSTS_JSON" | jq -c ".[] | select(.domain_names[]? == \"$DOMAIN\")" 2>/dev/null | head -1)
if [ -n "$EXISTING_ID" ] && [ "$EXISTING_ID" != "null" ]; then
echo "✓ Proxy host for $DOMAIN already exists (ID: $EXISTING_ID). Updating target to ${IP_SANKOFA_STUDIO}:${PORT}..."
PRESERVED_CERTIFICATE_ID=$(printf '%s' "$EXISTING_HOST_JSON" | jq -c '.certificate_id // null' 2>/dev/null || echo "null")
PRESERVED_SSL_FORCED=$(printf '%s' "$EXISTING_HOST_JSON" | jq -c '.ssl_forced // false' 2>/dev/null || echo "false")
PAYLOAD=$(jq -n \
--arg domain "$DOMAIN" \
--arg host "$IP_SANKOFA_STUDIO" \
--argjson port "$PORT" \
--argjson certificate_id "$PRESERVED_CERTIFICATE_ID" \
--argjson ssl_forced "$PRESERVED_SSL_FORCED" \
'{
domain_names: [$domain],
forward_scheme: "http",
@@ -54,15 +59,16 @@ if [ -n "$EXISTING_ID" ] && [ "$EXISTING_ID" != "null" ]; then
forward_port: $port,
allow_websocket_upgrade: false,
block_exploits: false,
certificate_id: null,
ssl_forced: false
certificate_id: $certificate_id,
ssl_forced: $ssl_forced,
advanced_config: ""
}')
RESP=$(curl -s -k -X PUT "$NPM_URL/api/nginx/proxy-hosts/$EXISTING_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "$PAYLOAD")
if echo "$RESP" | jq -e '.id' >/dev/null 2>&1; then
echo "✓ Updated $DOMAIN → http://${IP_SANKOFA_STUDIO}:${PORT}"
echo "✓ Updated $DOMAIN → http://${IP_SANKOFA_STUDIO}:${PORT} (preserved certificate_id=${PRESERVED_CERTIFICATE_ID}, ssl_forced=${PRESERVED_SSL_FORCED})"
else
echo "❌ Update failed: $(echo "$RESP" | jq -r '.message // .error // "unknown"' 2>/dev/null)"
exit 1
@@ -80,7 +86,8 @@ else
allow_websocket_upgrade: false,
block_exploits: false,
certificate_id: null,
ssl_forced: false
ssl_forced: false,
advanced_config: ""
}')
RESP=$(curl -s -k -X POST "$NPM_URL/api/nginx/proxy-hosts" \
-H "Authorization: Bearer $TOKEN" \
@@ -95,5 +102,5 @@ else
fi
fi
echo ""
echo "Next: Request SSL in NPMplus UI for $DOMAIN and enable Force SSL."
echo "Next: Request SSL in NPMplus UI for $DOMAIN and enable Force SSL if this is a new host."
echo "DNS: Ensure studio.sankofa.nexus resolves (e.g. run scripts/cloudflare/add-studio-sankofa-dns.sh)."

View File

@@ -36,10 +36,10 @@ const DOMAINS = [
// d-bis.org zone
{ domain: 'explorer.d-bis.org', target: 'http://192.168.11.140:4000', description: 'Blockscout Explorer (Direct Route)' },
{ domain: 'rpc-http-pub.d-bis.org', target: 'https://192.168.11.252:443', description: 'RPC HTTP Public' },
{ domain: 'rpc-ws-pub.d-bis.org', target: 'https://192.168.11.252:443', description: 'RPC WebSocket Public' },
{ domain: 'rpc-http-prv.d-bis.org', target: 'https://192.168.11.251:443', description: 'RPC HTTP Private' },
{ domain: 'rpc-ws-prv.d-bis.org', target: 'https://192.168.11.251:443', description: 'RPC WebSocket Private' },
{ domain: 'rpc-http-pub.d-bis.org', target: 'http://192.168.11.221:8545', description: 'RPC HTTP Public' },
{ domain: 'rpc-ws-pub.d-bis.org', target: 'http://192.168.11.221:8546', description: 'RPC WebSocket Public' },
{ domain: 'rpc-http-prv.d-bis.org', target: 'http://192.168.11.211:8545', description: 'RPC HTTP Private' },
{ domain: 'rpc-ws-prv.d-bis.org', target: 'http://192.168.11.211:8546', description: 'RPC WebSocket Private' },
{ domain: 'dbis-admin.d-bis.org', target: 'http://192.168.11.130:80', description: 'DBIS Admin' },
{ domain: 'dbis-api.d-bis.org', target: 'http://192.168.11.155:3000', description: 'DBIS API' },
{ domain: 'dbis-api-2.d-bis.org', target: 'http://192.168.11.156:3000', description: 'DBIS API 2' },
@@ -53,7 +53,7 @@ const DOMAINS = [
{ domain: 'training.mim4u.org', target: 'http://192.168.11.37:80', description: 'MIM4U Training' },
// defi-oracle.io zone
{ domain: 'rpc.public-0138.defi-oracle.io', target: 'https://192.168.11.252:443', description: 'ThirdWeb RPC' },
{ domain: 'rpc.public-0138.defi-oracle.io', target: 'https://192.168.11.240:443', description: 'ThirdWeb RPC' },
];
// Helper functions

View File

@@ -32,10 +32,10 @@ const DOMAINS = [
// d-bis.org zone
{ domain: 'explorer.d-bis.org', target: 'http://192.168.11.140:4000', websocket: false },
{ domain: 'rpc-http-pub.d-bis.org', target: 'https://192.168.11.252:443', websocket: true },
{ domain: 'rpc-ws-pub.d-bis.org', target: 'https://192.168.11.252:443', websocket: true },
{ domain: 'rpc-http-prv.d-bis.org', target: 'https://192.168.11.251:443', websocket: true },
{ domain: 'rpc-ws-prv.d-bis.org', target: 'https://192.168.11.251:443', websocket: true },
{ domain: 'rpc-http-pub.d-bis.org', target: 'http://192.168.11.221:8545', websocket: true },
{ domain: 'rpc-ws-pub.d-bis.org', target: 'http://192.168.11.221:8546', websocket: true },
{ domain: 'rpc-http-prv.d-bis.org', target: 'http://192.168.11.211:8545', websocket: true },
{ domain: 'rpc-ws-prv.d-bis.org', target: 'http://192.168.11.211:8546', websocket: true },
{ domain: 'dbis-admin.d-bis.org', target: 'http://192.168.11.130:80', websocket: false },
{ domain: 'dbis-api.d-bis.org', target: 'http://192.168.11.155:3000', websocket: false },
{ domain: 'dbis-api-2.d-bis.org', target: 'http://192.168.11.156:3000', websocket: false },
@@ -49,7 +49,7 @@ const DOMAINS = [
{ domain: 'training.mim4u.org', target: 'http://192.168.11.37:80', websocket: false },
// defi-oracle.io zone
{ domain: 'rpc.public-0138.defi-oracle.io', target: 'https://192.168.11.252:443', websocket: true },
{ domain: 'rpc.public-0138.defi-oracle.io', target: 'https://192.168.11.240:443', websocket: true },
];
// Helper functions

View File

@@ -0,0 +1,428 @@
#!/usr/bin/env bash
# Fix NPMplus SSL gaps reported by audit-npmplus-ssl-all-instances.sh (primary instance by default).
# - Hosts with a cert but ssl_forced=false: enable Force SSL + HSTS only for non-RPC endpoints (see should_force_ssl).
# - Hosts with no cert: request Let's Encrypt cert via NPM API, assign to host (ssl_forced per same rule).
# - Hosts with expired certs: request a fresh cert (or reuse an already-valid one) and reassign it.
# - Hosts bound to the wrong certificate: request/reuse a matching cert and reassign it.
# Supports both JSON bearer-token auth and cookie-session auth returned by some
# NPMplus UIs.
# Skips: disabled hosts; domain names containing * (wildcard — request certs in UI); dry-run.
#
# Usage: bash scripts/nginx-proxy-manager/fix-npmplus-ssl-issues.sh [--dry-run]
# Env: NPM_URL (default https://IP_NPMPLUS:81), NPM_EMAIL, NPM_PASSWORD, NPM_CURL_MAX_TIME
# NPM_SSL_FIX_SLEEP_LE=5 seconds between LE certificate requests (default 5)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# shellcheck source=/dev/null
source "${PROJECT_ROOT}/config/ip-addresses.conf" 2>/dev/null || true
DRY_RUN=0
for _a in "$@"; do [[ "$_a" == "--dry-run" ]] && DRY_RUN=1; done
_orig_npm_url="${NPM_URL:-}"
_orig_npm_email="${NPM_EMAIL:-}"
_orig_npm_password="${NPM_PASSWORD:-}"
_orig_npm_instance_label="${NPM_INSTANCE_LABEL:-}"
if [ -f "$PROJECT_ROOT/.env" ]; then
set +u
# shellcheck source=/dev/null
source "$PROJECT_ROOT/.env"
set -u
fi
[ -n "$_orig_npm_url" ] && NPM_URL="$_orig_npm_url"
[ -n "$_orig_npm_email" ] && NPM_EMAIL="$_orig_npm_email"
[ -n "$_orig_npm_password" ] && NPM_PASSWORD="$_orig_npm_password"
[ -n "$_orig_npm_instance_label" ] && NPM_INSTANCE_LABEL="$_orig_npm_instance_label"
# shellcheck source=/dev/null
source "${PROJECT_ROOT}/config/ip-addresses.conf" 2>/dev/null || true
NPM_URL="${NPM_URL:-https://${IP_NPMPLUS:-192.168.11.167}:81}"
NPM_EMAIL="${NPM_EMAIL:-}"
NPM_CURL_MAX_TIME="${NPM_CURL_MAX_TIME:-300}"
NPM_SSL_FIX_SLEEP_LE="${NPM_SSL_FIX_SLEEP_LE:-5}"
password_for_label() {
case "$1" in
primary) echo "${NPM_PASSWORD_PRIMARY:-${NPM_PASSWORD:-}}" ;;
secondary) echo "${NPM_PASSWORD_SECONDARY:-${NPM_PASSWORD:-}}" ;;
alltra-hybx) echo "${NPM_PASSWORD_ALLTRA_HYBX:-${NPM_PASSWORD:-}}" ;;
fourth-dev) echo "${NPM_PASSWORD_FOURTH:-${NPM_PASSWORD:-}}" ;;
mifos) echo "${NPM_PASSWORD_MIFOS:-${NPM_PASSWORD:-}}" ;;
*) echo "${NPM_PASSWORD:-}" ;;
esac
}
instance_label_from_url() {
local url="${1:-}"
local label="${NPM_INSTANCE_LABEL:-}"
if [ -n "$label" ]; then
echo "$label"
return 0
fi
case "$url" in
*"${IP_NPMPLUS_SECONDARY:-192.168.11.168}"*) echo "secondary" ;;
*"${IP_NPMPLUS_ALLTRA_HYBX:-192.168.11.169}"*) echo "alltra-hybx" ;;
*"${IP_NPMPLUS_FOURTH:-192.168.11.170}"*) echo "fourth-dev" ;;
*"${IP_NPMPLUS_MIFOS:-192.168.11.171}"*) echo "mifos" ;;
*) echo "primary" ;;
esac
}
NPM_INSTANCE_LABEL="$(instance_label_from_url "$NPM_URL")"
NPM_PASSWORD="$(password_for_label "$NPM_INSTANCE_LABEL")"
if [ -z "$NPM_PASSWORD" ]; then
echo "NPM_PASSWORD required (.env or export)." >&2
exit 1
fi
curl_npm() { curl -s -k -L --connect-timeout 10 --max-time "$NPM_CURL_MAX_TIME" "$@"; }
AUTH_TOKEN=""
AUTH_COOKIE_JAR=""
cleanup_auth() {
[ -n "${AUTH_COOKIE_JAR:-}" ] && [ -f "$AUTH_COOKIE_JAR" ] && rm -f "$AUTH_COOKIE_JAR"
}
trap cleanup_auth EXIT
extract_json_token() {
local body="${1:-}"
echo "$body" | jq -r '.token // empty' 2>/dev/null || true
}
cookie_has_session() {
local jar="$1"
[ -f "$jar" ] && grep -q $'\ttoken\t' "$jar"
}
npm_api() {
if [ -n "$AUTH_TOKEN" ]; then
curl_npm -H "Authorization: Bearer $AUTH_TOKEN" "$@"
else
curl_npm -b "$AUTH_COOKIE_JAR" -c "$AUTH_COOKIE_JAR" "$@"
fi
}
# 0 = force SSL OK for this host; 1 = keep ssl_forced false (JSON-RPC / WS edge)
should_force_ssl() {
local domain="${1,,}"
local port="${2:-0}"
case "$port" in 8545|8546) return 1 ;; esac
[[ "$domain" == *"*"* ]] && return 1
if [[ "$domain" =~ ^(rpc\.|ws\.|wss\.) ]]; then return 1; fi
if [[ "$domain" =~ rpc-http- ]]; then return 1; fi
if [[ "$domain" =~ rpc-ws- ]]; then return 1; fi
if [[ "$domain" =~ rpc-core ]]; then return 1; fi
if [[ "$domain" =~ rpc-fireblocks ]]; then return 1; fi
if [[ "$domain" =~ rpc\.public-0138 ]]; then return 1; fi
if [[ "$domain" == "rpc.d-bis.org" || "$domain" == "rpc2.d-bis.org" ]]; then return 1; fi
if [[ "$domain" =~ \.tw-core\. ]]; then return 1; fi
if [[ "$domain" =~ ^rpc\.defi-oracle ]]; then return 1; fi
if [[ "$domain" =~ ^wss\.defi-oracle ]]; then return 1; fi
return 0
}
log() { echo "[fix-npmplus-ssl] $*"; }
try_connect() {
curl_npm -o /dev/null -w "%{http_code}" "$1/" 2>/dev/null | grep -qE '^(200|301|302|401)$'
}
if ! try_connect "$NPM_URL"; then
http_url="${NPM_URL/https:/http:}"
try_connect "$http_url" && NPM_URL="$http_url" || { echo "Cannot reach NPM at $NPM_URL"; exit 1; }
fi
AUTH_JSON=$(jq -n --arg identity "$NPM_EMAIL" --arg secret "$NPM_PASSWORD" '{identity:$identity,secret:$secret}')
AUTH_COOKIE_JAR="$(mktemp)"
AUTH_RESPONSE=$(curl_npm -c "$AUTH_COOKIE_JAR" -X POST "$NPM_URL/api/tokens" -H "Content-Type: application/json" -d "$AUTH_JSON")
AUTH_TOKEN=$(extract_json_token "$AUTH_RESPONSE")
if [ -z "$AUTH_TOKEN" ] || [ "$AUTH_TOKEN" = "null" ]; then
AUTH_TOKEN=""
fi
if [ -z "$AUTH_TOKEN" ] && ! cookie_has_session "$AUTH_COOKIE_JAR"; then
echo "NPM auth failed for $NPM_URL" >&2
exit 1
fi
HOSTS_JSON=$(npm_api -X GET "$NPM_URL/api/nginx/proxy-hosts")
CERTS_JSON=$(npm_api -X GET "$NPM_URL/api/nginx/certificates")
if ! echo "$HOSTS_JSON" | jq -e 'type == "array"' >/dev/null 2>&1 || \
! echo "$CERTS_JSON" | jq -e 'type == "array"' >/dev/null 2>&1; then
echo "NPM auth succeeded but proxy-host or certificate API payload was not usable for $NPM_URL" >&2
exit 1
fi
# NPM PUT rejects full GET payloads ("additional properties"); use minimal SSL update for cert assignment.
cert_id_for_domain() {
local domain="$1"
echo "$CERTS_JSON" | jq -r --arg d "$domain" '
def parse_exp($c):
($c.expires_on // $c.meta.letsencrypt_expiry // $c.meta.expires_on // "") | tostring;
def epoch_if($s):
if $s == "" or $s == "null" then null
else
($s | gsub(" "; "T") | . + "Z" | fromdateiso8601? // null)
end;
[ .[] | select(.domain_names | type == "array" and any(.[]; . == $d)) |
{id, expires_at:(epoch_if(parse_exp(.)))} ] |
map(select(.expires_at == null or .expires_at > now)) |
.[0].id // empty
'
}
cert_is_expired() {
local cert_id="$1"
echo "$CERTS_JSON" | jq -e --argjson cid "$cert_id" '
def parse_exp($c):
($c.expires_on // $c.meta.letsencrypt_expiry // $c.meta.expires_on // "") | tostring;
def epoch_if($s):
if $s == "" or $s == "null" then null
else
($s | gsub(" "; "T") | . + "Z" | fromdateiso8601? // null)
end;
[ .[] | select(.id == $cid) ][0] as $c |
if $c == null then false
else
(epoch_if(parse_exp($c)) as $ep | ($ep != null and $ep < now))
end
' >/dev/null 2>&1
}
cert_matches_domain() {
local cert_id="$1"
local domain="$2"
echo "$CERTS_JSON" | jq -e --argjson cid "$cert_id" --arg d "$domain" '
def host_covered_by_cert($host; $names):
any(($names // [])[]; . == $host or (startswith("*.") and ($host | endswith("." + .[2:])) and ($host != .[2:])));
[ .[] | select(.id == $cid) ][0] as $c |
if $c == null then false
else host_covered_by_cert($d; ($c.domain_names // []))
end
' >/dev/null 2>&1
}
put_host_ssl() {
local hid="$1"
local force="$2"
local body resp upd cid
body=$(echo "$HOSTS_JSON" | jq --argjson id "$hid" --argjson force "$force" '
[.[] | select(.id == $id)][0] |
if . == null then empty
else
.ssl_forced = $force |
.http2_support = true |
.hsts_enabled = $force |
.hsts_subdomains = $force
end
')
if [ -z "$body" ] || [ "$body" = "null" ]; then
log "host id $hid not found in snapshot"
return 1
fi
if [ "$DRY_RUN" = 1 ]; then
log "dry-run: PUT proxy-hosts/$hid ssl_forced=$force"
return 0
fi
resp=$(npm_api -X PUT "$NPM_URL/api/nginx/proxy-hosts/$hid" \
-H "Content-Type: application/json" -d "$body")
if echo "$resp" | jq -e '.id' >/dev/null 2>&1; then
return 0
fi
cid=$(echo "$HOSTS_JSON" | jq -r --argjson id "$hid" '.[] | select(.id == $id) | .certificate_id // empty')
if [ -z "$cid" ] || [ "$cid" = "null" ]; then
log "PUT failed and no certificate_id for host $hid: $(echo "$resp" | head -c 250)"
return 1
fi
upd=$(jq -n --argjson cid "$cid" --argjson force "$force" \
'{certificate_id:$cid, ssl_forced:$force, http2_support:true, hsts_enabled:$force, hsts_subdomains:$force}')
resp=$(npm_api -X PUT "$NPM_URL/api/nginx/proxy-hosts/$hid" \
-H "Content-Type: application/json" -d "$upd")
if echo "$resp" | jq -e '.id' >/dev/null 2>&1; then
return 0
fi
log "PUT fallback failed: $(echo "$resp" | head -c 250)"
return 1
}
assign_minimal_ssl() {
local hid="$1"
local cid="$2"
local force="$3"
local upd uresp
upd=$(jq -n --argjson cid "$cid" --argjson f "$force" \
'{certificate_id:$cid, ssl_forced:$f, http2_support:true, hsts_enabled:$f, hsts_subdomains:$f}')
uresp=$(npm_api -X PUT "$NPM_URL/api/nginx/proxy-hosts/$hid" \
-H "Content-Type: application/json" -d "$upd")
if echo "$uresp" | jq -e '.id' >/dev/null 2>&1; then
return 0
fi
log "minimal PUT failed for host $hid: $(echo "$uresp" | head -c 280)"
return 1
}
prepare_host_for_certificate_request() {
local hid="$1"
local sf="$2"
local want_force="$3"
local domain="$4"
if [[ "$sf" == "$want_force" ]]; then
return 0
fi
log "pre-align SSL mode: $domain (host $hid, ssl_forced=$want_force)"
if put_host_ssl "$hid" "$want_force"; then
HOSTS_JSON=$(npm_api -X GET "$NPM_URL/api/nginx/proxy-hosts")
return 0
fi
return 1
}
request_cert_and_assign() {
local hid="$1"
local domain="$2"
local force="$3"
local cert_resp new_id existing_id
if [ "$DRY_RUN" = 1 ]; then
log "dry-run: POST certificates + assign host $hid domain=$domain force=$force"
return 0
fi
existing_id=$(cert_id_for_domain "$domain")
new_id=""
if [ -n "$existing_id" ] && [ "$existing_id" != "null" ]; then
log "reuse existing cert id=$existing_id for $domain"
new_id="$existing_id"
else
cert_resp=$(npm_api -X POST "$NPM_URL/api/nginx/certificates" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg domain "$domain" '{domain_names:[$domain], provider:"letsencrypt"}')")
new_id=$(echo "$cert_resp" | jq -r '.id // empty')
if [ -z "$new_id" ] || [ "$new_id" = "null" ]; then
log "cert request failed for $domain: $(echo "$cert_resp" | jq -c '.' 2>/dev/null | head -c 350)"
return 1
fi
fi
if assign_minimal_ssl "$hid" "$new_id" "$force"; then
CERTS_JSON=$(npm_api -X GET "$NPM_URL/api/nginx/certificates")
return 0
fi
return 1
}
n_force=0
n_force_fail=0
n_cert=0
n_cert_fail=0
n_skip=0
while IFS= read -r row; do
[ -z "$row" ] && continue
hid=$(echo "$row" | jq -r '.id')
domain=$(echo "$row" | jq -r '.domain_names[0] // empty')
cid=$(echo "$row" | jq -r '.certificate_id // 0')
sf=$(echo "$row" | jq -r '.ssl_forced // false')
en=$(echo "$row" | jq -r '.enabled // true')
port=$(echo "$row" | jq -r '.forward_port // 0')
[[ "$en" == "false" ]] && continue
[ -z "$domain" ] || [ "$domain" = "null" ] && continue
if should_force_ssl "$domain" "$port"; then
want_force=true
else
want_force=false
fi
if [[ "$cid" == "null" ]] || [[ -z "$cid" ]] || [[ "$cid" == "0" ]]; then
if [[ "$domain" == *"*"* ]]; then
log "skip wildcard (request cert in UI): $domain"
n_skip=$((n_skip + 1))
continue
fi
log "request cert: $domain (host $hid, ssl_forced=$want_force)"
if request_cert_and_assign "$hid" "$domain" "$want_force" "$row"; then
n_cert=$((n_cert + 1))
log "ok cert+assign: $domain"
else
n_cert_fail=$((n_cert_fail + 1))
fi
if [ "$DRY_RUN" = 0 ]; then
sleep "$NPM_SSL_FIX_SLEEP_LE"
fi
continue
fi
if cert_is_expired "$cid"; then
if [[ "$domain" == *"*"* ]]; then
log "skip expired wildcard (renew in UI): $domain"
n_skip=$((n_skip + 1))
continue
fi
if ! prepare_host_for_certificate_request "$hid" "$sf" "$want_force" "$domain"; then
n_cert_fail=$((n_cert_fail + 1))
log "FAIL pre-align SSL mode before renew: $domain"
continue
fi
log "renew expired cert: $domain (host $hid, cert $cid, ssl_forced=$want_force)"
if request_cert_and_assign "$hid" "$domain" "$want_force"; then
n_cert=$((n_cert + 1))
log "ok renewed cert: $domain"
else
n_cert_fail=$((n_cert_fail + 1))
log "FAIL renew: $domain"
fi
if [ "$DRY_RUN" = 0 ]; then
sleep "$NPM_SSL_FIX_SLEEP_LE"
fi
continue
fi
if ! cert_matches_domain "$cid" "$domain"; then
if [[ "$domain" == *"*"* ]]; then
log "skip mismatched wildcard cert (fix in UI): $domain"
n_skip=$((n_skip + 1))
continue
fi
if ! prepare_host_for_certificate_request "$hid" "$sf" "$want_force" "$domain"; then
n_cert_fail=$((n_cert_fail + 1))
log "FAIL pre-align SSL mode before mismatched-cert fix: $domain"
continue
fi
log "replace mismatched cert: $domain (host $hid, cert $cid, ssl_forced=$want_force)"
if request_cert_and_assign "$hid" "$domain" "$want_force"; then
n_cert=$((n_cert + 1))
log "ok reassigned matching cert: $domain"
else
n_cert_fail=$((n_cert_fail + 1))
log "FAIL reassign mismatched cert: $domain"
fi
if [ "$DRY_RUN" = 0 ]; then
sleep "$NPM_SSL_FIX_SLEEP_LE"
fi
continue
fi
if [[ "$sf" == "true" ]]; then
continue
fi
# Has cert, ssl not forced — align with want_force
if [[ "$want_force" == "false" ]]; then
n_skip=$((n_skip + 1))
continue
fi
log "enable Force SSL: $domain (host $hid, cert $cid)"
if put_host_ssl "$hid" true; then
n_force=$((n_force + 1))
log "ok: $domain"
else
n_force_fail=$((n_force_fail + 1))
log "FAIL: $domain"
fi
done < <(echo "$HOSTS_JSON" | jq -c '.[]')
log "done: force_ssl_ok=$n_force force_ssl_fail=$n_force_fail new_certs_ok=$n_cert new_certs_fail=$n_cert_fail skipped_rpc_or_wildcard=$n_skip dry_run=$DRY_RUN"
if [ "$DRY_RUN" = 1 ]; then
log "Re-run without --dry-run to apply."
fi

View File

@@ -45,10 +45,10 @@ phoenix.sankofa.nexus → http://${IP_SANKOFA_PHOENIX_API}:${SANKOFA_PHOENIX_API
www.phoenix.sankofa.nexus → http://${IP_SANKOFA_PHOENIX_API}:${SANKOFA_PHOENIX_API_PORT}
the-order.sankofa.nexus → http://${IP_ORDER_HAPROXY}:80
explorer.d-bis.org → http://${IP_BLOCKSCOUT}:80
rpc-http-pub.d-bis.org → https://${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-192.168.11.252}}}}}}}:443 (WebSocket)
rpc-ws-pub.d-bis.org → https://${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-192.168.11.252}}}}}}}:443 (WebSocket)
rpc-http-prv.d-bis.org → https://${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-192.168.11.251}}}}}}}:443 (WebSocket)
rpc-ws-prv.d-bis.org → https://${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-192.168.11.251}}}}}}}:443 (WebSocket)
rpc-http-pub.d-bis.org → http://${RPC_PUBLIC_1:-192.168.11.221}:8545 (WebSocket)
rpc-ws-pub.d-bis.org → http://${RPC_PUBLIC_1:-192.168.11.221}:8546 (WebSocket)
rpc-http-prv.d-bis.org → http://${RPC_CORE_1:-192.168.11.211}:8545 (WebSocket)
rpc-ws-prv.d-bis.org → http://${RPC_CORE_1:-192.168.11.211}:8546 (WebSocket)
dbis-admin.d-bis.org → http://${IP_DBIS_FRONTEND:-${IP_SERVICE_13:-${IP_SERVICE_13:-${IP_SERVICE_13:-${IP_SERVICE_13:-${IP_SERVICE_13:-192.168.11.13}}}}}0}:80
dbis-api.d-bis.org → http://${IP_DBIS_API:-${IP_DBIS_API:-192.168.11.155}}:3000
dbis-api-2.d-bis.org → http://${IP_DBIS_API_2:-${IP_DBIS_API_2:-192.168.11.156}}:3000
@@ -57,7 +57,7 @@ mim4u.org → http://${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SER
www.mim4u.org → http://${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-192.168.11.36}}}}}}:80
secure.mim4u.org → http://${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-192.168.11.36}}}}}}:80
training.mim4u.org → http://${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-192.168.11.36}}}}}}:80
rpc.public-0138.defi-oracle.io → https://${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-192.168.11.252}}}}}}}:443
rpc.public-0138.defi-oracle.io → https://${RPC_THIRDWEB_PRIMARY:-192.168.11.240}:443
DOMAINS
cat "$DOMAINS_FILE" | nl -w2 -s'. '

View File

@@ -173,10 +173,10 @@ create_proxy_host "the-order.sankofa.nexus" "http" "192.168.11.39" "80" "false"
# d-bis.org (9 domains)
create_proxy_host "explorer.d-bis.org" "http" "${IP_BLOCKSCOUT:-${IP_DEVICE_14:-${IP_DEVICE_14:-${IP_DEVICE_14:-${IP_DEVICE_14:-${IP_DEVICE_14:-192.168.11.14}}}}}0}" "80" "false" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "rpc-http-pub.d-bis.org" "https" "${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-192.168.11.252}}}}}}}" "443" "true" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "rpc-ws-pub.d-bis.org" "https" "${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-192.168.11.252}}}}}}}" "443" "true" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "rpc-http-prv.d-bis.org" "https" "${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-192.168.11.251}}}}}}}" "443" "true" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "rpc-ws-prv.d-bis.org" "https" "${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-192.168.11.251}}}}}}}" "443" "true" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "rpc-http-pub.d-bis.org" "https" "${RPC_PUBLIC_1:-192.168.11.221}" "443" "true" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "rpc-ws-pub.d-bis.org" "https" "${RPC_PUBLIC_1:-192.168.11.221}" "443" "true" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "rpc-http-prv.d-bis.org" "https" "${RPC_CORE_1:-192.168.11.211}" "443" "true" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "rpc-ws-prv.d-bis.org" "https" "${RPC_CORE_1:-192.168.11.211}" "443" "true" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "dbis-admin.d-bis.org" "http" "${IP_DBIS_FRONTEND:-${IP_SERVICE_13:-${IP_SERVICE_13:-${IP_SERVICE_13:-${IP_SERVICE_13:-${IP_SERVICE_13:-192.168.11.13}}}}}0}" "80" "false" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "dbis-api.d-bis.org" "http" "${IP_DBIS_API:-${IP_DBIS_API:-192.168.11.155}}" "3000" "false" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "dbis-api-2.d-bis.org" "http" "${IP_DBIS_API_2:-${IP_DBIS_API_2:-192.168.11.156}}" "3000" "false" && ((SUCCESS++)) || ((FAILED++))
@@ -189,7 +189,7 @@ create_proxy_host "secure.mim4u.org" "http" "${IP_SERVICE_36:-${IP_SERVICE_36:-$
create_proxy_host "training.mim4u.org" "http" "${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-${IP_SERVICE_36:-192.168.11.36}}}}}}" "80" "false" && ((SUCCESS++)) || ((FAILED++))
# defi-oracle.io (1 domain)
create_proxy_host "rpc.public-0138.defi-oracle.io" "https" "${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-192.168.11.252}}}}}}}" "443" "true" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "rpc.public-0138.defi-oracle.io" "https" "${RPC_THIRDWEB_PRIMARY:-192.168.11.240}" "443" "true" && ((SUCCESS++)) || ((FAILED++))
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

View File

@@ -315,10 +315,10 @@ fi
# d-bis.org (9 domains)
create_proxy_host "explorer.d-bis.org" "http" "${IP_BLOCKSCOUT:-${IP_DEVICE_14:-${IP_DEVICE_14:-${IP_DEVICE_14:-${IP_DEVICE_14:-${IP_DEVICE_14:-192.168.11.14}}}}}0}" "80" "false" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "rpc-http-pub.d-bis.org" "https" "${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-192.168.11.252}}}}}}}" "443" "true" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "rpc-ws-pub.d-bis.org" "https" "${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-${RPC_ALI_2:-192.168.11.252}}}}}}}" "443" "true" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "rpc-http-prv.d-bis.org" "https" "${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-192.168.11.251}}}}}}}" "443" "true" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "rpc-ws-prv.d-bis.org" "https" "${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-${RPC_ALI_1:-192.168.11.251}}}}}}}" "443" "true" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "rpc-http-pub.d-bis.org" "http" "${RPC_PUBLIC_1:-192.168.11.221}" "8545" "true" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "rpc-ws-pub.d-bis.org" "http" "${RPC_PUBLIC_1:-192.168.11.221}" "8546" "true" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "rpc-http-prv.d-bis.org" "http" "${RPC_CORE_1:-192.168.11.211}" "8545" "true" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "rpc-ws-prv.d-bis.org" "http" "${RPC_CORE_1:-192.168.11.211}" "8546" "true" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "dbis-admin.d-bis.org" "http" "${IP_DBIS_FRONTEND:-${IP_SERVICE_13:-${IP_SERVICE_13:-${IP_SERVICE_13:-${IP_SERVICE_13:-${IP_SERVICE_13:-192.168.11.13}}}}}0}" "80" "false" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "dbis-api.d-bis.org" "http" "${IP_DBIS_API:-${IP_DBIS_API:-192.168.11.155}}" "3000" "false" && ((SUCCESS++)) || ((FAILED++))
create_proxy_host "dbis-api-2.d-bis.org" "http" "${IP_DBIS_API_2:-${IP_DBIS_API_2:-192.168.11.156}}" "3000" "false" && ((SUCCESS++)) || ((FAILED++))

View File

@@ -0,0 +1,131 @@
#!/usr/bin/env python3
import argparse
import json
import subprocess
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
ENV_PATH = PROJECT_ROOT / '.env'
MINIMAL_REFERRER = 'add_header Referrer-Policy "strict-origin-when-cross-origin" always;'
MANAGED_ADVANCED_CONFIG = {
'explorer.d-bis.org': MINIMAL_REFERRER,
'sankofa.nexus': '',
'phoenix.sankofa.nexus': '',
'the-order.sankofa.nexus': '',
'rpc-ws-pub.d-bis.org': MINIMAL_REFERRER,
'rpc-http-prv.d-bis.org': MINIMAL_REFERRER,
'rpc-ws-prv.d-bis.org': MINIMAL_REFERRER,
'dbis-admin.d-bis.org': MINIMAL_REFERRER,
'dbis-api.d-bis.org': '',
'dbis-api-2.d-bis.org': '',
'secure.d-bis.org': MINIMAL_REFERRER,
'mim4u.org': MINIMAL_REFERRER,
'www.mim4u.org': MINIMAL_REFERRER,
'secure.mim4u.org': MINIMAL_REFERRER,
'training.mim4u.org': MINIMAL_REFERRER,
'rpc.public-0138.defi-oracle.io': MINIMAL_REFERRER,
'studio.sankofa.nexus': '',
}
def load_env(path: Path) -> dict[str, str]:
data: dict[str, str] = {}
for raw_line in path.read_text().splitlines():
line = raw_line.strip()
if not line or line.startswith('#') or '=' not in line:
continue
key, value = line.split('=', 1)
data[key] = value
return data
def curl_json(url: str, token: str | None, method: str = 'GET', payload: dict | None = None) -> dict | list:
cmd = [
'curl',
'-s',
'-k',
'-L',
'--connect-timeout',
'10',
'--max-time',
'120',
'-X',
method,
url,
]
if token:
cmd.extend(['-H', f'Authorization: Bearer {token}'])
if payload is not None:
cmd.extend(['-H', 'Content-Type: application/json', '-d', json.dumps(payload)])
raw = subprocess.check_output(cmd, text=True)
return json.loads(raw)
def normalize_adv(value: str | None) -> str:
return (value or '').replace('\r\n', '\n').strip()
def main() -> int:
parser = argparse.ArgumentParser(description='Normalize managed NPMplus per-host advanced_config values.')
parser.add_argument('--dry-run', action='store_true', help='Show proposed changes without applying them')
args = parser.parse_args()
env = load_env(ENV_PATH)
npm_url = env.get('NPM_URL', 'https://192.168.11.167:81')
auth = {'identity': env['NPM_EMAIL'], 'secret': env['NPM_PASSWORD']}
token = curl_json(f'{npm_url}/api/tokens', None, method='POST', payload=auth)['token']
hosts = curl_json(f'{npm_url}/api/nginx/proxy-hosts', token)
changed = 0
for host in hosts:
domains = [domain.lower() for domain in host.get('domain_names', [])]
matched_domain = next((domain for domain in domains if domain in MANAGED_ADVANCED_CONFIG), None)
if not matched_domain:
continue
desired_adv = MANAGED_ADVANCED_CONFIG[matched_domain]
current_adv = normalize_adv(host.get('advanced_config'))
if current_adv == normalize_adv(desired_adv):
print(f'SKIP {matched_domain}')
continue
changed += 1
if desired_adv == '':
mode = 'clear'
elif normalize_adv(desired_adv) == normalize_adv(MINIMAL_REFERRER):
mode = 'minimal-referrer'
else:
mode = 'custom'
print(f'UPDATE {matched_domain} -> {mode}')
if args.dry_run:
continue
payload = {
'forward_scheme': host['forward_scheme'],
'forward_host': host['forward_host'],
'forward_port': host['forward_port'],
'allow_websocket_upgrade': host['allow_websocket_upgrade'],
'block_exploits': host['block_exploits'],
'advanced_config': desired_adv,
}
response = curl_json(f"{npm_url}/api/nginx/proxy-hosts/{host['id']}", token, method='PUT', payload=payload)
if str(response.get('id')) != str(host['id']):
print(f'FAILED {matched_domain}: unexpected response {json.dumps(response)[:400]}', file=sys.stderr)
return 1
if changed == 0:
print('No managed advanced_config changes were needed.')
elif args.dry_run:
print(f'Dry run complete. {changed} host(s) would change.')
else:
print(f'Applied {changed} host(s).')
return 0
if __name__ == '__main__':
raise SystemExit(main())

View File

@@ -15,6 +15,8 @@ NPMPLUS_ALLTRA_IP="${IP_NPMPLUS_ALLTRA_HYBX:-192.168.11.169}"
NPM_URL="https://${NPMPLUS_ALLTRA_IP}:81"
NPM_EMAIL="${NPM_EMAIL:-admin@example.org}"
NPM_PASSWORD="${NPM_PASSWORD:-}"
INCLUDE_PLACEHOLDER_HOSTS="${INCLUDE_PLACEHOLDER_HOSTS:-0}"
SKIP_UNHEALTHY_UPSTREAMS="${SKIP_UNHEALTHY_UPSTREAMS:-1}"
if [ -z "$NPM_PASSWORD" ]; then
echo "Set NPM_PASSWORD. Get from: ssh root@192.168.11.11 'pct exec 10235 -- cat /opt/.npm_pwd 2>/dev/null'"
@@ -93,19 +95,74 @@ add_proxy_host() {
fi
}
# Update existing proxy host to set block_exploits: false (fixes 405 on JSON-RPC POST to /)
# NPM 2 PUT accepts only specific fields; try minimal payload and blockCommonExploits (camelCase)
update_block_exploits() {
fetch_proxy_hosts_json() {
curl_auth -X GET "$NPM_URL/api/nginx/proxy-hosts"
}
resolve_proxy_host_id() {
local domain=$1
local hosts_json=${2:-}
[ -z "$hosts_json" ] && hosts_json=$(fetch_proxy_hosts_json)
echo "$hosts_json" | jq -r --arg dom "$domain" '
if type == "array" then .
elif .data != null then .data
elif .proxy_hosts != null then .proxy_hosts
else []
end
| .[]
| select(.domain_names | type == "array")
| select(.domain_names[] == $dom)
| .id
' 2>/dev/null | head -n1
}
origin_tcp_ready() {
local fwd_host=$1
local fwd_port=$2
timeout 3 bash -lc "</dev/tcp/${fwd_host}/${fwd_port}" >/dev/null 2>&1
}
should_manage_proxy_host() {
local domain=$1
local fwd_host=$2
local fwd_port=$3
local purpose=${4:-active}
if [ "$purpose" = "placeholder" ] && [ "$INCLUDE_PLACEHOLDER_HOSTS" != "1" ]; then
echo " Skip placeholder host: $domain (set INCLUDE_PLACEHOLDER_HOSTS=1 after the real web service is deployed)"
return 1
fi
if [ "$SKIP_UNHEALTHY_UPSTREAMS" = "1" ] && ! origin_tcp_ready "$fwd_host" "$fwd_port"; then
echo " Skip $domain -> $fwd_host:$fwd_port (origin is not listening)"
return 1
fi
return 0
}
# Update existing proxy host while preserving cert/SSL state already on the row.
update_proxy_host() {
local domain=$1
local fwd_host=$2
local fwd_port=$3
local ws=${4:-false}
local hosts_json
hosts_json=$(curl_auth -X GET "$NPM_URL/api/nginx/proxy-hosts")
hosts_json=$(fetch_proxy_hosts_json)
local arr
arr=$(echo "$hosts_json" | jq -c '
if type == "array" then .
elif .data != null then .data
elif .proxy_hosts != null then .proxy_hosts
else []
end
' 2>/dev/null)
[ -z "$arr" ] && return 1
local id
id=$(echo "$hosts_json" | jq -r ".[] | select(.domain_names | type == \"array\") | select(.domain_names[] == \"$domain\") | .id" 2>/dev/null | head -n1)
id=$(echo "$arr" | jq -r --arg dom "$domain" '
.[]
| select(.domain_names | type == "array")
| select(.domain_names[] == $dom)
| .id
' 2>/dev/null | head -n1)
if [ -z "$id" ] || [ "$id" = "null" ]; then return 1; fi
# Minimal payload (NPM 2 rejects "additional properties")
local payload
payload=$(jq -n \
--arg scheme "http" --arg host "$fwd_host" --argjson port "$fwd_port" --argjson ws "$ws" \
@@ -117,52 +174,91 @@ update_block_exploits() {
local out_id
out_id=$(echo "$resp" | jq -r '.id // empty' 2>/dev/null)
if [ -n "$out_id" ] && [ "$out_id" != "null" ]; then
echo " Updated block_exploits=false: $domain"
echo " Updated: $domain -> $fwd_host:$fwd_port"
return 0
fi
# NPM 2 may use camelCase: blockCommonExploits
payload=$(jq -n \
--arg scheme "http" --arg host "$fwd_host" --argjson port "$fwd_port" --argjson ws "$ws" \
'{ forward_scheme: $scheme, forward_host: $host, forward_port: $port, allow_websocket_upgrade: $ws, blockCommonExploits: false }')
resp=$(curl_auth -X PUT "$NPM_URL/api/nginx/proxy-hosts/$id" -H "Content-Type: application/json" -d "$payload")
out_id=$(echo "$resp" | jq -r '.id // empty' 2>/dev/null)
if [ -n "$out_id" ] && [ "$out_id" != "null" ]; then
echo " Updated blockCommonExploits=false: $domain"
echo " Updated: $domain -> $fwd_host:$fwd_port"
return 0
fi
echo " Warning: could not set block_exploits false via API for $domain. Turn off 'Block Common Exploits' in NPM UI (Advanced) for this proxy host."
local host_obj
host_obj=$(echo "$arr" | jq -c --arg dom "$domain" '
.[]
| select(.domain_names | type == "array")
| select(.domain_names[] == $dom)
' 2>/dev/null | head -n1)
if [ -n "$host_obj" ]; then
payload=$(echo "$host_obj" | jq -c --arg host "$fwd_host" --argjson port "$fwd_port" --argjson ws "$ws" '
{
domain_names,
forward_scheme,
forward_host: $host,
forward_port: $port,
allow_websocket_upgrade: $ws,
block_exploits,
certificate_id,
ssl_forced,
caching_enabled,
advanced_config,
access_list_id,
enabled,
http2_support,
hsts_enabled,
hsts_subdomains
}
' 2>/dev/null)
if [ -n "$payload" ]; then
resp=$(curl_auth -X PUT "$NPM_URL/api/nginx/proxy-hosts/$id" -H "Content-Type: application/json" -d "$payload")
out_id=$(echo "$resp" | jq -r '.id // empty' 2>/dev/null)
if [ -n "$out_id" ] && [ "$out_id" != "null" ]; then
echo " Updated: $domain -> $fwd_host:$fwd_port"
return 0
fi
fi
fi
echo " Warning: could not update $domain via API. Check the NPM UI for this proxy host."
return 1
}
add_or_update_proxy_host() {
local domain=$1
local fwd_host=$2
local fwd_port=$3
local ws=${4:-false}
local purpose=${5:-active}
should_manage_proxy_host "$domain" "$fwd_host" "$fwd_port" "$purpose" || return 0
if add_proxy_host "$domain" "$fwd_host" "$fwd_port" "$ws"; then
return 0
fi
update_proxy_host "$domain" "$fwd_host" "$fwd_port" "$ws"
}
# Add or fix Alltra/HYBX + Nathan core-2 proxy hosts (third NPMplus = 76.53.10.38 → 192.168.11.169)
# RPC hosts must have block_exploits false or POST to / returns 405
add_proxy_host "rpc-core-2.d-bis.org" "192.168.11.212" 8545 true || true
add_proxy_host "rpc-alltra.d-bis.org" "192.168.11.172" 8545 true || true
add_proxy_host "rpc-alltra-2.d-bis.org" "192.168.11.173" 8545 true || true
add_proxy_host "rpc-alltra-3.d-bis.org" "192.168.11.174" 8545 true || true
add_proxy_host "rpc-hybx.d-bis.org" "192.168.11.246" 8545 true || true
add_proxy_host "rpc-hybx-2.d-bis.org" "192.168.11.247" 8545 true || true
add_proxy_host "rpc-hybx-3.d-bis.org" "192.168.11.248" 8545 true || true
add_proxy_host "cacti-alltra.d-bis.org" "192.168.11.177" 80 false || true
add_proxy_host "cacti-hybx.d-bis.org" "192.168.11.251" 80 false || true
# Firefly (Alltra: 6202-6203 @ .175,.176; HYBX: 6204-6205 @ .249,.250) — port 80 typical for web UI
add_proxy_host "firefly-alltra-1.d-bis.org" "192.168.11.175" 80 false || true
add_proxy_host "firefly-alltra-2.d-bis.org" "192.168.11.176" 80 false || true
add_proxy_host "firefly-hybx-1.d-bis.org" "192.168.11.249" 80 false || true
add_proxy_host "firefly-hybx-2.d-bis.org" "192.168.11.250" 80 false || true
# Fabric / Indy (Alltra: 6001,6401 @ .178,.179; HYBX: 6002,6402 @ .252,.253) — port 80 or adjust in NPM UI
add_proxy_host "fabric-alltra.d-bis.org" "192.168.11.178" 80 false || true
add_proxy_host "indy-alltra.d-bis.org" "192.168.11.179" 80 false || true
add_proxy_host "fabric-hybx.d-bis.org" "192.168.11.252" 80 false || true
add_proxy_host "indy-hybx.d-bis.org" "192.168.11.253" 80 false || true
echo "Ensuring block_exploits=false for RPC hosts (fixes 405 on JSON-RPC POST)..."
update_block_exploits "rpc-core-2.d-bis.org" "192.168.11.212" 8545 true || true
update_block_exploits "rpc-alltra.d-bis.org" "192.168.11.172" 8545 true || true
update_block_exploits "rpc-alltra-2.d-bis.org" "192.168.11.173" 8545 true || true
update_block_exploits "rpc-alltra-3.d-bis.org" "192.168.11.174" 8545 true || true
update_block_exploits "rpc-hybx.d-bis.org" "192.168.11.246" 8545 true || true
update_block_exploits "rpc-hybx-2.d-bis.org" "192.168.11.247" 8545 true || true
update_block_exploits "rpc-hybx-3.d-bis.org" "192.168.11.248" 8545 true || true
add_or_update_proxy_host "rpc-core-2.d-bis.org" "192.168.11.212" 8545 true active || true
add_or_update_proxy_host "rpc-alltra.d-bis.org" "192.168.11.172" 8545 true active || true
add_or_update_proxy_host "rpc-alltra-2.d-bis.org" "192.168.11.173" 8545 true active || true
add_or_update_proxy_host "rpc-alltra-3.d-bis.org" "192.168.11.174" 8545 true active || true
add_or_update_proxy_host "rpc-hybx.d-bis.org" "192.168.11.246" 8545 true active || true
add_or_update_proxy_host "rpc-hybx-2.d-bis.org" "192.168.11.247" 8545 true active || true
add_or_update_proxy_host "rpc-hybx-3.d-bis.org" "192.168.11.248" 8545 true active || true
add_or_update_proxy_host "cacti-alltra.d-bis.org" "192.168.11.177" 80 false active || true
add_or_update_proxy_host "cacti-hybx.d-bis.org" "192.168.11.251" 80 false active || true
# Firefly / Fabric / Indy web surfaces are placeholders until the actual HTTP listener is deployed.
add_or_update_proxy_host "firefly-alltra-1.d-bis.org" "192.168.11.175" 80 false placeholder || true
add_or_update_proxy_host "firefly-alltra-2.d-bis.org" "192.168.11.176" 80 false placeholder || true
add_or_update_proxy_host "firefly-hybx-1.d-bis.org" "192.168.11.249" 80 false placeholder || true
add_or_update_proxy_host "firefly-hybx-2.d-bis.org" "192.168.11.250" 80 false placeholder || true
add_or_update_proxy_host "fabric-alltra.d-bis.org" "192.168.11.178" 80 false placeholder || true
add_or_update_proxy_host "indy-alltra.d-bis.org" "192.168.11.179" 80 false placeholder || true
add_or_update_proxy_host "fabric-hybx.d-bis.org" "192.168.11.252" 80 false placeholder || true
add_or_update_proxy_host "indy-hybx.d-bis.org" "192.168.11.253" 80 false placeholder || true
echo ""
echo "Done. Request Let's Encrypt certs in NPMplus UI for each domain."
echo "Done. Request Let's Encrypt certs in NPMplus UI for each active domain."
echo "Placeholder Firefly/Fabric/Indy hosts are skipped by default until INCLUDE_PLACEHOLDER_HOSTS=1 and the real web service is listening."

View File

@@ -1,6 +1,8 @@
#!/usr/bin/env bash
# Add NPMplus proxy hosts for dev/Codespaces (fourth NPMplus at 192.168.11.170)
# Dev VM (Gitea) + direction to all three Proxmox VE admin panels.
# Dev VM (:3000) for dev.d-bis.org + codespaces; gitea.d-bis.org defaults to infra Gitea (VMID 104 @ IP_GITEA_INFRA :80).
# Override: GITEA_PUBLIC_UPSTREAM_HOST=$IP_DEV_VM GITEA_PUBLIC_UPSTREAM_PORT=3000 to use dev-local Gitea for the public hostname.
# Proxmox VE admin panels: pve.*.d-bis.org → :8006 (websocket).
# Usage: NPM_URL=https://192.168.11.170:81 NPM_PASSWORD=xxx bash scripts/nginx-proxy-manager/update-npmplus-fourth-proxy-hosts.sh
# Or use NPM_EMAIL + NPM_PASSWORD from .env (NPM_EMAIL_FOURTH / NPM_PASSWORD_FOURTH if set).
# See: docs/04-configuration/DEV_CODESPACES_76_53_10_40.md
@@ -14,6 +16,9 @@ source "$PROJECT_ROOT/config/ip-addresses.conf" 2>/dev/null || true
# Fourth NPMplus (dev/Codespaces)
NPMPLUS_FOURTH_IP="${IP_NPMPLUS_FOURTH:-192.168.11.170}"
IP_DEV_VM="${IP_DEV_VM:-192.168.11.59}"
IP_GITEA_INFRA="${IP_GITEA_INFRA:-192.168.11.31}"
GITEA_PUBLIC_UPSTREAM_HOST="${GITEA_PUBLIC_UPSTREAM_HOST:-$IP_GITEA_INFRA}"
GITEA_PUBLIC_UPSTREAM_PORT="${GITEA_PUBLIC_UPSTREAM_PORT:-80}"
PROXMOX_ML110="${PROXMOX_HOST_ML110:-192.168.11.10}"
PROXMOX_R630_01="${PROXMOX_HOST_R630_01:-192.168.11.11}"
PROXMOX_R630_02="${PROXMOX_HOST_R630_02:-192.168.11.12}"
@@ -198,9 +203,9 @@ add_or_update_proxy_host() {
update_proxy_host "$domain" "$fwd_host" "$fwd_port" "$ws"
}
# Dev VM (Gitea on 3000); dev and codespaces as aliases
# Dev VM stack on :3000; public Gitea hostname → infra CT 104 by default (config/ip-addresses.conf)
add_or_update_proxy_host "dev.d-bis.org" "$IP_DEV_VM" 3000 false || true
add_or_update_proxy_host "gitea.d-bis.org" "$IP_DEV_VM" 3000 false || true
add_or_update_proxy_host "gitea.d-bis.org" "$GITEA_PUBLIC_UPSTREAM_HOST" "$GITEA_PUBLIC_UPSTREAM_PORT" false || true
add_or_update_proxy_host "codespaces.d-bis.org" "$IP_DEV_VM" 3000 false || true
# Proxmox VE admin panels (port 8006; websocket required for console)

View File

@@ -0,0 +1,118 @@
#!/usr/bin/env bash
# Update NPMplus forward targets for info.defi-oracle.io only (avoids full fleet run).
# Same upstreams as update-npmplus-proxy-hosts-api.sh (IP_INFO_DEFI_ORACLE_WEB :80).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
source "${PROJECT_ROOT}/config/ip-addresses.conf" 2>/dev/null || true
_orig_npm_url="${NPM_URL:-}"
_orig_npm_email="${NPM_EMAIL:-}"
_orig_npm_password="${NPM_PASSWORD:-}"
if [ -f "$PROJECT_ROOT/.env" ]; then
set +u
# shellcheck source=/dev/null
source "$PROJECT_ROOT/.env"
set -u
fi
if [ -f "$PROJECT_ROOT/smom-dbis-138/.env" ]; then
set +u
# shellcheck source=/dev/null
source "$PROJECT_ROOT/smom-dbis-138/.env"
set -u
fi
[ -n "$_orig_npm_url" ] && NPM_URL="$_orig_npm_url"
[ -n "$_orig_npm_email" ] && NPM_EMAIL="$_orig_npm_email"
[ -n "$_orig_npm_password" ] && NPM_PASSWORD="$_orig_npm_password"
[ -f "$PROJECT_ROOT/config/ip-addresses.conf" ] && source "$PROJECT_ROOT/config/ip-addresses.conf" 2>/dev/null || true
NPM_URL="${NPM_URL:-https://${IP_NPMPLUS}:81}"
NPM_EMAIL="${NPM_EMAIL:-}"
NPM_PASSWORD="${NPM_PASSWORD:-}"
if [ -z "$NPM_PASSWORD" ]; then
echo "NPM_PASSWORD is required (repo .env or export)."
exit 1
fi
NPM_CURL_MAX_TIME="${NPM_CURL_MAX_TIME:-300}"
curl_npm() { curl -s -k -L --connect-timeout 10 --max-time "$NPM_CURL_MAX_TIME" "$@"; }
try_connect() { curl -s -k -L -o /dev/null --connect-timeout 5 --max-time 15 "$1" 2>/dev/null; }
if ! try_connect "$NPM_URL/"; then
http_url="${NPM_URL/https:/http:}"
if try_connect "$http_url/"; then
NPM_URL="$http_url"
echo "Using HTTP NPM URL: $NPM_URL"
fi
fi
AUTH_JSON=$(jq -n --arg identity "$NPM_EMAIL" --arg secret "$NPM_PASSWORD" '{identity:$identity,secret:$secret}')
TOKEN_RESPONSE=$(curl_npm -X POST "$NPM_URL/api/tokens" \
-H "Content-Type: application/json" \
-d "$AUTH_JSON")
TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.token // empty' 2>/dev/null || echo "")
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
echo "NPM authentication failed."
exit 1
fi
echo "Fetching proxy host list..."
PROXY_HOSTS_JSON=$(curl_npm -X GET "$NPM_URL/api/nginx/proxy-hosts" \
-H "Authorization: Bearer $TOKEN")
resolve_proxy_host_id() {
local domain="$1"
echo "$PROXY_HOSTS_JSON" | jq -r --arg d "$domain" '
.[] | select(.domain_names | type == "array") |
select(any(.domain_names[]; (. | tostring | ascii_downcase) == ($d | ascii_downcase))) |
.id' 2>/dev/null | head -n1
}
INFO_IP="${INFO_DEFI_ORACLE_UPSTREAM_IP:-${IP_INFO_DEFI_ORACLE_WEB:-192.168.11.218}}"
INFO_PORT="${INFO_DEFI_ORACLE_UPSTREAM_PORT:-80}"
put_forward() {
local domain="$1"
local canonical="${2:-}"
local HOST_ID
HOST_ID=$(resolve_proxy_host_id "$domain")
if [ -z "$HOST_ID" ] || [ "$HOST_ID" = "null" ]; then
echo "No NPM proxy host named $domain — add it in NPM UI or run full update-npmplus-proxy-hosts-api.sh"
return 1
fi
local adv_line="" manage_adv=false
if [ -n "$canonical" ]; then
adv_line="return 301 ${canonical}\$request_uri;"
manage_adv=true
fi
local UPDATE_PAYLOAD
UPDATE_PAYLOAD=$(jq -n \
--arg scheme "http" \
--arg hostname "$INFO_IP" \
--argjson port "$INFO_PORT" \
--argjson websocket false \
--argjson block_exploits false \
--arg adv "$adv_line" \
--argjson manage_adv "$manage_adv" \
'{
forward_scheme: $scheme,
forward_host: $hostname,
forward_port: $port,
allow_websocket_upgrade: $websocket,
block_exploits: $block_exploits
} + (if $manage_adv then {advanced_config: $adv} else {} end)')
local RESP
RESP=$(curl_npm -X PUT "$NPM_URL/api/nginx/proxy-hosts/$HOST_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "$UPDATE_PAYLOAD")
if ! echo "$RESP" | jq -e '.id' >/dev/null 2>&1; then
echo "PUT failed for $domain: $(echo "$RESP" | jq -c . 2>/dev/null || echo "$RESP" | head -c 300)"
return 1
fi
echo "OK $domain -> http://${INFO_IP}:${INFO_PORT}"
}
echo "Updating info.defi-oracle.io → http://${INFO_IP}:${INFO_PORT}"
put_forward "info.defi-oracle.io"
put_forward "www.info.defi-oracle.io" "https://info.defi-oracle.io"
echo "Done."

View File

@@ -155,6 +155,33 @@ validate_canonical_https_redirect() {
return 0
}
ADVANCED_CONFIG_CLEAR_SENTINEL="__CLEAR_ADVANCED_CONFIG__"
default_advanced_config_for_domain() {
local domain="${1,,}"
case "$domain" in
explorer.d-bis.org|dbis-admin.d-bis.org|secure.d-bis.org|relay-mainnet-cw.d-bis.org|mim4u.org|www.mim4u.org|secure.mim4u.org|training.mim4u.org|rpc-ws-pub.d-bis.org|rpc-http-prv.d-bis.org|rpc-ws-prv.d-bis.org|rpc.public-0138.defi-oracle.io)
# NPMplus already synthesizes the shared security headers in hsts.conf. For
# these hosts we only keep a single referrer policy at the edge and avoid
# stacking a second CSP / XFO / XCTO / XXSS block from per-host advanced_config.
cat <<'EOF'
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
EOF
return 0
;;
sankofa.nexus|phoenix.sankofa.nexus|the-order.sankofa.nexus|dbis-api.d-bis.org|dbis-api-2.d-bis.org|studio.sankofa.nexus)
# These origins already emit their own CSP / referrer policy. Send an empty
# advanced_config on PUT so stale per-host header blocks are actively cleared.
# Studio now owns its / -> /studio/ redirect in the app layer, not at NPMplus.
printf '%s\n' "$ADVANCED_CONFIG_CLEAR_SENTINEL"
return 0
;;
esac
return 1
}
# Function to add proxy host (POST) when domain does not exist
# Optional 6th arg: canonical HTTPS apex for www-style hosts (sets advanced_config 301 → apex$request_uri)
add_proxy_host() {
@@ -165,11 +192,20 @@ add_proxy_host() {
local block_exploits=${5:-false}
local canonical_https="${6:-}"
local adv_line=""
local manage_adv="false"
if [ -n "$canonical_https" ] && ! validate_canonical_https_redirect "$canonical_https" "add_proxy_host($domain)"; then
return 1
fi
if [ -n "$canonical_https" ]; then
adv_line="return 301 ${canonical_https}\$request_uri;"
manage_adv="true"
elif adv_line="$(default_advanced_config_for_domain "$domain" 2>/dev/null)"; then
manage_adv="true"
if [ "$adv_line" = "$ADVANCED_CONFIG_CLEAR_SENTINEL" ]; then
adv_line=""
fi
else
adv_line=""
fi
local payload
payload=$(jq -n \
@@ -179,6 +215,7 @@ add_proxy_host() {
--argjson ws "$websocket" \
--argjson block_exploits "$([ "$block_exploits" = "true" ] && echo true || echo false)" \
--arg adv "$adv_line" \
--argjson manage_adv "$manage_adv" \
'{
domain_names: [$domain],
forward_scheme: "http",
@@ -188,7 +225,7 @@ add_proxy_host() {
block_exploits: $block_exploits,
certificate_id: null,
ssl_forced: false
} + (if $adv != "" then {advanced_config: $adv} else {} end)' 2>/dev/null)
} + (if $manage_adv then {advanced_config: $adv} else {} end)' 2>/dev/null)
if [ -z "$payload" ]; then
echo " ❌ Failed to build payload for $domain"
return 1
@@ -280,8 +317,17 @@ update_proxy_host() {
local be_json="false"
[ "$block_exploits" = "true" ] && be_json="true"
local adv_line=""
local manage_adv="false"
if [ -n "$canonical_https" ]; then
adv_line="return 301 ${canonical_https}\$request_uri;"
manage_adv="true"
elif adv_line="$(default_advanced_config_for_domain "$domain" 2>/dev/null)"; then
manage_adv="true"
if [ "$adv_line" = "$ADVANCED_CONFIG_CLEAR_SENTINEL" ]; then
adv_line=""
fi
else
adv_line=""
fi
UPDATE_PAYLOAD=$(jq -n \
--arg scheme "$scheme" \
@@ -290,13 +336,14 @@ update_proxy_host() {
--argjson websocket "$websocket" \
--argjson block_exploits "$be_json" \
--arg adv "$adv_line" \
--argjson manage_adv "$manage_adv" \
'{
forward_scheme: $scheme,
forward_host: $hostname,
forward_port: $port,
allow_websocket_upgrade: $websocket,
block_exploits: $block_exploits
} + (if $adv != "" then {advanced_config: $adv} else {} end)' 2>/dev/null || echo "")
} + (if $manage_adv then {advanced_config: $adv} else {} end)' 2>/dev/null || echo "")
UPDATE_RESPONSE=$(curl_npm -X PUT "$NPM_URL/api/nginx/proxy-hosts/$HOST_ID" \
-H "Authorization: Bearer $TOKEN" \
@@ -344,12 +391,27 @@ update_proxy_host "wss.tw-core.d-bis.org" "http://${RPC_THIRDWEB_ADMIN_CORE}:854
# Catch-all for foo.tw-core.d-bis.org → Besu HTTP JSON-RPC :8545 (exact rpc./wss. hosts above take precedence for nginx server_name)
update_proxy_host '*.tw-core.d-bis.org' "http://${RPC_THIRDWEB_ADMIN_CORE}:8545" true false && updated_count=$((updated_count + 1)) || { add_proxy_host '*.tw-core.d-bis.org' "${RPC_THIRDWEB_ADMIN_CORE}" 8545 true false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
# RPC Core-2 (Nathan) is on the THIRD NPMplus (192.168.11.169) — use add-rpc-core-2-npmplus-proxy.sh and update-npmplus-alltra-hybx-proxy-hosts.sh
# ThirdWeb / public-0138 edge (VMID 2400 nginx HTTPS) — default IP must match ALL_VMIDS_ENDPOINTS if env is unset
# rpc.public-0138.defi-oracle.io — same Besu JSON-RPC as rpc-http-pub.d-bis.org (VMID 2201). historic: VM 2400 HTTPS edge caused 502 when nginx/tunnel drifted; HTTP upstream avoids HTTPS-to-HTTPS proxy issues for JSON-RPC POST.
RPC_THIRDWEB_PRIMARY="${RPC_THIRDWEB_PRIMARY:-192.168.11.240}"
update_proxy_host "rpc.public-0138.defi-oracle.io" "https://${RPC_THIRDWEB_PRIMARY}:443" true false && updated_count=$((updated_count + 1)) || { sleep 2; echo " ↪ Retry rpc.public-0138.defi-oracle.io after transient NPM/API error..."; update_proxy_host "rpc.public-0138.defi-oracle.io" "https://${RPC_THIRDWEB_PRIMARY}:443" true false && updated_count=$((updated_count + 1)) || failed_count=$((failed_count + 1)); }
update_proxy_host "rpc.public-0138.defi-oracle.io" "http://${RPC_PUBLIC_1}:8545" true false && updated_count=$((updated_count + 1)) || { sleep 2; echo " ↪ Retry rpc.public-0138.defi-oracle.io after transient NPM/API error..."; update_proxy_host "rpc.public-0138.defi-oracle.io" "http://${RPC_PUBLIC_1}:8545" true false && updated_count=$((updated_count + 1)) || failed_count=$((failed_count + 1)); }
# rpc.defi-oracle.io / wss.defi-oracle.io → same backend as rpc-http-pub / rpc-ws-pub (VMID 2201)
update_proxy_host "rpc.defi-oracle.io" "http://${RPC_PUBLIC_1}:8545" true false && updated_count=$((updated_count + 1)) || { add_proxy_host "rpc.defi-oracle.io" "${RPC_PUBLIC_1}" 8545 true false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
update_proxy_host "wss.defi-oracle.io" "http://${RPC_PUBLIC_1}:8546" true false && updated_count=$((updated_count + 1)) || { add_proxy_host "wss.defi-oracle.io" "${RPC_PUBLIC_1}" 8546 true false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
# info.defi-oracle.io — Chain 138 info hub SPA on dedicated web LXC (default VMID 2410 / IP_INFO_DEFI_ORACLE_WEB).
# This stays on the primary public edge so publication does not depend on a long-lived Cloudflare tunnel object.
INFO_DEFI_ORACLE_UPSTREAM_IP="${INFO_DEFI_ORACLE_UPSTREAM_IP:-${IP_INFO_DEFI_ORACLE_WEB:-192.168.11.218}}"
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))
# 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.
CCIP_RELAY_MAINNET_CW_PUBLIC_HOST="${CCIP_RELAY_MAINNET_CW_PUBLIC_HOST:-}"
CCIP_RELAY_MAINNET_CW_UPSTREAM_IP="${CCIP_RELAY_MAINNET_CW_UPSTREAM_IP:-${PROXMOX_HOST_R630_01:-192.168.11.11}}"
CCIP_RELAY_MAINNET_CW_UPSTREAM_PORT="${CCIP_RELAY_MAINNET_CW_UPSTREAM_PORT:-9863}"
if [ -n "$CCIP_RELAY_MAINNET_CW_PUBLIC_HOST" ]; then
update_proxy_host "$CCIP_RELAY_MAINNET_CW_PUBLIC_HOST" "http://${CCIP_RELAY_MAINNET_CW_UPSTREAM_IP}:${CCIP_RELAY_MAINNET_CW_UPSTREAM_PORT}" false false && updated_count=$((updated_count + 1)) || { add_proxy_host "$CCIP_RELAY_MAINNET_CW_PUBLIC_HOST" "${CCIP_RELAY_MAINNET_CW_UPSTREAM_IP}" "${CCIP_RELAY_MAINNET_CW_UPSTREAM_PORT}" false false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
fi
# rpc.d-bis.org / rpc2.d-bis.org and WS variants → VMID 2201 (besu-rpc-public-1); add if missing to fix 405
update_proxy_host "rpc.d-bis.org" "http://${RPC_PUBLIC_1}:8545" true false && updated_count=$((updated_count + 1)) || { add_proxy_host "rpc.d-bis.org" "${RPC_PUBLIC_1}" 8545 true false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
update_proxy_host "rpc2.d-bis.org" "http://${RPC_PUBLIC_1}:8545" true false && updated_count=$((updated_count + 1)) || { add_proxy_host "rpc2.d-bis.org" "${RPC_PUBLIC_1}" 8545 true false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
@@ -379,6 +441,10 @@ done
update_proxy_host "data.d-bis.org" "http://${IP_DBIS_API:-192.168.11.155}:3000" false && updated_count=$((updated_count + 1)) || { add_proxy_host "data.d-bis.org" "${IP_DBIS_API:-192.168.11.155}" 3000 false true && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
# DApp (VMID 5801) — frontend-dapp for Chain 138 bridge
update_proxy_host "dapp.d-bis.org" "http://${IP_DAPP_LXC:-192.168.11.58}:80" false && updated_count=$((updated_count + 1)) || { add_proxy_host "dapp.d-bis.org" "${IP_DAPP_LXC:-192.168.11.58}" 80 false false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
# Gitea (VMID 104 default) — aligns primary NPMplus with config/ip-addresses.conf GITEA_PUBLIC_UPSTREAM_*
GITEA_PUBLIC_UPSTREAM_HOST="${GITEA_PUBLIC_UPSTREAM_HOST:-${IP_GITEA_INFRA:-192.168.11.31}}"
GITEA_PUBLIC_UPSTREAM_PORT="${GITEA_PUBLIC_UPSTREAM_PORT:-80}"
update_proxy_host "gitea.d-bis.org" "http://${GITEA_PUBLIC_UPSTREAM_HOST}:${GITEA_PUBLIC_UPSTREAM_PORT}" false && updated_count=$((updated_count + 1)) || { add_proxy_host "gitea.d-bis.org" "${GITEA_PUBLIC_UPSTREAM_HOST}" "${GITEA_PUBLIC_UPSTREAM_PORT}" false false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
# MIM4U - VMID 7810 (mim-web-1) @ ${IP_MIM_WEB:-192.168.11.37} - Web Frontend serves main site and proxies /api/* to 7811
update_proxy_host "mim4u.org" "http://${IP_MIM_WEB:-192.168.11.37}:80" false && updated_count=$((updated_count + 1)) || { add_proxy_host "mim4u.org" "${IP_MIM_WEB:-192.168.11.37}" 80 false true && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
update_proxy_host "www.mim4u.org" "http://${IP_MIM_WEB:-192.168.11.37}:80" false && updated_count=$((updated_count + 1)) || { add_proxy_host "www.mim4u.org" "${IP_MIM_WEB:-192.168.11.37}" 80 false true && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))

View File

@@ -0,0 +1,94 @@
#!/usr/bin/env bash
# Upsert only the NPMplus proxy host for CCIP mainnet-cw relay health (no full fleet update).
# Use when update-npmplus-proxy-hosts-api.sh is slow or you need a quick relay-only change.
#
# Tries POST (create) first so a new hostname does not require downloading the full proxy-host list
# (which can be slow or time out on large NPM instances).
#
# Env: NPM_URL, NPM_EMAIL, NPM_PASSWORD (from .env); optional:
# CCIP_RELAY_MAINNET_CW_PUBLIC_HOST (default relay-mainnet-cw.d-bis.org)
# CCIP_RELAY_MAINNET_CW_UPSTREAM_IP (default PROXMOX_HOST_R630_01 or 192.168.11.11)
# CCIP_RELAY_MAINNET_CW_UPSTREAM_PORT (default 9863)
# NPM_CURL_MAX_TIME (default 300)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
source "${PROJECT_ROOT}/config/ip-addresses.conf" 2>/dev/null || true
_orig_npm_url="${NPM_URL:-}"
_orig_npm_email="${NPM_EMAIL:-}"
_orig_npm_password="${NPM_PASSWORD:-}"
if [ -f "$PROJECT_ROOT/.env" ]; then set +u; source "$PROJECT_ROOT/.env"; set -u; fi
if [ -f "$PROJECT_ROOT/smom-dbis-138/.env" ]; then set +u; source "$PROJECT_ROOT/smom-dbis-138/.env"; set -u; fi
[ -n "$_orig_npm_url" ] && NPM_URL="$_orig_npm_url"
[ -n "$_orig_npm_email" ] && NPM_EMAIL="$_orig_npm_email"
[ -n "$_orig_npm_password" ] && NPM_PASSWORD="$_orig_npm_password"
source "${PROJECT_ROOT}/config/ip-addresses.conf" 2>/dev/null || true
# Default HTTPS — plain http:// on :81 often 301s to https:// and breaks token POST if curl -L downgrades body.
NPM_URL="${NPM_URL:-https://${IP_NPMPLUS}:81}"
NPM_EMAIL="${NPM_EMAIL:-nsatoshi2007@hotmail.com}"
NPM_PASSWORD="${NPM_PASSWORD:-}"
[ -z "$NPM_PASSWORD" ] && { echo "NPM_PASSWORD required (.env or export)"; exit 1; }
DOMAIN="${CCIP_RELAY_MAINNET_CW_PUBLIC_HOST:-relay-mainnet-cw.d-bis.org}"
UP_IP="${CCIP_RELAY_MAINNET_CW_UPSTREAM_IP:-${PROXMOX_HOST_R630_01:-192.168.11.11}}"
UP_PORT="${CCIP_RELAY_MAINNET_CW_UPSTREAM_PORT:-9863}"
NPM_CURL_MAX_TIME="${NPM_CURL_MAX_TIME:-300}"
curl_npm() { curl -s -k -L --http1.1 --connect-timeout 30 --max-time "$NPM_CURL_MAX_TIME" "$@"; }
try_connect() { curl -s -k -L -o /dev/null --connect-timeout 5 --max-time 20 "$1" 2>/dev/null; }
if ! try_connect "$NPM_URL/"; then
http_url="${NPM_URL/https:/http:}"
try_connect "$http_url/" && NPM_URL="$http_url"
fi
AUTH_JSON=$(jq -n --arg identity "$NPM_EMAIL" --arg secret "$NPM_PASSWORD" '{identity:$identity,secret:$secret}')
TOKEN=$(curl_npm -X POST "$NPM_URL/api/tokens" -H "Content-Type: application/json" -d "$AUTH_JSON" | jq -r '.token // empty')
[ -n "$TOKEN" ] && [ "$TOKEN" != "null" ] || { echo "NPM auth failed"; exit 1; }
ADV='add_header Referrer-Policy "strict-origin-when-cross-origin" always;'
PAYLOAD_ADD=$(jq -n \
--arg domain "$DOMAIN" \
--arg host "$UP_IP" \
--argjson port "$UP_PORT" \
--arg adv "$ADV" \
'{domain_names:[$domain],forward_scheme:"http",forward_host:$host,forward_port:$port,allow_websocket_upgrade:false,block_exploits:false,certificate_id:null,ssl_forced:false,advanced_config:$adv}')
echo "Trying create (POST) for $DOMAIN -> http://${UP_IP}:${UP_PORT}"
RESP=$(curl_npm -X POST "$NPM_URL/api/nginx/proxy-hosts" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "$PAYLOAD_ADD")
if echo "$RESP" | jq -e '.id' >/dev/null 2>&1; then
echo "OK created id=$(echo "$RESP" | jq -r .id)"
exit 0
fi
ERR_MSG=$(echo "$RESP" | jq -r '.message // .error.message // .error // empty' 2>/dev/null || echo "")
if ! echo "$ERR_MSG" | grep -qiE 'already|in use|exist|duplicate|unique'; then
echo "Create failed (not a duplicate case): $ERR_MSG"
echo "$RESP" | jq . 2>/dev/null || echo "$RESP"
exit 1
fi
echo "Host exists or name in use; fetching proxy list for PUT ($ERR_MSG)"
PROXY_JSON=$(curl_npm -X GET "$NPM_URL/api/nginx/proxy-hosts" -H "Authorization: Bearer $TOKEN")
if [ -z "$PROXY_JSON" ] || [ "$PROXY_JSON" = "[]" ]; then
echo "Empty proxy list response (timeout or NPM error). Try NPM_CURL_MAX_TIME=600 or run from LAN."
exit 28
fi
HOST_ID=$(echo "$PROXY_JSON" | jq -r --arg d "$DOMAIN" '.[] | select(.domain_names | type == "array") | select(any(.domain_names[]; (. | tostring | ascii_downcase) == ($d | ascii_downcase))) | .id' | head -n1)
if [ -z "$HOST_ID" ] || [ "$HOST_ID" = "null" ]; then
echo "Could not resolve proxy host id for $DOMAIN after duplicate error."
exit 1
fi
echo "Updating proxy host $DOMAIN (id=$HOST_ID) -> http://${UP_IP}:${UP_PORT}"
PAYLOAD_PUT=$(jq -n \
--arg host "$UP_IP" \
--argjson port "$UP_PORT" \
--arg adv "$ADV" \
'{forward_scheme:"http",forward_host:$host,forward_port:$port,allow_websocket_upgrade:false,block_exploits:false,advanced_config:$adv}')
RESP=$(curl_npm -X PUT "$NPM_URL/api/nginx/proxy-hosts/$HOST_ID" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "$PAYLOAD_PUT")
echo "$RESP" | jq -e '.id' >/dev/null && echo "OK updated" || { echo "$RESP" | jq . 2>/dev/null || echo "$RESP"; exit 1; }

View File

@@ -0,0 +1,85 @@
#!/usr/bin/env bash
# Run CCIP relay NPM upsert from a Proxmox host (default r630-01) with good LAN path to NPMplus.
# Expands NPM_* and CCIP_* into a remote bash script with printf %q (safe quoting; not passed on argv).
#
# Usage (repo root):
# bash scripts/nginx-proxy-manager/upsert-ccip-relay-mainnet-cw-via-ssh.sh
# Env:
# NPM_RELAY_UPSERT_SSH default root@192.168.11.11
# NPM_URL default http://192.168.11.167:81 (HTTP on LAN)
# load-project-env: NPM_EMAIL, NPM_PASSWORD, CCIP_RELAY_MAINNET_CW_*
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# shellcheck source=/dev/null
source "${PROJECT_ROOT}/scripts/lib/load-project-env.sh"
SSH_HOST="${NPM_RELAY_UPSERT_SSH:-root@192.168.11.11}"
# NPMplus admin returns 301 HTTP→HTTPS on :81; token POST must use HTTPS (see NPM_URL in .env).
NPM_URL_USE="${NPM_URL:-https://${IP_NPMPLUS:-192.168.11.167}:81}"
DOMAIN="${CCIP_RELAY_MAINNET_CW_PUBLIC_HOST:-relay-mainnet-cw.d-bis.org}"
UP_IP="${CCIP_RELAY_MAINNET_CW_UPSTREAM_IP:-${PROXMOX_HOST_R630_01:-192.168.11.11}}"
UP_PORT="${CCIP_RELAY_MAINNET_CW_UPSTREAM_PORT:-9863}"
[ -n "${NPM_PASSWORD:-}" ] || { echo "NPM_PASSWORD required (.env)"; exit 1; }
[ -n "${NPM_EMAIL:-}" ] || { echo "NPM_EMAIL required (.env)"; exit 1; }
echo "SSH $SSH_HOST → NPM $NPM_URL_USE$DOMAIN → http://${UP_IP}:${UP_PORT}"
# Unquoted heredoc: local expands $(printf '%q' ...) into the remote script; use \$ for remote expansions.
# shellcheck disable=SC2087
ssh -o BatchMode=yes -o ConnectTimeout=15 "$SSH_HOST" bash <<EOF
set -euo pipefail
NPM_URL=$(printf '%q' "$NPM_URL_USE")
NPM_EMAIL=$(printf '%q' "$NPM_EMAIL")
NPM_PASSWORD=$(printf '%q' "$NPM_PASSWORD")
DOMAIN=$(printf '%q' "$DOMAIN")
UP_IP=$(printf '%q' "$UP_IP")
UP_PORT=$(printf '%q' "$UP_PORT")
curl_npm() { curl -s -k -L --http1.1 --connect-timeout 20 --max-time 300 "\$@"; }
AUTH_JSON=\$(jq -n --arg identity "\$NPM_EMAIL" --arg secret "\$NPM_PASSWORD" '{identity:\$identity,secret:\$secret}')
TOKEN=\$(curl_npm -X POST "\$NPM_URL/api/tokens" -H "Content-Type: application/json" -d "\$AUTH_JSON" | jq -r '.token // empty')
[ -n "\$TOKEN" ] && [ "\$TOKEN" != "null" ] || { echo "NPM auth failed on remote"; exit 1; }
ADV='add_header Referrer-Policy "strict-origin-when-cross-origin" always;'
PAYLOAD_ADD=\$(jq -n \\
--arg domain "\$DOMAIN" \\
--arg host "\$UP_IP" \\
--argjson port "\$UP_PORT" \\
--arg adv "\$ADV" \\
'{domain_names:[\$domain],forward_scheme:"http",forward_host:\$host,forward_port:\$port,allow_websocket_upgrade:false,block_exploits:false,certificate_id:null,ssl_forced:false,advanced_config:\$adv}')
echo "POST create \$DOMAIN"
RESP=\$(curl_npm -X POST "\$NPM_URL/api/nginx/proxy-hosts" -H "Authorization: Bearer \$TOKEN" -H "Content-Type: application/json" -d "\$PAYLOAD_ADD")
if echo "\$RESP" | jq -e '.id' >/dev/null 2>&1; then
echo "OK created id=\$(echo "\$RESP" | jq -r .id)"
exit 0
fi
ERR_MSG=\$(echo "\$RESP" | jq -r '.message // .error.message // .error // empty' 2>/dev/null || echo "")
if ! echo "\$ERR_MSG" | grep -qiE 'already|in use|exist|duplicate|unique'; then
echo "Create failed: \$ERR_MSG"
echo "\$RESP" | jq . 2>/dev/null || echo "\$RESP"
exit 1
fi
echo "Duplicate / exists; GET proxy list for PUT (\$ERR_MSG)"
PROXY_JSON=\$(curl_npm -X GET "\$NPM_URL/api/nginx/proxy-hosts" -H "Authorization: Bearer \$TOKEN")
HOST_ID=\$(echo "\$PROXY_JSON" | jq -r --arg d "\$DOMAIN" '.[] | select(.domain_names | type == "array") | select(any(.domain_names[]; (. | tostring | ascii_downcase) == (\$d | ascii_downcase))) | .id' | head -n1)
[ -n "\$HOST_ID" ] && [ "\$HOST_ID" != "null" ] || { echo "Could not find host id for \$DOMAIN"; exit 1; }
PAYLOAD_PUT=\$(jq -n \\
--arg host "\$UP_IP" \\
--argjson port "\$UP_PORT" \\
--arg adv "\$ADV" \\
'{forward_scheme:"http",forward_host:\$host,forward_port:\$port,allow_websocket_upgrade:false,block_exploits:false,advanced_config:\$adv}')
echo "PUT id=\$HOST_ID"
RESP=\$(curl_npm -X PUT "\$NPM_URL/api/nginx/proxy-hosts/\$HOST_ID" -H "Authorization: Bearer \$TOKEN" -H "Content-Type: application/json" -d "\$PAYLOAD_PUT")
echo "\$RESP" | jq -e '.id' >/dev/null && echo "OK updated" || { echo "\$RESP" | jq . 2>/dev/null; exit 1; }
EOF
echo "Done."

View File

@@ -0,0 +1,88 @@
#!/usr/bin/env bash
# Create or update NPMplus proxy host: omdnl.org + www.omdnl.org → static nginx upstream.
#
# Add Cloudflare A records first (scripts/cloudflare/configure-omdnl-org-dns.sh).
# Then request certificates in NPM (SSL) once DNS resolves.
#
# Env: NPM_URL, NPM_EMAIL, NPM_PASSWORD; optional:
# OMDNL_ORG_UPSTREAM_IP (default IP_OMDNL_ORG_WEB / 192.168.11.222)
# OMDNL_ORG_UPSTREAM_PORT (default 80)
# NPM_CURL_MAX_TIME (default 300)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
source "${PROJECT_ROOT}/config/ip-addresses.conf" 2>/dev/null || true
_orig_npm_url="${NPM_URL:-}"
_orig_npm_email="${NPM_EMAIL:-}"
_orig_npm_password="${NPM_PASSWORD:-}"
if [ -f "$PROJECT_ROOT/.env" ]; then set +u; source "$PROJECT_ROOT/.env"; set -u; fi
[ -n "$_orig_npm_url" ] && NPM_URL="$_orig_npm_url"
[ -n "$_orig_npm_email" ] && NPM_EMAIL="$_orig_npm_email"
[ -n "$_orig_npm_password" ] && NPM_PASSWORD="$_orig_npm_password"
source "${PROJECT_ROOT}/config/ip-addresses.conf" 2>/dev/null || true
NPM_URL="${NPM_URL:-https://${IP_NPMPLUS:-192.168.11.167}:81}"
NPM_EMAIL="${NPM_EMAIL:-}"
NPM_PASSWORD="${NPM_PASSWORD:-}"
[ -z "$NPM_PASSWORD" ] && { echo "NPM_PASSWORD required (.env or export)" >&2; exit 1; }
UP_IP="${OMDNL_ORG_UPSTREAM_IP:-${IP_OMDNL_ORG_WEB:-192.168.11.222}}"
UP_PORT="${OMDNL_ORG_UPSTREAM_PORT:-80}"
NPM_CURL_MAX_TIME="${NPM_CURL_MAX_TIME:-300}"
curl_npm() { curl -s -k -L --http1.1 --connect-timeout 30 --max-time "$NPM_CURL_MAX_TIME" "$@"; }
try_connect() { curl -s -k -L -o /dev/null --connect-timeout 5 --max-time 20 "$1" 2>/dev/null; }
if ! try_connect "$NPM_URL/"; then
http_url="${NPM_URL/https:/http:}"
try_connect "$http_url/" && NPM_URL="$http_url"
fi
AUTH_JSON=$(jq -n --arg identity "$NPM_EMAIL" --arg secret "$NPM_PASSWORD" '{identity:$identity,secret:$secret}')
TOKEN=$(curl_npm -X POST "$NPM_URL/api/tokens" -H "Content-Type: application/json" -d "$AUTH_JSON" | jq -r '.token // empty')
[ -n "$TOKEN" ] && [ "$TOKEN" != "null" ] || { echo "NPM auth failed" >&2; exit 1; }
ADV='add_header Referrer-Policy "strict-origin-when-cross-origin" always;'
PAYLOAD_ADD=$(jq -n \
--argjson domains '["omdnl.org","www.omdnl.org"]' \
--arg host "$UP_IP" \
--argjson port "$UP_PORT" \
--arg adv "$ADV" \
'{domain_names:$domains,forward_scheme:"http",forward_host:$host,forward_port:$port,allow_websocket_upgrade:false,block_exploits:true,certificate_id:null,ssl_forced:false,advanced_config:$adv}')
echo "Trying create (POST) omdnl.org + www → http://${UP_IP}:${UP_PORT}"
RESP=$(curl_npm -X POST "$NPM_URL/api/nginx/proxy-hosts" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "$PAYLOAD_ADD")
if echo "$RESP" | jq -e '.id' >/dev/null 2>&1; then
echo "OK created id=$(echo "$RESP" | jq -r .id)"
exit 0
fi
ERR_MSG=$(echo "$RESP" | jq -r '.message // .error.message // .error // empty' 2>/dev/null || echo "")
if ! echo "$ERR_MSG" | grep -qiE 'already|in use|exist|duplicate|unique'; then
echo "Create failed (not a duplicate case): $ERR_MSG" >&2
echo "$RESP" | jq . 2>/dev/null || echo "$RESP"
exit 1
fi
echo "Host exists; fetching proxy list for PUT ($ERR_MSG)"
PROXY_JSON=$(curl_npm -X GET "$NPM_URL/api/nginx/proxy-hosts" -H "Authorization: Bearer $TOKEN")
HOST_ID=$(echo "$PROXY_JSON" | jq -r '
.[] | select(.domain_names | type == "array") |
select(any(.domain_names[]; (. | tostring | ascii_downcase) == "omdnl.org")) |
.id' | head -n1)
if [ -z "$HOST_ID" ] || [ "$HOST_ID" = "null" ]; then
echo "Could not resolve proxy host id for omdnl.org." >&2
exit 1
fi
echo "Updating proxy host id=$HOST_ID -> http://${UP_IP}:${UP_PORT}"
PAYLOAD_PUT=$(jq -n \
--arg host "$UP_IP" \
--argjson port "$UP_PORT" \
--arg adv "$ADV" \
'{forward_scheme:"http",forward_host:$host,forward_port:$port,allow_websocket_upgrade:false,block_exploits:true,advanced_config:$adv}')
RESP=$(curl_npm -X PUT "$NPM_URL/api/nginx/proxy-hosts/$HOST_ID" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "$PAYLOAD_PUT")
echo "$RESP" | jq -e '.id' >/dev/null && echo "OK updated" || { echo "$RESP" | jq . 2>/dev/null || echo "$RESP"; exit 1; }