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() { reconcile_vpn_readiness() {
grep -qE '^COMPOSE_PROFILES=.*vpn' .env 2>/dev/null || return 0 grep -qE '^COMPOSE_PROFILES=.*vpn' .env 2>/dev/null || return 0
sleep 3 local host vpn_ready=false
local running vpn_ready host="$(sed -n 's/^VPN_INTERNAL_HOST=//p' .env | tail -1)"
running="$(docker compose --profile vpn ps --status running --services 2>/dev/null || true)"
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
vpn_ready=true # inside it listens in a network namespace that was torn down underneath it
else # — nothing errors, the address simply refuses connections, and a
vpn_ready=false # container-state check reports everything fine.
printf '\033[1;33m !\033[0m %s\n' "The tunnel gateway is not running — clients will not be given its resolver." 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
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 fi
if [[ "$(sed -n 's/^VPN_READY=//p' .env | tail -1)" != "$vpn_ready" ]]; then 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 # Nothing errors; connections to the tunnel address are simply refused, which
# looks exactly like the gateway never having worked. # looks exactly like the gateway never having worked.
if grep -qE '^COMPOSE_PROFILES=.*vpn' .env 2>/dev/null; then 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 docker compose --profile vpn restart vpn-dns vpn-gateway >/dev/null 2>&1 || true
fi fi