Skip to main content

Promotion Pipeline

Packages reach the serving stack through GitHub Actions workflows in this repository. There are two entry points: an automated one that ingests an upstream GitHub Release, and a manual one that promotes files staged by hand.

WorkflowTriggerWhat it does
promote-release.ymlworkflow_dispatch, normally called by the upstream release jobDownloads an upstream GitHub Release, verifies it, signs and publishes RPMs and DEBs, copies and signs images. One run per release
promote-rpm.ymlworkflow_dispatchDownloads RPMs from RustFS staging, GPG-signs them, publishes into the rpm container and rebuilds repodata
promote-deb.ymlworkflow_dispatchDownloads DEBs from RustFS staging, GPG-signs them, creates an Aptly snapshot and publishes it
promote-oci.ymlworkflow_dispatchDownloads per-architecture OCI tarballs from RustFS staging, pushes a multi-arch index to Zot and signs it keylessly

Automated: promote an upstream release

The upstream project publishes a GitHub Release on a version tag with its packages, a SHA256SUMS file signed with cosign, and images in its own registry. Its release job then triggers promote-release.yml here with one API call. Packyard pulls everything from public sources, so the upstream repository holds no SSH key and no storage credential.

upstream repo, tag v38.1.0 no42-org/packyard, promote-release.yml
────────────────────────────── ───────────────────────────────────────────────
GitHub Release 1. validate inputs, preflight the host over SSH
bbo-core-38.1.0-761.x86_64.rpm ──────────► 2. gh release download, cosign verify-blob SHA256SUMS,
bbo-core_38.1.0-761_amd64.deb sha256sum --check
SHA256SUMS, SHA256SUMS.sig/.pem 3. rpmsign with the Packyard GPG key
quay.io/bluebird/core:38.1.0 ──────────► 4. rpm container: add-package.sh into every rpm_target
(cosign-signed) 5. aptly container: one snapshot, published per deb_distro
6. cosign verify upstream, crane copy into Zot as
<component>/<image>:<version> and :<series>,
cosign sign with this workflow's identity
7. curl every published path through the public hostname

Inputs:

InputMeaningExample
source_repoUpstream repositoryBluebird-Community/opennms
tagRelease tag to promotev38.1.0
componentPackyard component, must existbluebird
seriesSeries label under the component38
rpm_targetsComma-separated os-arch targets, each provisioned on the componentel9-x86_64,el10-x86_64
deb_distrosComma-separated distributionsbookworm,trixie,jammy,noble
imagesComma-separated image names, empty to skipcore,minion,sentinel
source_registryWhere upstream images livequay.io/bluebird
source_workflowUpstream workflow that signed the releasemain.yml

The version is the tag without its v prefix. Re-running the workflow for the same tag is safe: files are overwritten with identical content, Aptly skips packages it already holds, and image digests do not change.

Wiring the upstream side is described in Upstream release dispatch.

Manual: stage and promote by hand

For one-off promotions that do not come from a GitHub Release, stage files into RustFS and dispatch the format-specific workflow. The component argument must match a name provisioned via POST /api/v1/components. series is any path-safe label, for example an LTS year (2025) or a major version (38).

RUSTFS_ACCESS_KEY=... RUSTFS_SECRET_KEY=... \
bash scripts/stage-artifact.sh core 2025 rpm el9-x86_64 /path/to/artifact.rpm

Then trigger the corresponding promotion workflow with component, series, and os inputs via the GitHub Actions UI or CLI:

gh workflow run promote-rpm.yml \
-f component=core \
-f series=2025 \
-f os=el9-x86_64

The step-by-step procedure is in the release runbook.

What every promotion has in common

  • Packyard signs, upstream signatures are only checked. RPMs carry the Packyard GPG key; DEB repositories carry it on their InRelease, which is what apt verifies (individual .deb files are not signed, apt never checks those). Images are signed keylessly by the promoting workflow's GitHub OIDC identity. Subscribers verify one identity for everything on the host.
  • The GPG key must be the served key. Before signing, the workflows compare the GPG_KEY_ID fingerprint with the key served at https://<host>/gpg/lts.asc. The copy of lts.asc in this repository is a placeholder.
  • Publishing happens inside the service containers, through one set of scripts. scripts/publish/rpm.sh, deb.sh and oci.sh are the only code that publishes. The workflows copy them to the host and stream packages into them over SSH; make test-publish-scripts runs the same scripts against a hardened local stack. The rpm image ships createrepo_c, Aptly runs in its own container, and the host needs Docker and the deploy user, nothing else. See Production deployment §5.
  • One copy per package. A package published into several RPM targets is hardlinked between the target directories. Aptly stores each .deb once in its pool no matter how many distributions publish it.
  • Concurrency groups serialise per component and target. Dispatching several promotions at once is safe.
  • CI runs the subscriber paths on every stack change. The integration workflow publishes fixtures through the same scripts and runs the RPM, DEB and OCI subscriber tests, including an assertion that anonymous writes to a public OCI component are refused.