#!/usr/bin/env bash set -euo pipefail # Update Sankofa NPMplus proxy hosts (portal + Phoenix API + the-order) via API by numeric host ID. # Prefer for production: scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.sh (domain-based, full fleet). # NPM proxy host IDs below match backup backup-20260325_183932 (3–6, 7, 59); override with SANKOFA_NPM_ID_* if your DB differs. 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 # Load environment variables if [ -f "$PROJECT_ROOT/.env" ]; then set -a # shellcheck source=/dev/null source "$PROJECT_ROOT/.env" set +a fi NPM_URL="${NPM_URL:-https://${IP_NPMPLUS}:81}" NPM_EMAIL="${NPM_EMAIL:-nsatoshi2007@hotmail.com}" NPM_PASSWORD="${NPM_PASSWORD:-}" NPM_CURL_MAX_TIME="${NPM_CURL_MAX_TIME:-180}" IP_SANKOFA_PORTAL="${IP_SANKOFA_PORTAL:-${IP_SERVICE_51:-192.168.11.51}}" IP_SANKOFA_PHOENIX_API="${IP_SANKOFA_PHOENIX_API:-${IP_SERVICE_50:-192.168.11.50}}" SANKOFA_PORTAL_PORT="${SANKOFA_PORTAL_PORT:-3000}" SANKOFA_PHOENIX_API_PORT="${SANKOFA_PHOENIX_API_PORT:-4000}" IP_ORDER_HAPROXY="${IP_ORDER_HAPROXY:-192.168.11.39}" THE_ORDER_UPSTREAM_IP="${THE_ORDER_UPSTREAM_IP:-${IP_ORDER_HAPROXY}}" THE_ORDER_UPSTREAM_PORT="${THE_ORDER_UPSTREAM_PORT:-80}" # NPM proxy host IDs: sankofa=3, www.sankofa=4, phoenix=5, www.phoenix=6; the-order=7, www.the-order=59 (typical; verify in NPM UI) SANKOFA_NPM_ID_ROOT="${SANKOFA_NPM_ID_ROOT:-3}" SANKOFA_NPM_ID_WWW="${SANKOFA_NPM_ID_WWW:-4}" SANKOFA_NPM_ID_PHOENIX="${SANKOFA_NPM_ID_PHOENIX:-5}" SANKOFA_NPM_ID_WWW_PHOENIX="${SANKOFA_NPM_ID_WWW_PHOENIX:-6}" SANKOFA_NPM_ID_THE_ORDER="${SANKOFA_NPM_ID_THE_ORDER:-7}" SANKOFA_NPM_ID_WWW_THE_ORDER="${SANKOFA_NPM_ID_WWW_THE_ORDER:-59}" # Optional 4th field: canonical HTTPS apex — NPM advanced_config 301 (www → apex). Matches update-npmplus-proxy-hosts-api.sh. declare -A PROXY_HOSTS=( ["$SANKOFA_NPM_ID_ROOT"]="sankofa.nexus|${IP_SANKOFA_PORTAL}|${SANKOFA_PORTAL_PORT}|" ["$SANKOFA_NPM_ID_WWW"]="www.sankofa.nexus|${IP_SANKOFA_PORTAL}|${SANKOFA_PORTAL_PORT}|https://sankofa.nexus" ["$SANKOFA_NPM_ID_PHOENIX"]="phoenix.sankofa.nexus|${IP_SANKOFA_PHOENIX_API}|${SANKOFA_PHOENIX_API_PORT}|" ["$SANKOFA_NPM_ID_WWW_PHOENIX"]="www.phoenix.sankofa.nexus|${IP_SANKOFA_PHOENIX_API}|${SANKOFA_PHOENIX_API_PORT}|https://phoenix.sankofa.nexus" ["$SANKOFA_NPM_ID_THE_ORDER"]="the-order.sankofa.nexus|${THE_ORDER_UPSTREAM_IP}|${THE_ORDER_UPSTREAM_PORT}|" ["$SANKOFA_NPM_ID_WWW_THE_ORDER"]="www.the-order.sankofa.nexus|${THE_ORDER_UPSTREAM_IP}|${THE_ORDER_UPSTREAM_PORT}|https://the-order.sankofa.nexus" ) echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "🔄 Updating Sankofa NPMplus Proxy Hosts" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Authenticate echo "🔐 Authenticating to NPMplus..." TOKEN_RESPONSE=$(curl -s -k --connect-timeout 15 --max-time "$NPM_CURL_MAX_TIME" -X POST "$NPM_URL/api/tokens" \ -H "Content-Type: application/json" \ -d "{\"identity\":\"$NPM_EMAIL\",\"secret\":\"$NPM_PASSWORD\"}") TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.token // empty' 2>/dev/null || echo "") if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then echo "❌ Authentication failed" exit 1 fi echo "✅ Authentication successful" echo "" # Function to update proxy host update_proxy_host() { local host_id=$1 local domain=$2 local target_ip=$3 local target_port=$4 local canonical_https="${5:-}" echo "📝 Updating Proxy Host $host_id: $domain → $target_ip:$target_port" # Get current proxy host CURRENT_HOST=$(curl -s -k --connect-timeout 15 --max-time "$NPM_CURL_MAX_TIME" -X GET "$NPM_URL/api/nginx/proxy-hosts/$host_id" \ -H "Authorization: Bearer $TOKEN" 2>/dev/null || echo "{}") if [ "$(echo "$CURRENT_HOST" | jq -r '.id // empty')" = "" ]; then echo "❌ Proxy host $host_id not found" return 1 fi # NPMplus rejects full-document PUT (e.g. locations: null) — send only allowed forward fields. local scheme="http" local adv_line="" if [ -n "$canonical_https" ]; then adv_line="return 301 ${canonical_https}\$request_uri;" fi UPDATE_PAYLOAD=$(jq -n \ --arg scheme "$scheme" \ --arg hostname "$target_ip" \ --argjson port "$(echo "$target_port" | sed 's/[^0-9]//g')" \ --argjson websocket false \ --argjson block_exploits false \ --arg adv "$adv_line" \ '{ 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)') RESPONSE=$(curl -s -k --connect-timeout 15 --max-time "${NPM_CURL_MAX_TIME:-120}" -X PUT "$NPM_URL/api/nginx/proxy-hosts/$host_id" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "$UPDATE_PAYLOAD") UPDATED_ID=$(echo "$RESPONSE" | jq -r '.id // empty' 2>/dev/null || echo "") if [ -n "$UPDATED_ID" ] && [ "$UPDATED_ID" != "null" ]; then echo "✅ Successfully updated proxy host $host_id" return 0 else echo "❌ Failed to update proxy host $host_id" echo "$RESPONSE" | jq '.' 2>/dev/null || echo "$RESPONSE" if echo "$RESPONSE" | jq -e '.error.code == 403' >/dev/null 2>&1; then echo " (403 often means NPM user lacks permission to mutate proxy hosts; check UI role or use an admin identity.)" fi return 1 fi } # Update all proxy hosts SUCCESS=0 FAILED=0 for host_id in "${!PROXY_HOSTS[@]}"; do IFS='|' read -r domain target_ip target_port canonical_https _ <<< "${PROXY_HOSTS[$host_id]}" if update_proxy_host "$host_id" "$domain" "$target_ip" "$target_port" "$canonical_https"; then ((SUCCESS++)) else ((FAILED++)) fi echo "" done echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "📊 Update Summary" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "✅ Successfully updated: $SUCCESS" echo "❌ Failed: $FAILED" echo "" if [ $FAILED -eq 0 ]; then echo "🎉 All proxy hosts updated successfully!" exit 0 else echo "⚠️ Some proxy hosts failed to update" exit 1 fi