diff --git a/.gitea/workflows/gates.yml b/.gitea/workflows/gates.yml index 165c3ed..bc6b3b2 100644 --- a/.gitea/workflows/gates.yml +++ b/.gitea/workflows/gates.yml @@ -347,18 +347,36 @@ jobs: # `-p 127.0.0.1::8080` takes an ephemeral host port instead of a # fixed 18080, which two concurrent runs on this daemon cannot both # bind. The actual port is read back with `docker port`. + # + # The command and the sysctl mirror deploy/docker/compose.yaml, + # because that is the invocation this gate exists to prove. The + # invocation is the sole configuration authority (milestone-20 ruling + # 1): the image's bare `run` grades the database, and a fresh + # /var/lib/nxdns volume holds no upstream, so it exits 2 with + # NoUsableUpstreams before it ever binds a port. net=$(docker inspect "$(hostname)" \ -f '{{range $k, $v := .NetworkSettings.Networks}}{{$k}}{{end}}' \ 2>/dev/null || true) cid=$(docker create --name "$SMOKE_NAME" \ ${net:+--network "$net"} \ -p 127.0.0.1::8080 \ - "$CI_IMAGE") + --sysctl net.ipv4.ip_unprivileged_port_start=0 \ + "$CI_IMAGE" run --config=/etc/nxdns/config.zon) trap 'docker rm -f "$SMOKE_NAME" >/dev/null 2>&1 || true' EXIT docker cp etc-nxdns/config.zon "$SMOKE_NAME:/etc/nxdns/config.zon" docker start "$SMOKE_NAME" - hostport=$(docker port "$SMOKE_NAME" 8080/tcp 2>/dev/null | head -1 | awk -F: '{ print $NF }') + # Before anything that assumes a live container. `docker port` fails + # on one that already exited, and under `set -e` that failure is the + # whole diagnosis the log gets — the container's own stderr never + # reaches CI. + if [ "$(docker inspect -f '{{.State.Running}}' "$cid")" != "true" ]; then + echo "container exited during startup" + docker logs "$cid" || true + exit 1 + fi + + hostport=$(docker port "$SMOKE_NAME" 8080/tcp | head -1 | awk -F: '{ print $NF }') ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$cid") echo "published host port: ${hostport:-none}, container ip: ${ip:-none}" healthy=""