Follow-ups: DNS dry-run/zone-only, Order NPM IDs, E2E Location assert, the-order block_exploits

- update-all-dns-to-public-ip.sh: --dry-run (no CF API), --zone-only=ZONE, help before .env, env CLOUDFLARE_DNS_DRY_RUN/DNS_ZONE_ONLY
- update-sankofa-npmplus-proxy-hosts.sh: the-order + www.the-order by ID (env SANKOFA_NPM_ID_THE_ORDER, SANKOFA_NPM_ID_WWW_THE_ORDER, THE_ORDER_UPSTREAM_*)
- update-npmplus-proxy-hosts-api.sh: the-order.sankofa.nexus uses block_exploits false like sankofa portal
- verify-end-to-end-routing.sh: E2E_WWW_CANONICAL_BASE + Location validation (fail on wrong apex); keep local redirect vars
- docs: ALL_VMIDS www 301 lines, E2E_ENDPOINTS_LIST verifier/DNS notes; AGENTS.md Cloudflare script pointer

Made-with: Cursor
This commit is contained in:
defiQUG
2026-03-27 11:27:09 -07:00
parent 50a3973662
commit 17b923ffdf
7 changed files with 142 additions and 24 deletions

View File

@@ -24,6 +24,7 @@ Orchestration for Proxmox VE, Chain 138 (`smom-dbis-138/`), explorers, NPMplus,
| Portal login + Keycloak systemd + `.env` (prints password once) | `./scripts/deployment/enable-sankofa-portal-login-7801.sh` (`--dry-run` first) |
| Completable (no LAN) | `./scripts/run-completable-tasks-from-anywhere.sh` |
| Operator (LAN + secrets) | `./scripts/run-all-operator-tasks-from-lan.sh` (use `--skip-backup` if `NPM_PASSWORD` unset) |
| Cloudflare bulk DNS → `PUBLIC_IP` | `./scripts/update-all-dns-to-public-ip.sh` — use **`--dry-run`** and **`--zone-only=sankofa.nexus`** (or `d-bis.org` / `mim4u.org` / `defi-oracle.io`) to limit scope; see script header |
## Rules of engagement

View File

@@ -249,9 +249,9 @@ The following VMIDs have been permanently removed:
**Public Domains** (NPMplus routing):
- `sankofa.nexus` → Routes to `http://192.168.11.51:3000` (Sankofa Portal/VMID 7801) ✅
- `www.sankofa.nexus`Routes to `http://192.168.11.51:3000` (Sankofa Portal/VMID 7801)
- `www.sankofa.nexus`Same upstream as apex; NPM **`advanced_config`** issues **301** to **`https://sankofa.nexus`** (preserve path/query via `$request_uri`).
- `phoenix.sankofa.nexus` → Routes to `http://192.168.11.50:4000` (Phoenix API/VMID 7800) ✅
- `www.phoenix.sankofa.nexus`Routes to `http://192.168.11.50:4000` (Phoenix API/VMID 7800)
- `www.phoenix.sankofa.nexus`Same upstream; **301** to **`https://phoenix.sankofa.nexus`**.
- `the-order.sankofa.nexus` / `www.the-order.sankofa.nexus` → OSJ management portal (secure auth). App source: **the_order** at `~/projects/the_order`. NPMplus **target** order-haproxy `http://192.168.11.39:80` (VMID **10210**) when that stack is serving. Until then, `update-npmplus-proxy-hosts-api.sh` defaults upstream to Sankofa portal `http://192.168.11.51:3000` (7801); override with `THE_ORDER_UPSTREAM_IP` / `THE_ORDER_UPSTREAM_PORT` when switching to HAProxy. **`www.the-order.sankofa.nexus`** is configured for **301** to **`https://the-order.sankofa.nexus`** (same pattern as `www.sankofa` / `www.phoenix`).
- `studio.sankofa.nexus` → Routes to `http://192.168.11.72:8000` (Sankofa Studio / VMID 7805)

View File

@@ -28,9 +28,9 @@
| secure.mim4u.org | web | https://secure.mim4u.org | MIM4U secure portal. |
| training.mim4u.org | web | https://training.mim4u.org | MIM4U training site. |
| sankofa.nexus | web | https://sankofa.nexus | Sankofa Nexus root / web. |
| www.sankofa.nexus | web | https://www.sankofa.nexus | Sankofa Nexus www. |
| phoenix.sankofa.nexus | web | https://phoenix.sankofa.nexus | Phoenix (Sankofa) web app. |
| www.phoenix.sankofa.nexus | web | https://www.phoenix.sankofa.nexus | Phoenix www. |
| www.sankofa.nexus | web | https://www.sankofa.nexus | **301** to `https://sankofa.nexus` (canonical apex; NPM `advanced_config`). |
| phoenix.sankofa.nexus | web | https://phoenix.sankofa.nexus | Phoenix API (7800); E2E uses `/health` for HTTPS check. |
| www.phoenix.sankofa.nexus | web | https://www.phoenix.sankofa.nexus | **301** to `https://phoenix.sankofa.nexus` (canonical apex; NPM `advanced_config`). |
| the-order.sankofa.nexus | web | https://the-order.sankofa.nexus | OSJ (Sovereign Military Order of Malta) management portal behind secure auth; app source repo **the_order** at `~/projects/the_order` (NPM upstream: order-haproxy 10210 when live, else interim portal 7801 per `update-npmplus-proxy-hosts-api.sh`). |
| www.the-order.sankofa.nexus | web | https://www.the-order.sankofa.nexus | **301** to `https://the-order.sankofa.nexus` (canonical apex; NPM `advanced_config`). |
| studio.sankofa.nexus | web | https://studio.sankofa.nexus | Sankofa Studio (FusionAI Creator) at VMID 7805. |
@@ -168,6 +168,10 @@ When running from outside LAN or when backends are down, the following endpoints
**Verifier behavior (2026-03):** `openssl s_client` is wrapped with `timeout` (`E2E_OPENSSL_TIMEOUT` default 15s, `E2E_OPENSSL_X509_TIMEOUT` default 5s) so `--profile=private` / `--profile=all` cannot hang. **`--profile=all`** merges private and public `E2E_OPTIONAL_WHEN_FAIL` lists for temporary regressions. Install **`wscat`** (`npm install -g wscat`) for full WSS JSON-RPC checks; the script uses `wscat -n` to match `curl -k`, and now treats a clean `wscat` exit as a successful full WebSocket check even when the tool prints no JSON output.
**Canonical www redirects (2026-03):** For `www.sankofa.nexus`, `www.phoenix.sankofa.nexus`, and `www.the-order.sankofa.nexus`, HTTP **301**/**308** must include a **`Location`** whose host matches the expected apex (`E2E_WWW_CANONICAL_BASE` in `verify-end-to-end-routing.sh`). Wrong apex → HTTPS **fail**. Missing `Location`**warn**.
**Cloudflare bulk DNS:** `scripts/update-all-dns-to-public-ip.sh` supports **`--dry-run`** (no API calls) and **`--zone-only=sankofa.nexus`** (or `d-bis.org` | `mim4u.org` | `defi-oracle.io`) to limit blast radius. Env: `CLOUDFLARE_DNS_DRY_RUN=1`, `DNS_ZONE_ONLY=…`.
**WebSocket test-format warnings:** Older runs may show "connection established but RPC test failed" when `wscat` is used: the upgrade succeeded but the verifier expected printable `"result"` output. The script now accepts either explicit JSON output or a clean `wscat` exit, so current runs treat those WS checks as pass when the connection completes successfully. The script also accepts Chain 138 chainId `0x8a` in output.
### Remediation (when you want these to pass from public)

View File

@@ -361,7 +361,8 @@ update_proxy_host "keycloak.sankofa.nexus" "http://${IP_KEYCLOAK}:8080" false fa
IP_ORDER_HAPROXY="${IP_ORDER_HAPROXY:-192.168.11.39}"
THE_ORDER_UPSTREAM_IP="${THE_ORDER_UPSTREAM_IP:-${IP_SANKOFA_PORTAL}}"
THE_ORDER_UPSTREAM_PORT="${THE_ORDER_UPSTREAM_PORT:-${SANKOFA_PORTAL_PORT}}"
update_proxy_host "the-order.sankofa.nexus" "http://${THE_ORDER_UPSTREAM_IP}:${THE_ORDER_UPSTREAM_PORT}" false && updated_count=$((updated_count + 1)) || { add_proxy_host "the-order.sankofa.nexus" "${THE_ORDER_UPSTREAM_IP}" "${THE_ORDER_UPSTREAM_PORT}" false false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
# block_exploits false — same policy as sankofa.nexus portal (Next/API-friendly; avoid 405 on some POST paths)
update_proxy_host "the-order.sankofa.nexus" "http://${THE_ORDER_UPSTREAM_IP}:${THE_ORDER_UPSTREAM_PORT}" false false && updated_count=$((updated_count + 1)) || { add_proxy_host "the-order.sankofa.nexus" "${THE_ORDER_UPSTREAM_IP}" "${THE_ORDER_UPSTREAM_PORT}" false false && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
update_proxy_host "www.the-order.sankofa.nexus" "http://${THE_ORDER_UPSTREAM_IP}:${THE_ORDER_UPSTREAM_PORT}" false false "https://the-order.sankofa.nexus" && updated_count=$((updated_count + 1)) || { add_proxy_host "www.the-order.sankofa.nexus" "${THE_ORDER_UPSTREAM_IP}" "${THE_ORDER_UPSTREAM_PORT}" false false "https://the-order.sankofa.nexus" && updated_count=$((updated_count + 1)); } || failed_count=$((failed_count + 1))
# Sankofa Studio (FusionAI) — VMID 7805; UI at /studio/ on same origin (port 8000). Prefer IP_SANKOFA_STUDIO from ip-addresses.conf / .env
IP_SANKOFA_STUDIO="${IP_SANKOFA_STUDIO:-192.168.11.72}"

View File

@@ -3,6 +3,16 @@
# Sets all records to DNS only mode (gray cloud) for direct NAT routing
# Supports multiple zones: sankofa.nexus, d-bis.org, mim4u.org, defi-oracle.io
# UDM Pro port forwarding: 76.53.10.36:80/443 → ${IP_NPMPLUS:-${IP_NPMPLUS:-192.168.11.167}}:80/443 (NPMplus)
#
# WARNING: For each hostname, existing CNAME records are deleted before an A record is created.
# By default all configured zones are processed. Use --zone-only to limit scope.
#
# Usage:
# ./scripts/update-all-dns-to-public-ip.sh
# ./scripts/update-all-dns-to-public-ip.sh --dry-run
# ./scripts/update-all-dns-to-public-ip.sh --zone-only=sankofa.nexus
# ./scripts/update-all-dns-to-public-ip.sh --dry-run --zone-only=d-bis.org
# Env (optional): CLOUDFLARE_DNS_DRY_RUN=1, DNS_ZONE_ONLY=sankofa.nexus (same as flags)
set -euo pipefail
@@ -11,6 +21,22 @@ 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
# --help before .env (so operators can read usage without credentials)
for _arg in "$@"; do
if [[ "$_arg" == "--help" || "$_arg" == "-h" ]]; then
cat <<'EOF'
Cloudflare DNS → PUBLIC_IP (A records, DNS-only / gray cloud).
Options:
--dry-run Log intended changes only; no Cloudflare API calls.
--zone-only=ZONE ZONE one of: sankofa.nexus | d-bis.org | mim4u.org | defi-oracle.io
-h, --help This message.
Requires repo .env with Cloudflare auth and zone IDs (see script header).
EOF
exit 0
fi
done
# Colors
RED='\033[0;31m'
@@ -63,6 +89,23 @@ ZONE_D_BIS_ORG="${CLOUDFLARE_ZONE_ID_D_BIS_ORG:-${CLOUDFLARE_ZONE_ID:-}}"
ZONE_MIM4U_ORG="${CLOUDFLARE_ZONE_ID_MIM4U_ORG:-}"
ZONE_DEFI_ORACLE_IO="${CLOUDFLARE_ZONE_ID_DEFI_ORACLE_IO:-}"
# CLI / env: dry-run and single-zone scope
CLOUDFLARE_DNS_DRY_RUN="${CLOUDFLARE_DNS_DRY_RUN:-0}"
DNS_ZONE_ONLY="${DNS_ZONE_ONLY:-}"
parse_dns_update_cli_args() {
for arg in "$@"; do
case "$arg" in
--dry-run) CLOUDFLARE_DNS_DRY_RUN=1 ;;
--zone-only=*) DNS_ZONE_ONLY="${arg#*=}" ;;
esac
done
}
parse_dns_update_cli_args "$@"
if [ "$CLOUDFLARE_DNS_DRY_RUN" = "1" ]; then
log_warn "DRY-RUN: no Cloudflare list/create/update/delete API calls will be made."
fi
# Function to make Cloudflare API request
cf_api_request() {
local method="$1"
@@ -152,6 +195,11 @@ create_or_update_dns_record() {
fi
log_info "Processing: $full_name$ip (proxied: $proxied)"
if [ "$CLOUDFLARE_DNS_DRY_RUN" = "1" ]; then
log_success "[dry-run] Would remove CNAME(s) on $full_name if any, then upsert A → $ip (proxied=$proxied)"
return 0
fi
# Check for existing CNAME records (must delete before creating A record)
local all_records=$(get_all_dns_records "$zone_id" "$full_name")
@@ -256,12 +304,29 @@ main() {
echo ""
log_info "Public IP: $PUBLIC_IP"
log_info "Proxy Mode: DNS Only (gray cloud)"
if [ -n "$DNS_ZONE_ONLY" ]; then
log_info "Zone filter: only $DNS_ZONE_ONLY"
fi
echo ""
local total_failures=0
local run_sankofa=1 run_dbis=1 run_mim4u=1 run_defi=1
if [ -n "$DNS_ZONE_ONLY" ]; then
run_sankofa=0 run_dbis=0 run_mim4u=0 run_defi=0
case "$DNS_ZONE_ONLY" in
sankofa.nexus) run_sankofa=1 ;;
d-bis.org) run_dbis=1 ;;
mim4u.org) run_mim4u=1 ;;
defi-oracle.io) run_defi=1 ;;
*)
log_error "Unknown --zone-only=$DNS_ZONE_ONLY (expected: sankofa.nexus | d-bis.org | mim4u.org | defi-oracle.io)"
return 2
;;
esac
fi
# sankofa.nexus domain records
if [ -n "$ZONE_SANKOFA_NEXUS" ]; then
if [ "$run_sankofa" = 1 ] && [ -n "$ZONE_SANKOFA_NEXUS" ]; then
SANKOFA_RECORDS=(
"@" # sankofa.nexus
"www" # www.sankofa.nexus
@@ -273,12 +338,12 @@ main() {
if ! process_zone "$ZONE_SANKOFA_NEXUS" "sankofa.nexus" "${SANKOFA_RECORDS[@]}"; then
((total_failures++))
fi
else
elif [ "$run_sankofa" = 1 ]; then
log_warn "Skipping sankofa.nexus (no zone ID configured)"
fi
# d-bis.org domain records
if [ -n "$ZONE_D_BIS_ORG" ]; then
if [ "$run_dbis" = 1 ] && [ -n "$ZONE_D_BIS_ORG" ]; then
DBIS_RECORDS=(
"rpc-http-pub" # rpc-http-pub.d-bis.org
"rpc-ws-pub" # rpc-ws-pub.d-bis.org
@@ -297,12 +362,12 @@ main() {
if ! process_zone "$ZONE_D_BIS_ORG" "d-bis.org" "${DBIS_RECORDS[@]}"; then
((total_failures++))
fi
else
elif [ "$run_dbis" = 1 ]; then
log_warn "Skipping d-bis.org (no zone ID configured)"
fi
# mim4u.org domain records
if [ -n "$ZONE_MIM4U_ORG" ]; then
if [ "$run_mim4u" = 1 ] && [ -n "$ZONE_MIM4U_ORG" ]; then
MIM4U_RECORDS=(
"@" # mim4u.org
"www" # www.mim4u.org
@@ -312,12 +377,12 @@ main() {
if ! process_zone "$ZONE_MIM4U_ORG" "mim4u.org" "${MIM4U_RECORDS[@]}"; then
((total_failures++))
fi
else
elif [ "$run_mim4u" = 1 ]; then
log_warn "Skipping mim4u.org (no zone ID configured)"
fi
# defi-oracle.io domain records
if [ -n "$ZONE_DEFI_ORACLE_IO" ]; then
if [ "$run_defi" = 1 ] && [ -n "$ZONE_DEFI_ORACLE_IO" ]; then
DEFI_ORACLE_RECORDS=(
"explorer" # explorer.defi-oracle.io (Blockscout - same as explorer.d-bis.org)
"rpc.public-0138" # rpc.public-0138.defi-oracle.io
@@ -327,7 +392,7 @@ main() {
if ! process_zone "$ZONE_DEFI_ORACLE_IO" "defi-oracle.io" "${DEFI_ORACLE_RECORDS[@]}"; then
((total_failures++))
fi
else
elif [ "$run_defi" = 1 ]; then
log_warn "Skipping defi-oracle.io (no zone ID configured)"
fi
@@ -354,5 +419,5 @@ main() {
return $total_failures
}
# Run main function
main "$@"
# Run (CLI already parsed above)
main

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
# Update Sankofa NPMplus proxy hosts (portal + Phoenix API) via API.
# Prefer: scripts/nginx-proxy-manager/update-npmplus-proxy-hosts-api.sh (domain-based, runs with operator waves).
# NPM proxy host IDs below match backup backup-20260325_183932 (36); if your DB differs, use the main script instead.
# 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 (36, 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)"
@@ -27,12 +27,16 @@ 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}"
THE_ORDER_UPSTREAM_IP="${THE_ORDER_UPSTREAM_IP:-${IP_SANKOFA_PORTAL}}"
THE_ORDER_UPSTREAM_PORT="${THE_ORDER_UPSTREAM_PORT:-${SANKOFA_PORTAL_PORT}}"
# NPM proxy host IDs: sankofa=3, www.sankofa=4, phoenix=5, www.phoenix=6 (typical fresh install order)
# 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=(
@@ -40,6 +44,8 @@ declare -A PROXY_HOSTS=(
["$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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

View File

@@ -193,6 +193,30 @@ declare -A E2E_HTTPS_PATH=(
["studio.sankofa.nexus"]="/studio/"
)
# Expected apex URL for NPM www → canonical 301/308 (Location must use this host; path from E2E_HTTPS_PATH must appear when set)
declare -A E2E_WWW_CANONICAL_BASE=(
["www.sankofa.nexus"]="https://sankofa.nexus"
["www.phoenix.sankofa.nexus"]="https://phoenix.sankofa.nexus"
["www.the-order.sankofa.nexus"]="https://the-order.sankofa.nexus"
)
# Returns 0 if Location URL matches expected canonical apex (and HTTPS path suffix when non-empty).
e2e_www_redirect_location_ok() {
local loc_val="$1" base="$2" path="${3:-}"
local loc_lc base_lc
loc_lc=$(printf '%s' "$loc_val" | tr '[:upper:]' '[:lower:]')
base_lc=$(printf '%s' "$base" | tr '[:upper:]' '[:lower:]')
if [[ "$loc_lc" != "$base_lc" && "$loc_lc" != "$base_lc/"* ]]; then
return 1
fi
if [ -n "$path" ] && [ "$path" != "/" ]; then
local p_lc
p_lc=$(printf '%s' "$path" | tr '[:upper:]' '[:lower:]')
[[ "$loc_lc" == *"$p_lc"* ]] || return 1
fi
return 0
}
# --list-endpoints: print selected profile endpoints and exit (no tests)
if [[ "$LIST_ENDPOINTS" == "1" ]]; then
echo ""
@@ -332,7 +356,8 @@ test_domain() {
if [ -n "$http_code" ]; then
# NPM canonical www → apex (advanced_config return 301/308)
_e2e_canonical_www_redirect=""
local _e2e_canonical_www_redirect=""
local location_hdr=""
case "$domain" in
www.sankofa.nexus|www.phoenix.sankofa.nexus|www.the-order.sankofa.nexus)
if [ "$http_code" = "301" ] || [ "$http_code" = "308" ]; then
@@ -342,9 +367,25 @@ test_domain() {
esac
if [ -n "$_e2e_canonical_www_redirect" ]; then
location_hdr=$(echo "$headers" | grep -iE '^[Ll]ocation:' | head -1 | tr -d '\r' || echo "")
log_success "HTTPS: $domain returned HTTP $http_code (canonical redirect)${https_path:+ at ${https_url}}"
result=$(echo "$result" | jq --arg code "$http_code" --arg time "$time_total" --arg loc "$location_hdr" \
'.tests.https = {"status": "pass", "http_code": ($code | tonumber), "response_time_seconds": ($time | tonumber), "canonical_redirect": true, "location_header": $loc}')
loc_val=$(printf '%s' "$location_hdr" | sed -E 's/^[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:[[:space:]]*//' | sed 's/[[:space:]]*$//')
expected_base="${E2E_WWW_CANONICAL_BASE[$domain]:-}"
if [ -z "$loc_val" ]; then
log_warn "HTTPS: $domain returned HTTP $http_code but no Location header${https_path:+ (${https_url})}"
result=$(echo "$result" | jq --arg code "$http_code" --arg time "$time_total" \
'.tests.https = {"status": "warn", "http_code": ($code | tonumber), "response_time_seconds": ($time | tonumber), "note": "missing Location on redirect"}')
elif [ -z "$expected_base" ]; then
log_warn "HTTPS: $domain redirect pass (no E2E_WWW_CANONICAL_BASE entry)"
result=$(echo "$result" | jq --arg code "$http_code" --arg time "$time_total" --arg loc "$location_hdr" \
'.tests.https = {"status": "pass", "http_code": ($code | tonumber), "response_time_seconds": ($time | tonumber), "canonical_redirect": true, "location_header": $loc}')
elif ! e2e_www_redirect_location_ok "$loc_val" "$expected_base" "$https_path"; then
log_error "HTTPS: $domain Location mismatch (got \"$loc_val\", expected prefix \"$expected_base\" with path \"${https_path:-/}\")"
result=$(echo "$result" | jq --arg code "$http_code" --arg time "$time_total" --arg loc "$loc_val" --arg exp "$expected_base" --arg pth "${https_path:-}" \
'.tests.https = {"status": "fail", "http_code": ($code | tonumber), "response_time_seconds": ($time | tonumber), "reason": "location_mismatch", "location": $loc, "expected_prefix": $exp, "expected_path_suffix": $pth}')
else
log_success "HTTPS: $domain returned HTTP $http_code (canonical redirect → $loc_val)${https_path:+ at ${https_url}}"
result=$(echo "$result" | jq --arg code "$http_code" --arg time "$time_total" --arg loc "$location_hdr" \
'.tests.https = {"status": "pass", "http_code": ($code | tonumber), "response_time_seconds": ($time | tonumber), "canonical_redirect": true, "location_header": $loc}')
fi
elif [ "$http_code" -ge 200 ] && [ "$http_code" -lt 400 ]; then
log_success "HTTPS: $domain returned HTTP $http_code (Time: ${time_total}s)${https_path:+ at ${https_path}}"