diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 9363e04..2137920 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -148,46 +148,39 @@ jobs: links: name: every external link resolves runs-on: ubuntu-latest + permissions: + contents: read + timeout-minutes: 10 steps: - uses: actions/checkout@v7 + # linkcheck (go-fleettools/fleettools, pinned): retries with exponential + # backoff that honours Retry-After, github.com repo and path links decided + # through the REST API rather than the HTML page, a DEAD link told apart + # from a FLAKY host. It already skips img.shields.io, example domains + # and localhost. + # + # --skip: this site's own pages exist only once this workflow has + # deployed them (the offline check above proves every link inside the + # site), and this repository's own files (figures/), on a pull request + # that adds them, are not on main yet. + # + # THE ONE DELIBERATE EXCEPTION to "no secret on a command line or in the + # environment": the job's ephemeral, read-only github.token (permissions: + # contents: read, for this job alone) reaches the tool as a 0600 file in + # RUNNER_TEMP, written by the shell builtin printf (no process argument, + # no environment variable) and removed on exit. It is sent to + # api.github.com only and never printed. - name: Check run: | - set -uo pipefail - # The character class matters: an extractor stopping only at ) and " - # carries a closing > or < into the request. - urls=$(grep -rhoE 'https?://[^)">< [:space:]]+' content/ README.md | sed -E 's/[.,`]+$//' | sort -u) - test -n "$urls" || { echo "::error::no links found; are the pages still there?"; exit 1; } - echo "checking $(printf '%s\n' "$urls" | wc -l) distinct URLs" - fail=0 - while read -r u; do - case "$u" in - # Renders whatever it is asked for; proves nothing. - *img.shields.io*) continue ;; - # Documented placeholders, not hosts. - *example.com*|*example.org*|*example.net*|*localhost*|*127.0.0.1*|*.invalid*) continue ;; - # This site's own pages: they exist only once this workflow - # has deployed them, and the offline check above already - # proves every link inside the site. - *go-plot.github.io/docs*) continue ;; - # This repository's own files (figures/): on a pull request - # that adds them, they are not on main yet. - *github.com/go-plot/docs/*) continue ;; - esac - # Three attempts: one is a flake detector rather than a link - # checker, and a lane that fails at random gets ignored. - code=000 - for _ in 1 2 3; do - code=$(curl -sS -o /dev/null -w '%{http_code}' -L --max-time 25 "$u" 2>/dev/null) || true - code=${code:-000} - case "$code" in 2*|3*) break ;; esac - sleep 3 - done - case "$code" in - 2*|3*) printf ' ok %s %s\n' "$code" "$u" ;; - *) printf ' FAIL %s %s\n' "$code" "$u"; fail=1 ;; - esac - done <<< "$urls" - test "$fail" -eq 0 || { echo "::error::at least one link does not resolve"; exit 1; } + umask 077 + token="${RUNNER_TEMP}/linkcheck-token" + trap 'rm -f "${token}"' EXIT + printf '%s' '${{ github.token }}' > "${token}" + go run github.com/go-fleettools/fleettools/cmd/linkcheck@v0.74.0 \ + --token-file "${token}" \ + --skip go-plot.github.io/docs \ + --skip github.com/go-plot/docs/ \ + content/ README.md deploy: # Only main publishes. A pull request stops after build above: it has no