hermes vhost: dial netavark's host address, not the LAN IP (fixes the 502)
hermes-webui is Network=host, so 8787 is a bare host-netns bind, and netavark DNATs only *published* ports. From Caddy's bridge netns 192.168.0.143:8787 and 10.89.1.1:8787 are both refused, while host.containers.internal:8787 (169.254.1.2) answers 200 — measured live 2026-09-16 from inside the Caddy container. The dashboard's published 9119 answers on that same LAN IP, which is what made the old upstream look sound. The vhost comment asserted "192.168.0.143:8787 is open" from that bridge; it is not, so the claim is replaced with the measurement, and the hermes-webui quadlet comment carries the same correction. The seam guarding this invariant accepted any numeric upstream, so the LAN IP passed it; it now pins the measured-working form and fails on the LAN IP (teeth verified by substitution).
This commit is contained in:
1 parent
65fbef63dc
commit
d14e0a94eb
3 files changed
+30
-15
No files matched your search
+8
-5
@@ -176,12 +176,15 @@ pihole.franksdank.cloud {
|
|||||||
# here enters the http://:8016 auth chain. Internal-only by DNS absence: there is
|
# here enters the http://:8016 auth chain. Internal-only by DNS absence: there is
|
||||||
# no public Porkbun A record for this name, so only LAN clients (Pi-hole
|
# no public Porkbun A record for this name, so only LAN clients (Pi-hole
|
||||||
# addn-hosts → 192.168.0.143) can resolve it at all; DNS-01 issuance via the
|
# addn-hosts → 192.168.0.143) can resolve it at all; DNS-01 issuance via the
|
||||||
# Porkbun module needs no public record. Upstream is the host LAN IP rather than
|
# Porkbun module needs no public record. Upstream is `host.containers.internal`
|
||||||
# localhost because Caddy itself lives on dankclaw_default: from that bridge the
|
# — netavark's host address, 169.254.1.2 — NOT the host LAN IP: hermes-webui is
|
||||||
# gateway address 10.89.1.1 has nothing serving 8787 (connection refused), while
|
# `Network=host`, so 8787 is a host-netns bind and not a published port, and
|
||||||
# 192.168.0.143:8787 is open.
|
# netavark DNATs only published ports (192.168.0.143:9119, the dashboard below,
|
||||||
|
# answers from this bridge; 192.168.0.143:8787 does not). Measured live
|
||||||
|
# 2026-09-16 inside the Caddy container: 192.168.0.143:8787 and 10.89.1.1:8787
|
||||||
|
# both REFUSED, host.containers.internal:8787 → 200.
|
||||||
hermes.franksdank.cloud {
|
hermes.franksdank.cloud {
|
||||||
reverse_proxy 192.168.0.143:8787
|
reverse_proxy host.containers.internal:8787
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Hermes dashboard (admin UI; published 9119 on dankclaw_default) --------
|
# --- Hermes dashboard (admin UI; published 9119 on dankclaw_default) --------
|
||||||
|
|||||||
@@ -16,10 +16,12 @@
|
|||||||
# can drive the browser; a bridge-network client could not reach it at all.
|
# can drive the browser; a bridge-network client could not reach it at all.
|
||||||
# The server binds 0.0.0.0:8787 of the host netns, and that is deliberate:
|
# The server binds 0.0.0.0:8787 of the host netns, and that is deliberate:
|
||||||
# this unit is host-networked (the CDP daemon above needs a shared loopback), so
|
# this unit is host-networked (the CDP daemon above needs a shared loopback), so
|
||||||
# Caddy cannot reach it by container name and fronts the guarded vhost
|
# Caddy cannot reach it by container name and fronts the vhost
|
||||||
# hermes.franksdank.cloud with `reverse_proxy 192.168.0.143:8787` (see the
|
# hermes.franksdank.cloud with `reverse_proxy host.containers.internal:8787`
|
||||||
# hermes block in dankclaw/Caddyfile, which records the same constraint). A
|
# (netavark's host address, 169.254.1.2) — not the host LAN IP, which Caddy's
|
||||||
# loopback bind was tried on 2026-09-16 for a tighter posture and broke that
|
# bridge netns cannot reach for a bare host-netns bind; the measured asymmetry
|
||||||
|
# with published ports is recorded in the hermes block of dankclaw/Caddyfile.
|
||||||
|
# A loopback bind was tried on 2026-09-16 for a tighter posture and broke that
|
||||||
# vhost, because a bind is also the only way an off-host process can reach a
|
# vhost, because a bind is also the only way an off-host process can reach a
|
||||||
# host-networked container: the LAN address and the tunnel's localhost hop are
|
# host-networked container: the LAN address and the tunnel's localhost hop are
|
||||||
# both served by the wildcard, and a loopback-only bind serves only the latter.
|
# both served by the wildcard, and a loopback-only bind serves only the latter.
|
||||||
|
|||||||
+16
-6
@@ -44,14 +44,24 @@ webui_service_yaml() {
|
|||||||
caddy="$REPO_ROOT/dankclaw/Caddyfile"
|
caddy="$REPO_ROOT/dankclaw/Caddyfile"
|
||||||
grep -q 'HERMES_WEBUI_PASSWORD' <<< "$y" \
|
grep -q 'HERMES_WEBUI_PASSWORD' <<< "$y" \
|
||||||
|| { echo "password gate missing"; return 1; }
|
|| { echo "password gate missing"; return 1; }
|
||||||
# This unit is host-networked, so Caddy cannot reach it by container name:
|
# This unit is host-networked, so Caddy cannot reach it by container name.
|
||||||
# hermes.franksdank.cloud proxies to the host's LAN address. The app's bind
|
# The app's bind is load-bearing for that vhost — a loopback-only bind was
|
||||||
# is therefore load-bearing for that vhost — a loopback-only bind was tried
|
# tried on 2026-09-16 and broke it (502), while host networking means there
|
||||||
# on 2026-09-16 and broke it (502), while host networking means there is no
|
# is no port forwarder to bridge the gap.
|
||||||
# port forwarder to bridge the gap.
|
#
|
||||||
|
# The upstream must be netavark's HOST ADDRESS, not the host's LAN IP.
|
||||||
|
# 8787 is a bare host-netns bind and netavark DNATs only *published* ports,
|
||||||
|
# so from Caddy's bridge netns 192.168.0.143:8787 and 10.89.1.1:8787 are
|
||||||
|
# both refused, while host.containers.internal:8787 (169.254.1.2) answers
|
||||||
|
# 200 — measured 2026-09-16 from inside the Caddy container. The dashboard's
|
||||||
|
# published 9119 is reachable on the LAN IP precisely because it IS
|
||||||
|
# published. Pin the measured-working form so reverting to the LAN IP cannot
|
||||||
|
# pass silently.
|
||||||
local upstream
|
local upstream
|
||||||
upstream=$(grep -A 6 '^hermes\.franksdank\.cloud {' "$caddy" | grep -oE 'reverse_proxy [0-9.]+:[0-9]+' | head -1)
|
upstream=$(grep -A 6 '^hermes\.franksdank\.cloud {' "$caddy" | grep -oE 'reverse_proxy [^ {]+' | head -1)
|
||||||
[ -n "$upstream" ] || { echo "no hermes vhost upstream in the Caddyfile"; return 1; }
|
[ -n "$upstream" ] || { echo "no hermes vhost upstream in the Caddyfile"; return 1; }
|
||||||
|
grep -qE '^reverse_proxy (host\.containers\.internal|169\.254\.1\.2):8787$' <<< "$upstream" \
|
||||||
|
|| { echo "hermes upstream is '$upstream', not netavark's host address (host.containers.internal:8787)"; return 1; }
|
||||||
grep -q 'Environment=HERMES_WEBUI_HOST=0.0.0.0' <<< "$y" \
|
grep -q 'Environment=HERMES_WEBUI_HOST=0.0.0.0' <<< "$y" \
|
||||||
|| { echo "webui must bind all interfaces: $upstream cannot reach loopback"; return 1; }
|
|| { echo "webui must bind all interfaces: $upstream cannot reach loopback"; return 1; }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user