From 46c6be65e26213ab5db9f8cb8500b151934d20ca Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Thu, 12 Nov 2026 04:19:46 +1300 Subject: [PATCH 1/2] ci: map the Azure apt mirror to archive.ubuntu.com when the runner can't resolve it Since 2026-11-11 ~14:40 UTC GitHub's Ubuntu runners cannot resolve azure.archive.ubuntu.com, so 'playwright install --with-deps' and the runner image's apt-get retry for minutes and fail every CI run (bugpatrol#174 failed three attempts the same way). The Ubuntu archive serves the same paths under that host name, so scripts/apt-mirror-fallback.sh maps the name to archive.ubuntu.com in /etc/hosts for the job, and docker build gets the same mapping through --add-host. It does nothing while the mirror resolves, and the runner Dockerfile is untouched, so the image digest and baselines don't change. Co-Authored-By: Claude Opus 5.5 Co-authored-by: Nebula AI Agent <277912164+nebula-ai-agent[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 ++ .github/workflows/determinism.yml | 7 ++++++- .github/workflows/release.yml | 2 ++ scripts/apt-mirror-fallback.sh | 27 +++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 scripts/apt-mirror-fallback.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2436329c..a6b9f6c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,8 @@ jobs: # The landing page in site/, which the Pages workflow deploys from main. - run: pnpm --filter @bugpatrol/site typecheck - run: pnpm --filter @bugpatrol/site build + # Runner DNS sometimes loses the Azure apt mirror; see the script. + - run: bash scripts/apt-mirror-fallback.sh # The capture tests drive a real browser, because font loading and layout # only mean anything against a real rendering engine. - run: pnpm --filter @bugpatrol/capture exec playwright install --with-deps chromium diff --git a/.github/workflows/determinism.yml b/.github/workflows/determinism.yml index 01a1d565..d827bb1f 100644 --- a/.github/workflows/determinism.yml +++ b/.github/workflows/determinism.yml @@ -34,6 +34,8 @@ jobs: - run: pnpm install --frozen-lockfile - run: pnpm build + # Runner DNS sometimes loses the Azure apt mirror; see the script. + - run: bash scripts/apt-mirror-fallback.sh - name: Install the pinned browser build run: pnpm --filter @bugpatrol/capture exec playwright install --with-deps chromium @@ -59,4 +61,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - run: docker build -t bugpatrol-runner:ci -f images/runner/Dockerfile . + - run: bash scripts/apt-mirror-fallback.sh + # The Playwright base image also names the Azure mirror; --add-host only + # applies when the script found it unresolvable. + - run: docker build ${APT_MIRROR_ADD_HOST:+--add-host "$APT_MIRROR_ADD_HOST"} -t bugpatrol-runner:ci -f images/runner/Dockerfile . diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ce5622f5..072d0a8e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,6 +39,8 @@ jobs: - run: npm install -g npm@^11.5.1 - run: pnpm install --frozen-lockfile - run: pnpm build + # Runner DNS sometimes loses the Azure apt mirror; see the script. + - run: bash scripts/apt-mirror-fallback.sh # Capture tests need Chromium's real font and layout engine. - run: pnpm --filter @bugpatrol/capture exec playwright install --with-deps chromium # The recording tests encode real video. diff --git a/scripts/apt-mirror-fallback.sh b/scripts/apt-mirror-fallback.sh new file mode 100644 index 00000000..991d7427 --- /dev/null +++ b/scripts/apt-mirror-fallback.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# GitHub's Ubuntu runners (and the Playwright image built on Azure) fetch apt +# packages from azure.archive.ubuntu.com. When the runner's resolver cannot find +# that name, every apt step retries for minutes and fails the job, though +# nothing in the commit is wrong. The Ubuntu archive serves the same paths under +# that host name, so point the name at it for this job only. +# +# It changes nothing while the mirror resolves. For `docker build`, which does +# not read the host's /etc/hosts, it exports APT_MIRROR_ADD_HOST for --add-host. +set -euo pipefail + +mirror=azure.archive.ubuntu.com +if getent hosts "$mirror" >/dev/null; then + exit 0 +fi + +ip=$(getent ahostsv4 archive.ubuntu.com | awk 'NR == 1 { print $1 }') +if [ -z "$ip" ]; then + echo "::warning::Neither $mirror nor archive.ubuntu.com resolves; apt steps will fail." + exit 0 +fi + +echo "::warning::$mirror does not resolve on this runner; using archive.ubuntu.com ($ip)." +echo "$ip $mirror" | sudo tee -a /etc/hosts >/dev/null +if [ -n "${GITHUB_ENV:-}" ]; then + echo "APT_MIRROR_ADD_HOST=$mirror:$ip" >>"$GITHUB_ENV" +fi From 06209ce040c6deb218ac80fdbd5dec770bed119e Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Thu, 12 Nov 2026 04:21:34 +1300 Subject: [PATCH 2/2] ci: resolve unresolvable apt hosts through DNS over HTTPS The first fallback mapped the Azure mirror to archive.ubuntu.com, but on the runners archive.ubuntu.com does not resolve either (#175's first run exited 2 in getent), so the ubuntu.com names are gone from the runner's resolver as a whole. Look each apt host up through Cloudflare's or Google's DNS over HTTPS instead and pin the answer, and pass every pin to docker build. Co-Authored-By: Claude Opus 5.5 Co-authored-by: Nebula AI Agent <277912164+nebula-ai-agent[bot]@users.noreply.github.com> --- .github/workflows/determinism.yml | 5 ++- scripts/apt-mirror-fallback.sh | 52 ++++++++++++++++++------------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/.github/workflows/determinism.yml b/.github/workflows/determinism.yml index d827bb1f..e9886495 100644 --- a/.github/workflows/determinism.yml +++ b/.github/workflows/determinism.yml @@ -62,6 +62,5 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - run: bash scripts/apt-mirror-fallback.sh - # The Playwright base image also names the Azure mirror; --add-host only - # applies when the script found it unresolvable. - - run: docker build ${APT_MIRROR_ADD_HOST:+--add-host "$APT_MIRROR_ADD_HOST"} -t bugpatrol-runner:ci -f images/runner/Dockerfile . + # Word splitting is intended: the variable is a list of --add-host flags. + - run: docker build $APT_MIRROR_ADD_HOSTS -t bugpatrol-runner:ci -f images/runner/Dockerfile . diff --git a/scripts/apt-mirror-fallback.sh b/scripts/apt-mirror-fallback.sh index 991d7427..56cff7f8 100644 --- a/scripts/apt-mirror-fallback.sh +++ b/scripts/apt-mirror-fallback.sh @@ -1,27 +1,37 @@ #!/usr/bin/env bash -# GitHub's Ubuntu runners (and the Playwright image built on Azure) fetch apt -# packages from azure.archive.ubuntu.com. When the runner's resolver cannot find -# that name, every apt step retries for minutes and fails the job, though -# nothing in the commit is wrong. The Ubuntu archive serves the same paths under -# that host name, so point the name at it for this job only. +# GitHub's Ubuntu runners fetch apt packages from azure.archive.ubuntu.com, and +# the Playwright image the runner image builds from names the same mirror. When +# the runner's resolver loses the ubuntu.com names, every apt step retries for +# minutes and fails the job though nothing in the commit is wrong. So any apt +# host the runner cannot resolve is looked up through public DNS over HTTPS and +# pinned in /etc/hosts for this job only. # -# It changes nothing while the mirror resolves. For `docker build`, which does -# not read the host's /etc/hosts, it exports APT_MIRROR_ADD_HOST for --add-host. -set -euo pipefail +# It changes nothing while the names resolve. `docker build` does not read the +# host's /etc/hosts, so the pins are also exported as APT_MIRROR_ADD_HOSTS, a +# list of --add-host flags. +set -uo pipefail -mirror=azure.archive.ubuntu.com -if getent hosts "$mirror" >/dev/null; then - exit 0 -fi - -ip=$(getent ahostsv4 archive.ubuntu.com | awk 'NR == 1 { print $1 }') -if [ -z "$ip" ]; then - echo "::warning::Neither $mirror nor archive.ubuntu.com resolves; apt steps will fail." - exit 0 -fi +flags=() +for host in azure.archive.ubuntu.com archive.ubuntu.com security.ubuntu.com; do + if getent hosts "$host" >/dev/null; then + continue + fi + ip="" + # DNS over HTTPS: the runner resolves these providers' own names fine. + for doh in https://cloudflare-dns.com/dns-query https://dns.google/resolve; do + ip=$(curl -fsS --max-time 10 -H "accept: application/dns-json" "$doh?name=$host&type=A" | + jq -r '[.Answer[]? | select(.type == 1) | .data][0] // empty') + [ -n "$ip" ] && break + done + if [ -z "$ip" ]; then + echo "::warning::$host does not resolve here or through public DNS; apt steps that need it will fail." + continue + fi + echo "::warning::$host does not resolve on this runner; pinned to $ip from public DNS." + echo "$ip $host" | sudo tee -a /etc/hosts >/dev/null + flags+=("--add-host=$host:$ip") +done -echo "::warning::$mirror does not resolve on this runner; using archive.ubuntu.com ($ip)." -echo "$ip $mirror" | sudo tee -a /etc/hosts >/dev/null if [ -n "${GITHUB_ENV:-}" ]; then - echo "APT_MIRROR_ADD_HOST=$mirror:$ip" >>"$GITHUB_ENV" + echo "APT_MIRROR_ADD_HOSTS=${flags[*]}" >>"$GITHUB_ENV" fi