diff --git a/.gitea/workflows/docker.yml b/.gitea/workflows/docker.yml index 38079d8..5fea119 100644 --- a/.gitea/workflows/docker.yml +++ b/.gitea/workflows/docker.yml @@ -74,28 +74,38 @@ jobs: echo "container uid: $uid" [ "$uid" = "1000" ] || { echo "::error::image runs as uid $uid, expected 1000"; exit 1; } - # 2. Boot the server and probe it over HTTP. - docker run -d --name familiar-smoke -e FAMILIAR_HTTP_TOKEN="$TOKEN" -p 18080:8080 "$IMG" + # 2. Boot the server and probe it from *inside* its own container via + # `docker exec`. On self-hosted runners the job step runs in a container + # that shares the host's Docker socket but sits on a different network, + # so it can reach neither a published host port (-p lands on the runner + # host, not this step's netns) nor the server container's bridge IP + # (different network → connect hangs). `docker exec` goes through the + # socket and runs the probe in the server's own netns, where + # 127.0.0.1:8080 always works regardless of runner topology. The image + # ships python + httpx, so no curl or extra pull is needed. + docker run -d --name familiar-smoke -e FAMILIAR_HTTP_TOKEN="$TOKEN" "$IMG" + + # probe "" -> prints the status code + probe() { docker exec familiar-smoke python -c "import httpx; print(($1).status_code)"; } + for i in $(seq 1 30); do - if curl -fsS "http://127.0.0.1:18080/healthz" >/dev/null 2>&1; then break; fi + if docker exec familiar-smoke python -c \ + "import httpx; httpx.get('http://127.0.0.1:8080/healthz', timeout=5)" >/dev/null 2>&1; then + break + fi sleep 1 done echo "== /healthz (expect 200) ==" - code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:18080/healthz") + code=$(probe "httpx.get('http://127.0.0.1:8080/healthz', timeout=10)") [ "$code" = "200" ] || { echo "::error::/healthz returned $code"; docker logs familiar-smoke; exit 1; } echo "== MCP without token (expect 401) ==" - code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "http://127.0.0.1:18080/mcp" \ - -H 'Accept: application/json, text/event-stream' -H 'Content-Type: application/json' \ - -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}') + code=$(probe "httpx.post('http://127.0.0.1:8080/mcp', timeout=10, headers={'Accept':'application/json, text/event-stream','Content-Type':'application/json'}, json={'jsonrpc':'2.0','id':1,'method':'initialize','params':{}})") [ "$code" = "401" ] || { echo "::error::unauthenticated MCP returned $code"; exit 1; } echo "== MCP with valid token (expect 200) ==" - code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "http://127.0.0.1:18080/mcp" \ - -H "Authorization: Bearer $TOKEN" \ - -H 'Accept: application/json, text/event-stream' -H 'Content-Type: application/json' \ - -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"ci","version":"1"}}}') + code=$(probe "httpx.post('http://127.0.0.1:8080/mcp', timeout=10, headers={'Authorization':'Bearer $TOKEN','Accept':'application/json, text/event-stream','Content-Type':'application/json'}, json={'jsonrpc':'2.0','id':1,'method':'initialize','params':{'protocolVersion':'2025-06-18','capabilities':{},'clientInfo':{'name':'ci','version':'1'}}})") [ "$code" = "200" ] || { echo "::error::authenticated MCP returned $code"; docker logs familiar-smoke; exit 1; } echo "Image smoke-test passed."