Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -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

Expand All @@ -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

Expand Down
6 changes: 4 additions & 2 deletions docs/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
9 changes: 8 additions & 1 deletion docs/docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<project>/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
Expand Down
7 changes: 5 additions & 2 deletions docs/docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
147 changes: 147 additions & 0 deletions docs/docs/publishing.md
Original file line number Diff line number Diff line change
@@ -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/<project>/` path (see
[layout](#published-layout) below).
- **`repository`** (required) — the `meigma/<name>` 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` (`<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.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 `<project>` — consumers put the project key
in the `Components:` line of their sources entry.
- RPM: repository definition at `rpm/<project>/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]=<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=<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=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.
1 change: 1 addition & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ theme:
nav:
- Home: index.md
- Install packages: install.md
- Publish a release: publishing.md
- Operations boundary: operations.md

plugins:
Expand Down