Freshness, mirror lists #2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Freshness, mirror lists | |
| # Mirrors go away. A list that was healthy when it was generated decays, and the | |
| # riscv64 outage started with a list holding one server. | |
| # | |
| # ⚠ This does not run daily, and must not. Upstream mirror ranking tools decline | |
| # to ship a timer for this because of the load it puts on mirrors, and that | |
| # reasoning applies here. Monthly, opening a pull request. | |
| # | |
| # The candidate pools in mirrors/*.pool are hand maintained and nothing else | |
| # watches it, so a pool entry that stops answering is reported here too. | |
| on: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: "45 03 1 * *" # the 1st of each month, 03:45 UTC | |
| defaults: | |
| run: | |
| shell: bash | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: freshness-mirrors | |
| cancel-in-progress: false | |
| jobs: | |
| regenerate: | |
| name: Regenerate and test the mirror lists | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Measure the lists as they stand | |
| id: before | |
| run: | | |
| set -euo pipefail | |
| # The probe is the same one the static suite uses, so before and after | |
| # are measured with one instrument. | |
| total=0 | |
| for a in amd64 arm64 armv7 loong64 riscv64 ppc ppc64 ppc64le; do | |
| n="$(awk '/^Server/ { c++ } END { print c + 0 }' "rootfs/$a/etc/pacman.d/mirrorlist")" | |
| echo "$a: $n servers" | |
| total=$((total + n)) | |
| done | |
| echo "servers_before=$total" >> "$GITHUB_OUTPUT" | |
| # A pool entry that no longer answers is invisible until a regeneration | |
| # drops it, so it is counted explicitly. Every hand maintained pool is | |
| # probed, and the architecture each belongs to is read from that | |
| # architecture's own pacman.conf, so a new port's pool needs no edit | |
| # here. | |
| dead=0 | |
| for pool in mirrors/*.pool; do | |
| [ -e "$pool" ] || continue | |
| d="${pool#mirrors/}" | |
| d="${d%.pool}" | |
| pacman_arch="$(awk -F= '/^[[:space:]]*Architecture[[:space:]]*=/ { gsub(/[[:space:]]/, "", $2); print $2; exit }' "rootfs/$d/etc/pacman.conf")" | |
| while IFS= read -r s; do | |
| [ -n "$s" ] || continue | |
| url="${s//\$repo/core}" | |
| url="${url//\$arch/$pacman_arch}" | |
| code="$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 10 --max-time 30 -L "$url/core.db")" | |
| echo " pool $d $code $s" | |
| if [ "$code" != "200" ]; then dead=$((dead + 1)); fi | |
| done < <(awk '/^[[:space:]]*Server[[:space:]]*=/ { sub(/^[^=]*=[[:space:]]*/, ""); print }' "$pool") | |
| done | |
| echo "pool_dead=$dead" >> "$GITHUB_OUTPUT" | |
| echo "::notice::the hand maintained pools have $dead entries that do not answer" | |
| - name: Regenerate | |
| id: gen | |
| run: | | |
| set -euo pipefail | |
| branch="freshness/mirrors-$(date -u +%Y%m%d)" | |
| git switch -c "$branch" | |
| echo "branch=$branch" >> "$GITHUB_OUTPUT" | |
| scripts/gen-mirrorlist all | |
| total=0 | |
| for a in amd64 arm64 armv7 loong64 riscv64 ppc ppc64 ppc64le; do | |
| n="$(awk '/^Server/ { c++ } END { print c + 0 }' "rootfs/$a/etc/pacman.d/mirrorlist")" | |
| echo "$a: $n servers" | |
| total=$((total + n)) | |
| done | |
| echo "servers_after=$total" >> "$GITHUB_OUTPUT" | |
| if git diff --quiet -- rootfs; then | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::the regenerated lists are identical, nothing to open" | |
| else | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| git diff --stat -- rootfs | |
| fi | |
| - name: Test the regenerated lists | |
| if: steps.gen.outputs.changed == 'true' | |
| run: | | |
| set -euo pipefail | |
| # The mirror test is the one that matters here, and the rest of the | |
| # static suite guards against a regeneration corrupting a config. | |
| tests/run.sh static | |
| - name: Open a pull request carrying the measurement | |
| if: steps.gen.outputs.changed == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| BRANCH: ${{ steps.gen.outputs.branch }} | |
| BEFORE: ${{ steps.before.outputs.servers_before }} | |
| AFTER: ${{ steps.gen.outputs.servers_after }} | |
| POOL_DEAD: ${{ steps.before.outputs.pool_dead }} | |
| run: | | |
| set -euo pipefail | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add rootfs | |
| git commit -m "mirrors: regenerate the mirror lists" | |
| git push --set-upstream origin "$BRANCH" | |
| # The printf formats are markdown. Backticks inside them are literal | |
| # text for the pull request body, not shell expansions. | |
| # shellcheck disable=SC2016 | |
| { | |
| printf '## What moved\n\n' | |
| printf 'Regenerated by `scripts/gen-mirrorlist all`. Every server written\n' | |
| printf 'answered 200 for `core.db` at generation time.\n\n' | |
| printf '| | before | after |\n' | |
| printf '| --- | --- | --- |\n' | |
| printf '| active servers, every list | %s | %s |\n\n' "$BEFORE" "$AFTER" | |
| printf 'Per architecture, after regeneration:\n\n' | |
| for a in amd64 arm64 armv7 loong64 riscv64 ppc ppc64 ppc64le; do | |
| printf -- '- `%s`: %s servers\n' "$a" "$(awk '/^Server/ { c++ } END { print c + 0 }' "rootfs/$a/etc/pacman.d/mirrorlist")" | |
| done | |
| printf '\nThe hand maintained pools under `mirrors/` had **%s** entries between\n' "$POOL_DEAD" | |
| printf 'them that did not answer when this ran. Nothing else watches those files.\n\n' | |
| printf '## What the tests did\n\n' | |
| printf '`tests/run.sh static`, the whole suite, including\n' | |
| printf '`40-mirrors-reachable.sh` which probes every written entry again from\n' | |
| printf 'the runner rather than trusting the generator.\n\n' | |
| printf '## ⚠ What this job did NOT verify\n\n' | |
| printf -- '- **No image was built.** A mirror can serve `core.db` and still fail\n' | |
| printf ' mid transfer on a package.\n' | |
| printf -- '- **Ranking is by connection time from one runner in one region.** The\n' | |
| printf ' order suits that runner, not every consumer.\n' | |
| printf -- '- **Anchors are not ranked and are never dropped.** If an anchor is down\n' | |
| printf ' the generator refuses to write rather than quietly removing it.\n' | |
| printf -- '- **The pools were probed, not repaired.** A dead entry stays in its\n' | |
| printf ' `mirrors/*.pool` file until someone edits it.\n' | |
| printf -- '- **https availability was tested per host, once.** A host that answers\n' | |
| printf ' https intermittently may be written as either.\n' | |
| printf -- '- **This pull request carries no status check.** A pull request opened\n' | |
| printf ' with the built-in token has its run held at `action_required` until a\n' | |
| printf ' human approves it. The tree was tested inside run `%s`, linked above.\n' "$GITHUB_RUN_ID" | |
| printf ' See `HISTORY/maintainer-actions.md` section 4.\n' | |
| } > /tmp/body.md | |
| gh pr create --base main --head "$BRANCH" \ | |
| --title "mirrors: regenerate the mirror lists" --body-file /tmp/body.md |