From 82936168a5452f5ae79dc166ba6af9375334eb68 Mon Sep 17 00:00:00 2001 From: 0xMarcio Date: Thu, 3 Sep 2026 23:02:44 +0200 Subject: [PATCH] Keep the hourly refresh on GitHub with a pacemaker chain GitHub's cron scheduler creates this repository's scheduled runs four to five hours late and drops the slots in between, so the hourly Hot CVEs refresh cannot come from a cron. A workflow_dispatch call creates a run within seconds, so a pacemaker workflow now sleeps until the next :05, fires the refresh and its own successor, and keeps time by itself. Its cron only restarts the chain if a link fails. Hot CVEs loses its cron and the gate that was there to coexist with an external trigger; nothing outside GitHub is involved any more. --- .github/workflows/hot_cves.yml | 46 ++------------------------------- .github/workflows/pacemaker.yml | 37 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 44 deletions(-) create mode 100644 .github/workflows/pacemaker.yml diff --git a/.github/workflows/hot_cves.yml b/.github/workflows/hot_cves.yml index a4eac869407..00e28b51350 100644 --- a/.github/workflows/hot_cves.yml +++ b/.github/workflows/hot_cves.yml @@ -1,15 +1,9 @@ name: Hot CVEs List -# The hourly trigger is a systemd timer on the VPS calling workflow_dispatch: -# GitHub's own scheduler creates cron runs four to five hours late and drops -# the slots in between. The cron below is the fallback and the watchdog. It -# yields while the trigger is alive, takes over when it is not, and fails -# loudly once the trigger has been dead for three hours so the failure email -# says why. +# Fired every hour at :05 by pacemaker.yml. No cron here: GitHub's scheduler +# creates cron runs hours late, which is the whole reason the pacemaker exists. on: workflow_dispatch: - schedule: - - cron: '7 * * * *' permissions: contents: write @@ -20,37 +14,7 @@ concurrency: cancel-in-progress: false jobs: - gate: - runs-on: ubuntu-latest - outputs: - run: ${{ steps.check.outputs.run }} - stale: ${{ steps.check.outputs.stale }} - steps: - - name: Yield to the hourly trigger while it is alive - id: check - env: - GH_TOKEN: ${{ github.token }} - run: | - if [ "$GITHUB_EVENT_NAME" != "schedule" ]; then - echo "run=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - last=$(gh api "repos/$GITHUB_REPOSITORY/commits?per_page=30" \ - --jq 'map(select(.commit.message | startswith("Trending CVEs update")))[0].commit.committer.date') - age=$(( $(date -u +%s) - $(date -u -d "${last:-1970-01-01T00:00:00Z}" +%s) )) - echo "Last trending refresh: ${last:-none}, $((age / 60)) minutes ago" - if [ "$age" -lt 3300 ]; then - echo "Fresh enough, the hourly trigger is doing its job" - exit 0 - fi - echo "run=true" >> "$GITHUB_OUTPUT" - if [ "$age" -gt 10800 ]; then - echo "stale=true" >> "$GITHUB_OUTPUT" - fi - ScheduledRun: - needs: gate - if: needs.gate.outputs.run == 'true' runs-on: ubuntu-latest steps: - name: Checkout repository @@ -124,9 +88,3 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: gh workflow run site.yml --ref main - - - name: Fail loudly when the hourly trigger is dead - if: needs.gate.outputs.stale == 'true' - run: | - echo "::error::No trending refresh for over three hours: the external hourly trigger is not firing" - exit 1 diff --git a/.github/workflows/pacemaker.yml b/.github/workflows/pacemaker.yml new file mode 100644 index 00000000000..30b67962fbe --- /dev/null +++ b/.github/workflows/pacemaker.yml @@ -0,0 +1,37 @@ +name: Pacemaker + +# GitHub's cron scheduler creates this repository's scheduled runs four to +# five hours late and drops the slots in between, so the hourly refresh cannot +# come from a cron. A workflow_dispatch call creates a run within seconds, so +# each pacemaker run sleeps until the next :05, fires the Hot CVEs refresh and +# its own successor, and the chain keeps time by itself. The cron below only +# restarts the chain if a link ever fails; while the chain is alive the +# concurrency group holds the extra run back and the next link cancels it. +on: + workflow_dispatch: + schedule: + - cron: '7 * * * *' + +permissions: + actions: write + +concurrency: + group: pacemaker + cancel-in-progress: false + +jobs: + tick: + runs-on: ubuntu-latest + timeout-minutes: 70 + steps: + - name: Sleep until the next :05, then fire the hourly refresh + env: + GH_TOKEN: ${{ github.token }} + run: | + now=$(date -u +%s) + next=$(( now / 3600 * 3600 + 300 )) + [ "$next" -gt "$now" ] || next=$(( next + 3600 )) + echo "Sleeping $(( (next - now) / 60 )) minutes until $(date -u -d @"$next" +%H:%M) UTC" + sleep $(( next - now )) + gh workflow run hot_cves.yml --ref main + gh workflow run pacemaker.yml --ref main