| title | Publish a release |
|---|---|
| description | Register a project in projects.yml and run the Publish workflow. |
Publication has two halves: a one-time registration of the project in
projects.yml,
and a per-release run of the Publish workflow. Registered projects publish
automatically when their release workflow dispatches to this repository;
manual runs use the same path.
Add an entry under projects: in projects.yml. The registry is strict YAML
(unknown fields are rejected) with schema: 1 at the top level. The existing
entry is a complete example:
schema: 1
defaults:
retention: 5
projects:
incus-gh-runner:
repository: meigma/incus-gh-runner
package_name: incus-gh-runner
assets:
checksums: checksums.txt
deb: 'incus-gh-runner_${version}_*.deb'
rpm: 'incus-gh-runner-${version}-1.*.rpm'
architectures:
amd64:
deb: amd64
rpm: x86_64
arm64:
deb: arm64
rpm: aarch64
provenance:
signer_workflow: meigma/incus-gh-runner/.github/workflows/attest.ymlField by field:
- Project key — lowercase letters, digits, and single hyphens
(
^[a-z0-9]+(-[a-z0-9]+)*$). This key becomes the project's published location: the APT component and therpm/<project>/path (see layout below). repository(required) — themeigma/<name>GitHub repository whose Releases are the authoritative package source. Onlymeigma/-owned repositories are accepted.package_name(required) — the package identity that every DEB and RPM in the release must report in its metadata, and the name users pass toapt install/dnf install.retention(optional) — how many stable versions the published repository keeps. Falls back todefaults.retention, then to 5.assets.checksums(required) — the exact checksum asset name in the GitHub Release.assets.deb/assets.rpm(required) — bare file-name glob patterns (no paths) that select the release's package assets.${version}expands to the tag with its leadingvremoved, so tagv1.1.0selectsincus-gh-runner_1.1.0_*.deb.architectures(at least one) — maps each repository architecture key to the architecture strings recorded inside the DEB and RPM packages (amd64/x86_64,arm64/aarch64). Every architecture needs both mappings, and no two architectures may share one. The release must contain exactly one DEB and one RPM per registered architecture.provenance.signer_workflow(required) — the exact workflow insiderepository(<repository>/.github/workflows/<file>.yml) trusted to have produced the release's artifact attestations. Publication verifies every package asset's attestation against this workflow.
What the source repository must therefore ship in each release:
- an exact stable
vX.Y.Ztag (no prereleases, build metadata, or leading-zero components); - the checksum asset plus one DEB and one RPM per registered architecture, matching the asset patterns;
- GitHub artifact attestations from the pinned signer workflow;
- package metadata whose name is
package_nameand whose version is the tag without its leadingv— clean installs assert this exact version.
Asset digests are verified against GitHub's recorded SHA-256 values after download; any mismatch, missing architecture, or failed attestation fails the run before anything is published.
The project key determines where packages land under
https://pkgs.meigma.dev:
- APT: suite
stable, component<project>— consumers put the project key in theComponents:line of their sources entry. - RPM: repository definition at
rpm/<project>/meigma.repo, with per-arch package trees below it. /_state/manifest.jsonlists every published project, selected version, and package, and is the quickest way to see what is currently live.
A registered project's release workflow dispatches to this repository after
its GitHub Release is published. It mints a short-lived token from the private
Meigma GitHub App (installed only on meigma/packages with
Contents: write) via actions/create-github-app-token, then sends:
gh api \
--method POST \
repos/meigma/packages/dispatches \
--raw-field event_type=publish-package \
--raw-field 'client_payload[project]=<project>' \
--raw-field "client_payload[tag]=$PACKAGE_TAG"The payload carries only project and tag. A trusted dispatch always runs
validation, then protected staging, then protected production; it cannot skip
staging or select any other behavior. See meigma/incus-gh-runner's
.github/workflows/packages.yml for the complete working example, including
the stable-tag guard and app-token step.
Run the Publish workflow from the Actions tab or the CLI:
# Validation only (no secrets, nothing published)
gh workflow run publish.yml -f project=<project> -f tag=vX.Y.Z
# Staging-only rehearsal
gh workflow run publish.yml -f project=<project> -f tag=vX.Y.Z \
-f apply_staging=true
# Full publish through staging to production
gh workflow run publish.yml -f project=<project> -f tag=vX.Y.Z \
-f apply_staging=true -f apply_production=trueapply_production only takes effect after staging succeeds in the same run;
there is no staging bypass. Every publish is convergent: re-running the same
release is a verified no-op, and recovering staging from a bad or empty state
is just another staging publish. See the
operations boundary for what the protected jobs can and
cannot do.