From 76f09215255c49cda9f306c99ff3d54d9162032c Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Wed, 22 Jul 2026 19:34:05 -0700 Subject: [PATCH] docs: document project registration and publish operation Fix stale README claims (fixture wording, mise tool list, live MEIGMA_PACKAGES_* prefix) and add a Publish a release page covering the projects.yml contract, upstream release requirements, the published layout convention, and manual/dispatch publish invocation. Co-Authored-By: Claude Fable 5 --- README.md | 17 +++-- docs/docs/index.md | 6 +- docs/docs/install.md | 9 ++- docs/docs/operations.md | 7 +- docs/docs/publishing.md | 147 ++++++++++++++++++++++++++++++++++++++++ docs/mkdocs.yml | 1 + 6 files changed, 176 insertions(+), 11 deletions(-) create mode 100644 docs/docs/publishing.md diff --git a/README.md b/README.md index b701fef..3c64c2d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # meigma/packages `meigma/packages` builds and publishes the signed APT and RPM repositories used -by Meigma projects. It builds verified candidates from fixture release sets, +by Meigma projects. It builds verified candidates from GitHub Release assets, applies deterministic retention, proves rebuild/no-op behavior, and executes deletion-safe sync plans. The protected publication path signs with the CI signing subkey, publishes a verified GitHub Release to either the isolated R2 @@ -26,7 +26,8 @@ Install [mise](https://mise.jdx.dev), then provision the pinned toolchain: mise install ``` -`mise.toml` selects Go, Moon, Python, uv, and golangci-lint. `mise.lock` +`mise.toml` selects Go, Moon, Python, uv, golangci-lint, actionlint, and +ShellCheck. `mise.lock` records their per-platform URLs and checksums, and locked mode fails closed when a selected platform has no resolved artifact. @@ -56,8 +57,11 @@ go run ./cmd/meigma-packages --version ``` The entrypoint under `cmd/meigma-packages` remains thin. Cobra/Viper command -construction lives under `internal/cli`, with `MEIGMA_PACKAGES_*` reserved as -the environment-variable prefix for future configuration. +construction lives under `internal/cli`. `MEIGMA_PACKAGES_*` is the live +environment-variable prefix: `MEIGMA_PACKAGES_GITHUB_TOKEN` authenticates +`fetch-release`, and `MEIGMA_PACKAGES_R2_ACCESS_KEY_ID`, +`MEIGMA_PACKAGES_R2_SECRET_ACCESS_KEY`, and +`MEIGMA_PACKAGES_R2_SESSION_TOKEN` credential `apply-sync`. ## Publication @@ -79,8 +83,9 @@ select `apply_staging` and `apply_production` explicitly; production only runs after staging succeeds. Immutable package and content-addressed metadata objects receive a one-year immutable cache policy; activation metadata, state, keys, and repo configuration remain `no-store`. Production deletion is not an -operator mode. See the [operations boundary](docs/docs/operations.md) before -dispatching it. +operator mode. See [Publish a release](docs/docs/publishing.md) for the +registry contract and workflow inputs, and the +[operations boundary](docs/docs/operations.md) before dispatching it. ## Documentation diff --git a/docs/docs/index.md b/docs/docs/index.md index aac7996..16c6c9d 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -36,5 +36,7 @@ run `scripts/publish.sh` to rebuild, sign, apply, verify, and clean-install the release. Manual runs select `apply_staging` and `apply_production` explicitly; a trusted consumer dispatch carrying `project` and `tag` always publishes through staging to production. See [Install packages](install.md) -for the public repository commands and [Operations boundary](operations.md) -for the enforced scope and recovery behavior. +for the public repository commands, [Publish a release](publishing.md) for +the registry contract and how to run a publish, and +[Operations boundary](operations.md) for the enforced scope and recovery +behavior. diff --git a/docs/docs/install.md b/docs/docs/install.md index ff5c2e1..b9f3f3e 100644 --- a/docs/docs/install.md +++ b/docs/docs/install.md @@ -5,9 +5,16 @@ description: Add the signed Meigma APT or RPM repository and install incus-gh-ru # Install Meigma packages -Use the Meigma package repository to install `incus-gh-runner` and receive +Use the Meigma package repository to install Meigma software and receive updates through your operating system's package manager. +The examples below install `incus-gh-runner`. For any other published +project, substitute its name in the same places: each project is its own APT +component (the `Components:` line) and its own RPM path +(`rpm//meigma.repo`), and its packages install by the project's +package name. The full list of published projects, versions, and packages is +at [`https://pkgs.meigma.dev/_state/manifest.json`](https://pkgs.meigma.dev/_state/manifest.json). + ## Verify the signing key Download the repository key and verify its full primary fingerprint before diff --git a/docs/docs/operations.md b/docs/docs/operations.md index f5dddc3..5b94681 100644 --- a/docs/docs/operations.md +++ b/docs/docs/operations.md @@ -69,5 +69,8 @@ subkey, never the primary signing key. The following remains deferred beyond the generalized production slice: -- automated credential renewal or an OIDC broker; -- onboarding additional projects through the existing registry contract. +- automated credential renewal or an OIDC broker. + +Onboarding additional projects is routine operation; see +[Publish a release](publishing.md) for the registry contract and workflow +inputs. diff --git a/docs/docs/publishing.md b/docs/docs/publishing.md new file mode 100644 index 0000000..794777a --- /dev/null +++ b/docs/docs/publishing.md @@ -0,0 +1,147 @@ +--- +title: Publish a release +description: Register a project in projects.yml and run the Publish workflow. +--- + +# Publish a release + +Publication has two halves: a one-time registration of the project in +[`projects.yml`](https://github.com/meigma/packages/blob/main/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. + +## Register a project + +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: + +```yaml +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.yml +``` + +Field 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 the `rpm//` path (see + [layout](#published-layout) below). +- **`repository`** (required) — the `meigma/` GitHub repository whose + Releases are the authoritative package source. Only `meigma/`-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 to + `apt install` / `dnf install`. +- **`retention`** (optional) — how many stable versions the published + repository keeps. Falls back to `defaults.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 leading `v` removed, so tag + `v1.1.0` selects `incus-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 inside + `repository` (`/.github/workflows/.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.Z` tag (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_name` and whose version is the tag + without its leading `v` — 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. + +## Published layout + +The project key determines where packages land under +`https://pkgs.meigma.dev`: + +- APT: suite `stable`, component `` — consumers put the project key + in the `Components:` line of their sources entry. +- RPM: repository definition at `rpm//meigma.repo`, with per-arch + package trees below it. +- `/_state/manifest.json` lists every published project, selected version, + and package, and is the quickest way to see what is currently live. + +## Run a publish + +### Automatic (trusted dispatch) + +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: + +```sh +gh api \ + --method POST \ + repos/meigma/packages/dispatches \ + --raw-field event_type=publish-package \ + --raw-field 'client_payload[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. + +### Manual + +Run the `Publish` workflow from the Actions tab or the CLI: + +```sh +# Validation only (no secrets, nothing published) +gh workflow run publish.yml -f project= -f tag=vX.Y.Z + +# Staging-only rehearsal +gh workflow run publish.yml -f project= -f tag=vX.Y.Z \ + -f apply_staging=true + +# Full publish through staging to production +gh workflow run publish.yml -f project= -f tag=vX.Y.Z \ + -f apply_staging=true -f apply_production=true +``` + +`apply_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](operations.md) for what the protected jobs can and +cannot do. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 9bedfe2..0f4cc7d 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -35,6 +35,7 @@ theme: nav: - Home: index.md - Install packages: install.md + - Publish a release: publishing.md - Operations boundary: operations.md plugins: