diff --git a/.forgejo/workflows/diag-cache-probe.yml b/.forgejo/workflows/diag-cache-probe.yml new file mode 100644 index 0000000..5ca1370 --- /dev/null +++ b/.forgejo/workflows/diag-cache-probe.yml @@ -0,0 +1,146 @@ +name: Diag — cache server reachability (#234) + +# THROWAWAY DIAGNOSTIC — never merge. Lives on its own branch for #234. +# +# Since #232 no workflow calls the Actions cache: in July both the restore +# (getCacheEntry) and the save (reserveCache) ended in "Request timeout". The +# suspected cause is that the job container cannot reach the cache address the +# runner hands it. This job checks that from inside a job container, without +# host access: +# 1. which cache address the runner advertises (path redacted: it may carry a +# per-run token, and this repository's job logs are public); +# 2. raw TCP reachability of that address, next to other ports and other host +# addresses, to tell "dropped" from "refused" from "open"; +# 3. an end-to-end round trip through actions/cache with a tiny payload. +# The job is green only if the payload comes back from the cache server. +# +# Steps that use bash features set `shell: bash`: inside a container the runner +# defaults to `sh`, and a job-level `defaults` block is refused by the runner. +# The runner starts those steps as `bash -e -o pipefail`, so every probe that +# is expected to fail must swallow its own exit code (run 453 stopped at the +# first TIMEOUT for that reason). + +on: + pull_request: + paths: + - '.forgejo/workflows/diag-cache-probe.yml' + +permissions: + contents: read + +jobs: + probe: + runs-on: ubuntu + container: ubuntu:22.04 + timeout-minutes: 20 + steps: + - name: Cache address and TCP reachability + shell: bash + run: | + echo "Runner-provided variables (names only):" + compgen -e | grep -E 'ACTIONS|RUNNER|FORGEJO|GITEA' | sort | sed 's/^/ /' || true + compgen -e | grep -E '_RUNNER_VERSION$' | while read -r v; do echo " $v=${!v}"; done || true + + url="${ACTIONS_CACHE_URL:-}" + if [ -z "$url" ]; then + echo "ACTIONS_CACHE_URL is empty: the runner gives this job no cache server." + exit 0 + fi + base=$(printf '%s' "$url" | sed -E 's#^(https?://[^/]+).*#\1#') + scheme=${base%%://*} + hostport=${base#*://} + case "$hostport" in + \[*\]:*) host=${hostport%]:*}; host=${host#[}; port=${hostport##*]:} ;; + *:*) host=${hostport%:*}; port=${hostport##*:} ;; + *) host=$hostport; port="" ;; + esac + if [ -z "$port" ]; then + if [ "$scheme" = https ]; then port=443; else port=80; fi + fi + shown=$host + case "$host" in *:*) shown="[$host]" ;; esac + echo + echo "Cache address advertised by the runner: $scheme://$shown:$port/" + forge=$(getent ahostsv4 git.lacompagniemaximus.com | awk '{ print $1; exit }') || true + echo "git.lacompagniemaximus.com resolves to (IPv4): ${forge:-unresolved}" + echo "Container addresses: $(hostname -I 2>/dev/null || echo unknown)" + gw_hex=$(awk '$2 == "00000000" { print $3; exit }' /proc/net/route) + gw="" + if [ -n "$gw_hex" ]; then + gw=$(printf '%d.%d.%d.%d' "0x${gw_hex:6:2}" "0x${gw_hex:4:2}" "0x${gw_hex:2:2}" "0x${gw_hex:0:2}") + fi + echo "Default gateway (the host, seen from this job network): ${gw:-unknown}" + + probe() { + local label=$1 h=$2 p=$3 start ms rc err verdict + start=$(date +%s%N) + err=$(timeout 10 bash -c "exec 3<>/dev/tcp/$h/$p" 2>&1) && rc=0 || rc=$? + ms=$(( ($(date +%s%N) - start) / 1000000 )) + case $rc in + 0) verdict="OPEN" ;; + 124) verdict="TIMEOUT" ;; + *) case "$err" in + *refused*) verdict="REFUSED" ;; + *) verdict="ERROR (${err##*: })" ;; + esac ;; + esac + printf ' %-36s %-26s %-8s %6d ms\n' "$label" "$h:$p" "$verdict" "$ms" + } + + echo + echo "TCP probes, 10 s each. OPEN = handshake done; REFUSED = the host answered" + echo "but nothing listens there; TIMEOUT = no answer (packets dropped, or the" + echo "address is unreachable from this network)." + probe "advertised cache address" "$host" "$port" + probe "same host, port 22 (sshd)" "$host" 22 + probe "same host, port 443 (Traefik)" "$host" 443 + if [ -n "$gw" ]; then + probe "job network gateway, cache port" "$gw" "$port" + probe "job network gateway, port 22" "$gw" 22 + fi + probe "docker0 172.17.0.1, cache port" 172.17.0.1 "$port" + + - name: Install Node.js 20 + run: | + apt-get update + apt-get install -y --no-install-recommends curl ca-certificates + # actions/cache is a JavaScript action: it needs `node` in the container PATH. + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - + apt-get install -y nodejs + node --version + + - name: Write a tiny payload + id: payload + shell: bash + run: | + key="diag-234-$(date -u +%Y%m%dT%H%M%SZ)-$RANDOM" + mkdir -p probe-payload + printf '%s' "$key" > probe-payload/stamp + echo "key=$key" >> "${GITHUB_OUTPUT:-$FORGEJO_OUTPUT}" + echo "payload key: $key" + + - name: Save it (actions/cache/save) + uses: https://github.com/actions/cache/save@v4 + with: + path: probe-payload + key: ${{ steps.payload.outputs.key }} + + - name: Delete the local copy + run: rm -rf probe-payload + + - name: Restore it (actions/cache/restore, fails on a miss) + uses: https://github.com/actions/cache/restore@v4 + with: + path: probe-payload + key: ${{ steps.payload.outputs.key }} + fail-on-cache-miss: true + + - name: Check the payload made the round trip + shell: bash + run: | + if [ "$(cat probe-payload/stamp 2>/dev/null)" = "${{ steps.payload.outputs.key }}" ]; then + echo "ROUND TRIP OK: the payload came back from the cache server." + else + echo "ROUND TRIP FAILED: the payload is missing or different." + exit 1 + fi