Files
proxmox/scripts/nginx-proxy-manager/update-npmplus-fourth-proxy-hosts.sh
defiQUG bea1903ac9
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
Sync all local changes: docs, config, scripts, submodule refs, verification evidence
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 15:46:06 -08:00

214 lines
9.8 KiB
Bash

#!/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.
# 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
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
[ -f "$PROJECT_ROOT/.env" ] && set +u && source "$PROJECT_ROOT/.env" 2>/dev/null || true && set -u
# Fourth NPMplus (dev/Codespaces)
NPMPLUS_FOURTH_IP="${IP_NPMPLUS_FOURTH:-192.168.11.170}"
IP_DEV_VM="${IP_DEV_VM:-192.168.11.59}"
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}"
# Prefer fourth NPMplus URL so .env NPM_URL (e.g. first instance) does not override
NPM_URL="${NPM_URL_FOURTH:-https://${NPMPLUS_FOURTH_IP}:81}"
NPM_EMAIL="${NPM_EMAIL_FOURTH:-${NPM_EMAIL:-admin@example.org}}"
NPM_PASSWORD="${NPM_PASSWORD_FOURTH:-${NPM_PASSWORD:-}}"
if [ -z "$NPM_PASSWORD" ]; then
echo "Set NPM_PASSWORD or NPM_PASSWORD_FOURTH. Example: get from fourth NPMplus container (VMID TBD) or set in .env"
exit 1
fi
echo "Adding or updating proxy hosts on NPMplus Fourth (dev/Codespaces) at $NPM_URL..."
# Authenticate (NPM 2 may use cookie-only)
COOKIE_JAR="/tmp/npm_fourth_cookies_$$"
cleanup_cookies() { rm -f "$COOKIE_JAR"; }
trap cleanup_cookies EXIT
AUTH_JSON=$(jq -n --arg identity "$NPM_EMAIL" --arg secret "$NPM_PASSWORD" '{identity:$identity,secret:$secret}')
TOKEN_RESPONSE=$(curl -s -k -X POST "$NPM_URL/api/tokens" -H "Content-Type: application/json" -d "$AUTH_JSON" -c "$COOKIE_JAR")
TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.token // .accessToken // .access_token // .data.token // empty' 2>/dev/null)
USE_COOKIE_AUTH=0
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
if echo "$TOKEN_RESPONSE" | jq -e '.expires' >/dev/null 2>&1; then
USE_COOKIE_AUTH=1
echo "Using cookie-based auth (NPM 2 style)."
else
echo "Authentication failed"
MSG=$(echo "$TOKEN_RESPONSE" | jq -r '.message // .error // .error.message // empty' 2>/dev/null)
[ -n "$MSG" ] && echo "API: $MSG"
exit 1
fi
fi
curl_auth() {
if [ "$USE_COOKIE_AUTH" = "1" ]; then
curl -s -k -b "$COOKIE_JAR" "$@"
else
curl -s -k -H "Authorization: Bearer $TOKEN" "$@"
fi
}
add_proxy_host() {
local domain=$1
local fwd_host=$2
local fwd_port=$3
local ws=${4:-false}
local payload
payload=$(jq -n \
--arg domain "$domain" \
--arg host "$fwd_host" \
--argjson port "$fwd_port" \
--argjson ws "$ws" \
'{
domain_names: [$domain],
forward_scheme: "http",
forward_host: $host,
forward_port: $port,
allow_websocket_upgrade: $ws,
block_exploits: false,
certificate_id: null,
ssl_forced: false
}')
local resp
resp=$(curl_auth -X POST "$NPM_URL/api/nginx/proxy-hosts" \
-H "Content-Type: application/json" \
-d "$payload")
local id
id=$(echo "$resp" | jq -r '.id // empty' 2>/dev/null)
if [ -n "$id" ] && [ "$id" != "null" ]; then
echo " Added: $domain -> $fwd_host:$fwd_port (websocket=$ws)"
return 0
else
echo " Skip (may exist): $domain - $(echo "$resp" | jq -r '.message // .error // "unknown"' 2>/dev/null)"
return 1
fi
}
# Update existing proxy host (forward_host, forward_port). Use when "already in use" on POST.
# Set DEBUG_NPM_FOURTH=1 to print API responses when update fails.
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" 2>/dev/null)
# NPM 2 may return array or { data: array }; normalize to array for jq
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 empty end' 2>/dev/null)
if [ -z "$arr" ] || [ "$arr" = "null" ]; then
[ -n "${DEBUG_NPM_FOURTH:-}" ] && echo " [DEBUG] GET response (first 400 chars): $(echo "$hosts_json" | head -c 400)"
return 1
fi
local id
id=$(echo "$arr" | jq -r --arg dom "$domain" '(if type == "array" then . else [.] end) | .[] | select(.domain_names | type == "array") | select(.domain_names[] == $dom) | .id' 2>/dev/null | head -n1)
if [ -z "$id" ] || [ "$id" = "null" ]; then
[ -n "${DEBUG_NPM_FOURTH:-}" ] && echo " [DEBUG] GET response (first 300 chars): $(echo "$hosts_json" | head -c 300)"
return 1
fi
local payload
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, 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 -> $fwd_host:$fwd_port (websocket=$ws)"
return 0
fi
# NPM 2 camelCase fallback
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: $domain -> $fwd_host:$fwd_port (websocket=$ws)"
return 0
fi
# Minimal payload (some NPM 2 instances reject block_exploits / blockCommonExploits on PUT)
payload=$(jq -n --arg host "$fwd_host" --argjson port "$fwd_port" '{ forward_host: $host, forward_port: $port }')
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 (minimal PUT)"
return 0
fi
# GET full record; build PUT payload with only allowed fields (exclude meta, created_on, modified_on)
local host_obj
host_obj=$(echo "$arr" | jq -c --arg dom "$domain" '(if type == "array" then . else [.] end) | .[] | 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" '
. + {forward_host: $host, forward_port: $port, allow_websocket_upgrade: $ws}
| del(.meta, .created_on, .modified_on)
' 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 (full body PUT)"
return 0
fi
[ -n "${DEBUG_NPM_FOURTH:-}" ] && echo " [DEBUG] PUT response: $resp"
fi
# Try only fields that appear in NPM 2 schema (no locations, no meta)
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 (schema fields PUT)"
return 0
fi
[ -n "${DEBUG_NPM_FOURTH:-}" ] && echo " [DEBUG] PUT schema response: $resp"
fi
fi
echo " Warning: could not update $domain via API. Set Forward host to $fwd_host and port $fwd_port in NPM UI for $domain."
echo " Manual steps: docs/05-network/CHECK_ALL_UPDATES_AND_CLOUDFLARE_TUNNELS.md §8 (https://192.168.11.170:81)"
return 1
}
# Add or update: try add first; if "already in use", update existing to correct forward_host/port
add_or_update_proxy_host() {
local domain=$1
local fwd_host=$2
local fwd_port=$3
local ws=${4:-false}
if add_proxy_host "$domain" "$fwd_host" "$fwd_port" "$ws"; then return 0; fi
update_proxy_host "$domain" "$fwd_host" "$fwd_port" "$ws"
}
# Dev VM (Gitea on 3000); dev and codespaces as aliases
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 "codespaces.d-bis.org" "$IP_DEV_VM" 3000 false || true
# Proxmox VE admin panels (port 8006; websocket required for console)
add_or_update_proxy_host "pve.ml110.d-bis.org" "$PROXMOX_ML110" 8006 true || true
add_or_update_proxy_host "pve.r630-01.d-bis.org" "$PROXMOX_R630_01" 8006 true || true
add_or_update_proxy_host "pve.r630-02.d-bis.org" "$PROXMOX_R630_02" 8006 true || true
echo ""
echo "Done. Request Let's Encrypt certs in NPMplus UI (Fourth instance) for: dev, gitea, codespaces, pve.ml110, pve.r630-01, pve.r630-02."
echo "Proxmox admin: https://pve.ml110.d-bis.org, https://pve.r630-01.d-bis.org, https://pve.r630-02.d-bis.org"