Phase 3: dual bind per Sam's decision (DoD 3 amended, DoD 1/4/8 re-evidenced)

Sam answered the bind question on AUD-133 (2026-10-26):
"A - dual bind: 127.0.0.1:8096 and 192.168.20.13:8096".

- docker-compose: publish 127.0.0.1 and ${APP_LAN_IP:-192.168.20.13}, never 0.0.0.0
- deploy.sh: carry APP_LAN_IP into the release .env, --lan-ip flag, health gate
  now requires /healthz 200 on loopback AND the LAN address
- rehearse.sh: assert both binds, no wildcard, LAN reachability; KEEP_REHEARSAL
  now really keeps the container/image; N3 waits up to 20s instead of 3s
- docs/phase-3-evidence.md: decision recorded, new measured output

DoD: 1, 3 (as amended by Sam), 4, 8 met with recorded output. 2, 5, 6 deferred
on the Phase 2 binary; 7 is Sam's Caddy apply. rehearse.sh 24/24, exit 0.
This commit is contained in:
System agent
2026-10-26 17:44:22 +11:00
parent 8b3209fab5
commit e6240c1e8e
5 changed files with 147 additions and 56 deletions

View File

@@ -28,6 +28,10 @@ DEFAULT_ROOT="/home/sam/deployment/resume"
SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ROOT="${DEPLOY_ROOT:-$DEFAULT_ROOT}"
PORT="${APP_PORT:-$DEFAULT_PORT}"
# Caddy on .35 proxies to this host's LAN address (phase doc §3.3), so the
# container publishes on loopback *and* on that address. Sam's decision on
# AUD-133, 2026-10-26: "A - dual bind: 127.0.0.1:8096 and 192.168.20.13:8096".
LAN_IP="${APP_LAN_IP:-192.168.20.13}"
DO_ROLLBACK=0
HEALTH_TIMEOUT="${HEALTH_TIMEOUT:-40}"
@@ -39,6 +43,7 @@ Usage: deploy.sh [options]
--source DIR build from DIR instead of the repo this script lives in
--root DIR release root (default /home/sam/deployment/resume)
--port N app port (default 8096)
--lan-ip ADDR LAN address the container also publishes on (default 192.168.20.13)
--health-timeout N seconds to wait for /healthz (default 40)
-h, --help this text
@@ -46,6 +51,7 @@ Environment:
GO path to the go toolchain (auto-detected, incl. /nix/store)
DEPLOY_ROOT same as --root
APP_PORT same as --port
APP_LAN_IP same as --lan-ip
EOF
}
@@ -55,6 +61,7 @@ while [[ $# -gt 0 ]]; do
--source) SRC_DIR="$(cd "$2" && pwd)"; shift 2 ;;
--root) ROOT="$2"; shift 2 ;;
--port) PORT="$2"; shift 2 ;;
--lan-ip) LAN_IP="$2"; shift 2 ;;
--health-timeout) HEALTH_TIMEOUT="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "unknown argument: $1" >&2; usage >&2; exit 2 ;;
@@ -110,6 +117,17 @@ check_health() {
fi
fi
log "health: $url -> 200"
# The container also publishes on the LAN address because that is what Caddy
# on .35 proxies to. A loopback-only success would be a site nobody can reach.
local lan_url lan_code
lan_url="http://${LAN_IP}:${PORT}/healthz"
lan_code=$(curl -s --max-time 3 -o /dev/null -w '%{http_code}' "$lan_url" 2>/dev/null || true)
if [[ "$lan_code" != "200" ]]; then
log "health: $lan_url -> HTTP ${lan_code:-none} (LAN bind missing?)"
return 1
fi
log "health: $lan_url -> 200"
return 0
}
@@ -218,7 +236,7 @@ do_deploy() {
echo "built_on=$(hostname)"
echo "binary_sha256=$(sha256sum "$release/bin/server" | cut -d' ' -f1)"
} > "$release/manifest.txt"
printf 'APP_VERSION=%s\nAPP_PORT=%s\n' "$ts" "$PORT" > "$release/.env"
printf 'APP_VERSION=%s\nAPP_PORT=%s\nAPP_LAN_IP=%s\n' "$ts" "$PORT" "$LAN_IP" > "$release/.env"
# 3. Runtime image, tagged with this release.
log "deploy: docker build resume/app:$ts"