Wait for the tunnel interface, and prove the gateway answers
tests / pest (push) Successful in 7m23s Details
tests / assets (push) Successful in 25s Details
tests / release (push) Successful in 5s Details

Two things reported success while the tunnel console was unreachable.

The services were restarted the moment the hub container counted as started,
but the hub brings wg0 up as part of its start command — so they were binding an
address that did not exist yet. They now wait for the interface.

And readiness was a container-state check. A container can be up and running
while the process inside it listens in a network namespace that was torn down
underneath it: nothing errors, the address simply refuses connections, and
"running" reports everything fine. It asks the gateway now, and only a real
answer counts — because the consequence of getting this wrong is a client
config naming a resolver that is not there, which takes the device's whole name
resolution with it for as long as the tunnel is up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat/portal-design tested-20260727-0946-c464957
Claude 2026-07-27 10:24:29 +02:00
parent d2d79b4f5f
commit c4649571df
1 changed files with 25 additions and 8 deletions

View File

@ -113,15 +113,25 @@ deployed="$(cat "$STATE_FILE" 2>/dev/null || echo '')"
reconcile_vpn_readiness() {
grep -qE '^COMPOSE_PROFILES=.*vpn' .env 2>/dev/null || return 0
sleep 3
local running vpn_ready
running="$(docker compose --profile vpn ps --status running --services 2>/dev/null || true)"
local host vpn_ready=false
host="$(sed -n 's/^VPN_INTERNAL_HOST=//p' .env | tail -1)"
if grep -q '^vpn-gateway$' <<<"$running" && grep -q '^vpn-dns$' <<<"$running"; then
# ASKED, not assumed. A container can be up and running while the process
# inside it listens in a network namespace that was torn down underneath it
# — nothing errors, the address simply refuses connections, and a
# container-state check reports everything fine.
for _ in $(seq 1 10); do
if docker compose exec -T vpn-gateway sh -c \
"wget -q --spider --no-check-certificate https://127.0.0.1/ 2>/dev/null \
|| wget -q -O /dev/null --no-check-certificate https://127.0.0.1/ 2>/dev/null" >/dev/null 2>&1; then
vpn_ready=true
else
vpn_ready=false
printf '\033[1;33m !\033[0m %s\n' "The tunnel gateway is not running — clients will not be given its resolver."
break
fi
sleep 2
done
if [[ "$vpn_ready" != "true" ]]; then
printf '\033[1;33m !\033[0m %s\n' "The tunnel gateway is not answering on ${host} — clients will not be given its resolver."
fi
if [[ "$(sed -n 's/^VPN_READY=//p' .env | tail -1)" != "$vpn_ready" ]]; then
@ -322,6 +332,13 @@ docker compose restart queue queue-provisioning scheduler reverb
# Nothing errors; connections to the tunnel address are simply refused, which
# looks exactly like the gateway never having worked.
if grep -qE '^COMPOSE_PROFILES=.*vpn' .env 2>/dev/null; then
# And only once wg0 is back. The hub brings the interface up as part of its
# start command, so restarting these the instant the container is "started"
# has them binding an address that does not exist yet.
for _ in $(seq 1 30); do
docker compose exec -T queue-provisioning ip -4 addr show wg0 2>/dev/null | grep -q 'inet ' && break
sleep 2
done
docker compose --profile vpn restart vpn-dns vpn-gateway >/dev/null 2>&1 || true
fi