Skip to content

Fix the self-hosted APT repo publish workflow - #15164

Open
rtibbles wants to merge 4 commits into
developfrom
apt-repo-publish-fixes
Open

Fix the self-hosted APT repo publish workflow#15164
rtibbles wants to merge 4 commits into
developfrom
apt-repo-publish-fixes

Conversation

@rtibbles

@rtibbles rtibbles commented Aug 8, 2026

Copy link
Copy Markdown
Member

Summary

Fixes a range of issues with the apt repo publishing workflow:

  • Corrects the source of the signing key to be the Debian specific one
  • Updates the apt location to be inside downloads/kolibri/apt rather than at the root of the bucket
  • Sets appropriate cache ages depending on whether fixed assets (long) or metadata (no cache)
  • Removes previous test only implementation and replaces with a gcloud mock instead

References

Fixes #15149
Follow-up to: #15054
Ops prerequisites: #13720

Reviewer guidance

Manually tested by dispatching this branch against the live bucket, twice:

  • Publishing kolibri into the empty prefix: https://github.com/learningequality/kolibri/actions/runs/31230971790. A fresh ubuntu:24.04 then bootstrapped from the repo root deb, verified InRelease against the shipped keyring, and installed kolibri 0.19.5-0ubuntu1 (kolibri, version 0.19.5).
  • Publishing kolibri-server into the now-populated prefix, exercising the read-modify-write: https://github.com/learningequality/kolibri/actions/runs/31231573658. Same container flow installed kolibri-server 0.5.1-0ubuntu1, and the suite still lists all three packages — checked in binary-armhf as well as binary-amd64.
  • That second dispatch first failed on GCS permissions, which is what the README's new ops note records: the uploader SA holds objectCreator, so the pool creates succeeded and every dists overwrite was denied. The published index stayed valid and signed throughout, and once the grant was added the retry healed the orphan pool object with no manual cleanup — the pool-first ordering behaving as intended.

Left for a reviewer to weigh:

  • platforms/apt-repo/publish.sh:63include_deb keeps the published build when that version already exists, so a rebuilt artifact would leave the repo serving bytes that differ from the release asset; check that skipping is preferable to failing the job.

AI usage

Used Claude Code to diagnose the failing publish job, write the fixes and build the gcloud test double. Verified with the apt-repo shell test suite in a Debian container, prek, and gcloud's own argument parsing for the flag changes.

KOLIBRI_PUBLIC_RELEASE_GCS_BUCKET holds a bare bucket name, so the
"<bucket>/apt" the workflow passed never matched publish.sh's gs:// branch.
Every publish rsynced into a directory of that name on the runner and exited
0 — a green job that shipped nothing. publish.sh now derives
gs://<bucket>/downloads/kolibri/apt itself.

The local-directory branch it fell through to only ever ran under test, so
the tests proved nothing about the path CI takes. It is gone, and the tests
now stub `gcloud` at the CLI boundary and drive the real one. The stub
rejects flags gcloud does not have, which is what the up-leg was passing:
`-d` does not exist and `-c` is --continue-on-error, not a checksum compare,
so it would have aborted before writing anything.

Publishes are also idempotent now — a version already in the repo is kept
rather than re-included, since reprepro hard-errors on same-version bytes
that differ and CI rebuilds the keyring .deb every run. Cache-Control is set
per prefix so a cached index is never served against a newer pool.

build-essential rounds it off: it is not on the runner image, and
dpkg-checkbuilddeps demands it even for the arch-all keyring package.
The imported GPG_SIGNING_KEY is Learning Equality's general signing key. The
repo has to be signed by the Debian repository key instead: that is what
kolibri-archive-keyring ships, what the Pi trusts, and what signs the Pages
repos this one replaces. Signed by anything else, every client pinning the
shipped keyring with Signed-By rejects the repo, and nothing in the pipeline
notices — reprepro signs happily with whatever key it is handed.

Take DEBIAN_REPO_SIGNING_KEY, the secret the kolibri-installer-debian Pages
publish already uses, with that workflow's keyring layout: an isolated
GNUPGHOME, loopback pinentry and an ownertrust import. It carries no
passphrase, so the preset-passphrase dance goes with it.

The key id comes from the committed public key rather than a secret or a
variable, so the id cannot drift from the key we ship, and the job aborts up
front if the secret does not hold it.
Both Pages repos being replaced advertise amd64, i386, arm64 and armhf, and
those clients are real: the published wheel bundles C extensions for armv6l,
armv7l and i686, and the .deb is Architecture: all. reprepro only lists an
arch-all package under the architectures a suite declares, so migrating a
32-bit host to a suite declaring amd64 and arm64 leaves it with a repo it can
read but no package it can install.
@github-actions github-actions Bot added DEV: dev-ops Continuous integration & deployment SIZE: medium labels Aug 8, 2026
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

roles/storage.objectCreator, which the release uploads run on, cannot
overwrite: a GCS overwrite is a delete plus a create. Publishing into an empty
prefix therefore succeeds, and every publish after it fails on the indices with
the pool already ahead of the metadata.

Convert the README to Markdown while here, matching the rest of platforms/, and
drop two stale claims: the signing key needs no passphrase preset, and the host
it says ops has yet to provision is serving.
@rtibbles
rtibbles marked this pull request as ready for review August 8, 2026 01:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DEV: dev-ops Continuous integration & deployment SIZE: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pi image build fails: apt.learningequality.org was never published because the APT publish workflow lacks build-essential

2 participants