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