Skip to content

Reconcile apt repo

Reconcile apt repo #75

name: Reconcile apt repo
# Rebuilds the SHARED apt index from the full pool and re-signs it — without
# building or releasing anything. Guarantees the index always matches the pool
# (self-heals a partial/failed release publish), and is the seed of the single
# index-builder for when a 2nd package ships (see packaging/README.md).
on:
schedule:
- cron: "17 6 * * *" # daily; pick a quiet hour
workflow_dispatch: {}
permissions:
contents: read
# Same group as release.yml so a reconcile never races a release on the shared index.
concurrency:
group: apt-publish
cancel-in-progress: false
jobs:
reconcile:
runs-on: ubuntu-latest
# Backstop, not the primary guard (apt-publish deadlines each download). A hung
# reconcile holds the shared apt-publish concurrency group, which would block
# releases and every later reconcile behind it.
timeout-minutes: 30
# Dedicated environment (not `release`): same S3/GPG creds, but a branch
# policy allowing `main` and no required reviewer, so the daily scheduled
# run can publish unattended. `release` stays locked to v* tags + review.
environment: reconcile
env:
APT_S3_ENDPOINT: ${{ vars.APT_S3_ENDPOINT }}
APT_S3_REGION: ${{ vars.APT_S3_REGION }}
APT_S3_BUCKET: ${{ vars.APT_S3_BUCKET }}
APT_S3_PREFIX: ${{ vars.APT_S3_PREFIX }}
AWS_ACCESS_KEY_ID: ${{ secrets.APT_S3_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.APT_S3_SECRET_KEY }}
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version: "1.24"
# apt-ftparchive ships in apt-utils, which the runner image already carries ("apt-utils
# is already the newest version" in every successful run), so the install is a fallback
# for an image that ever drops it and the apt-get update behind it does not run at all
# in the normal case. It was not a free no-op: on 2026-08-19 the Ubuntu mirror hung
# mid-fetch with no timeout of its own and the step sat silent for 29 minutes, until the
# job timeout killed a reconcile that had not yet reached the pool. A stall here is
# expensive out of proportion to the step, because the job holds the shared apt-publish
# concurrency group while it burns: releases and every later reconcile queue behind it.
# The step timeout bounds the fallback so a bad mirror fails fast and names itself.
- name: Ensure apt-ftparchive is available
timeout-minutes: 3
run: |
if ! command -v apt-ftparchive >/dev/null; then
sudo apt-get update
sudo apt-get install -y apt-utils
fi
- name: Import GPG signing key
id: gpg
uses: crazy-max/ghaction-import-gpg@v7
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
- name: Pull full pool from S3
run: |
mkdir -p aptrepo
go run ./cmd/apt-publish pull aptrepo
- name: Rebuild + sign index from the full pool
env:
APT_GPG_KEY_ID: ${{ steps.gpg.outputs.fingerprint }}
run: ./scripts/build-apt-repo.sh aptrepo
- name: Publish (Release files last)
run: go run ./cmd/apt-publish push aptrepo