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
168 changes: 168 additions & 0 deletions docs/how-to/install-release-cli-from-package-repositories.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# Install release-cli from native package repositories

Use this guide to install and update `release-cli` from the signed APT, DNF, or APK repository at `https://pkgs.meigma.dev`.

The repository publishes amd64 and arm64 packages. Each client verifies both repository metadata and the producer package before installation. Do not disable either check when recovering a failed install.

## Prerequisites

You need:

- root access on the target host;
- a working system clock and HTTPS certificate store;
- Debian or Ubuntu with APT, a DNF-based Linux distribution, or Alpine Linux.

## Install with APT

Install the HTTPS prerequisites from the distribution repositories:

```sh
sudo apt-get update
sudo apt-get install -y ca-certificates curl
```

Install the repository key in a dedicated keyring file:

```sh
sudo install -d -m 0755 /etc/apt/keyrings
curl --fail --silent --show-error --location \
https://pkgs.meigma.dev/keys/apt-repository-001.asc \
| sudo tee /etc/apt/keyrings/meigma-packages.asc >/dev/null
sudo chmod 0644 /etc/apt/keyrings/meigma-packages.asc
```

Add the signed repository and install the command:

```sh
printf '%s\n' \
'deb [signed-by=/etc/apt/keyrings/meigma-packages.asc] https://pkgs.meigma.dev/apt stable main' \
| sudo tee /etc/apt/sources.list.d/meigma-packages.list >/dev/null
sudo apt-get update
sudo apt-get install -y release-cli
```

Confirm the installed package:

```sh
dpkg-query --show --showformat='${Package} ${Version} ${Architecture}\n' release-cli
```

## Install with DNF

Create a repository definition that checks the aggregate repository signature and the producer RPM signature:

```sh
sudo tee /etc/yum.repos.d/meigma-packages.repo >/dev/null <<'EOF'
[meigma-packages]
name=Meigma packages
baseurl=https://pkgs.meigma.dev/rpm/stable/$basearch
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://pkgs.meigma.dev/keys/rpm-repository-001.asc https://pkgs.meigma.dev/keys/release-rpm-001.asc
EOF
```

Install and inspect the package:

```sh
sudo dnf install -y release-cli
rpm -q --qf '%{NAME} %{VERSION}-%{RELEASE} %{ARCH}\n' release-cli
```

Keep both `gpgcheck=1` and `repo_gpgcheck=1`. The first checks the producer-signed RPM; the second checks aggregate repository metadata.

## Install with APK

APK identifies signing keys by filename. Keep the published filenames unchanged when you install them:

```sh
sudo wget -q \
https://pkgs.meigma.dev/keys/apk-index-001.rsa.pub \
-O /etc/apk/keys/apk-index-001.rsa.pub
sudo wget -q \
https://pkgs.meigma.dev/keys/meigma-release-001.rsa.pub \
-O /etc/apk/keys/meigma-release-001.rsa.pub
```

Add the repository, refresh its signed index, and install the command:

```sh
printf '%s\n' 'https://pkgs.meigma.dev/apk/stable/main' \
| sudo tee -a /etc/apk/repositories >/dev/null
sudo apk update
sudo apk add release-cli
apk info --installed release-cli
```

The aggregate key verifies `APKINDEX.tar.gz`. The producer key verifies the package. Both keys are required.

## Update release-cli

Use the package manager's normal update path:

```sh
sudo apt-get update && sudo apt-get install --only-upgrade release-cli
```

```sh
sudo dnf upgrade release-cli
```

```sh
sudo apk update && sudo apk upgrade release-cli
```

The repository retains published versions. A normal install selects the newest version accepted by the client unless the host has a package pin or version lock.

## Recover a failed install

### TLS verification fails

Check the system clock and update the host's CA certificate package from its distribution repository. Do not replace `https://pkgs.meigma.dev` with HTTP and do not disable certificate verification.

### A repository or package signature is rejected

Stop before installing the package. Do not use APT `trusted=yes`, DNF `gpgcheck=0`, APK `--allow-untrusted`, or a similar bypass.

Confirm that the configured key URLs and filenames match the reviewed package policy. The current public names are:

- `apt-repository-001.asc` for APT metadata;
- `rpm-repository-001.asc` and `release-rpm-001.asc` for DNF metadata and packages;
- `apk-index-001.rsa.pub` and `meigma-release-001.rsa.pub` for APK indexes and packages.

A key replacement uses a new reviewed public-key object; immutable key objects are not overwritten. Replace keys manually:

1. Obtain the new key URL, expected fingerprint or digest, and activation notice through the trusted release channel.
2. Download the new key to a temporary file over HTTPS.
3. Compare its fingerprint or digest with the independently reviewed value.
4. Install the new key alongside the old key. For APK, preserve the announced basename exactly.
5. Update the APT `signed-by` path, DNF `gpgkey` list, or APK key set to include the new key.
6. Refresh repository metadata and complete one verified installation.
7. Remove the retired key only after the refreshed metadata and package both verify.

If no reviewed replacement exists, leave the old configuration in place and report the failure. A repeated signature error can indicate stale metadata, an incomplete key rollout, or tampering; bypassing verification hides the distinction.

### The requested version is unavailable

Refresh metadata, then inspect the versions visible to the client:

```sh
apt-cache policy release-cli
```

```sh
dnf --showduplicates list release-cli
```

```sh
apk policy release-cli
```

If the release is visible on GitHub but absent from the package repository, wait for the central publication workflow or contact its operator. Do not install an unverified package copied from a failed publication run.

### An update was interrupted

Run the metadata refresh and install command again. Publication is convergent: immutable package objects are never overwritten, and repository roots become visible only after their referenced objects are uploaded. A replay either completes the same state or fails without accepting conflicting immutable content.

For repository operation and replay procedures, see [Set up the shared package repository](set-up-package-repository.md). The [package repository contract](../reference/package-repository-contract.md) defines the public paths, trust checks, cache behavior, and publication states.
45 changes: 24 additions & 21 deletions docs/how-to/set-up-package-repository.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Set up the shared package repository

Use this guide to publish verified DEB, RPM, and APK packages from producer GitHub Releases to a static Cloudflare R2 repository. The [package repository contract](../reference/package-repository-contract.md) defines the accepted release, configuration, object layout, trust checks, and recovery behavior.
Use this guide to publish verified DEB, RPM, and APK packages from producer GitHub Releases to a static Cloudflare R2 repository. The [package repository contract](../reference/package-repository-contract.md) defines the accepted release, configuration, object layout, trust checks, and recovery behavior. After publication, give consumers the [native package installation guide](install-release-cli-from-package-repositories.md).

## Prerequisites

Expand Down Expand Up @@ -129,31 +129,34 @@ Keep this workflow on the central repository's default branch. GitHub runs `repo

## Dispatch from a producer

After the producer publishes its GitHub Release, mint a short-lived GitHub App token whose installation includes the central repository. Send only the producer repository and exact stable tag:
Add the reusable dispatch job after the producer's public GitHub Release job.
Keep it disabled during setup:

```yaml
- name: Create package repository token
id: package-app
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
package-repository:
name: Request package repository publication
needs: github-release
permissions: {}
uses: meigma/release/.github/workflows/request-package-repository.yml@583937edadfbae183e49f16df46b98e0b36807ba # v0.1.16
with:
client-id: ${{ vars.MEIGMA_RELEASE_APP_CLIENT_ID }}
private-key: ${{ secrets.MEIGMA_RELEASE_APP_PRIVATE_KEY }}
owner: meigma
repositories: pkgs

- name: Request package repository publication
env:
GH_TOKEN: ${{ steps.package-app.outputs.token }}
PRODUCER_REPOSITORY: ${{ github.repository }}
PRODUCER_TAG: ${{ github.ref_name }}
run: |
gh api --method POST repos/meigma/pkgs/dispatches \
-f event_type=package-release \
-f "client_payload[repository]=${PRODUCER_REPOSITORY}" \
-f "client_payload[tag]=${PRODUCER_TAG}"
package-repository-owner: meigma
package-repository-name: pkgs
release-app-client-id: ${{ vars.MEIGMA_RELEASE_APP_CLIENT_ID }}
publish-package-repository: false
secrets:
release-app-private-key: ${{ secrets.MEIGMA_RELEASE_APP_PRIVATE_KEY }}
```

The GitHub App needs permission to send the dispatch to `meigma/pkgs`. It does not need R2 or aggregate signing credentials.
The reusable workflow mints a short-lived token scoped to the central
repository and sends only the producer repository and exact tag. The GitHub App
installation needs repository Contents write permission on `meigma/pkgs` so it
can create `repository_dispatch` events. It does not receive R2 credentials or
aggregate signing keys.

After the central policy, keys, environment, and receiver workflow are ready,
change `publish-package-repository` to `true`. Keep the job ordered after the
public GitHub Release; the receiver rejects a missing, draft, or mismatched
release.

## Verify the first publication

Expand Down
13 changes: 13 additions & 0 deletions examples/go-release/.github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,16 @@ jobs:
publish-release: false
secrets:
release-app-private-key: ${{ secrets.MEIGMA_RELEASE_APP_PRIVATE_KEY }}

package-repository:
name: Request package repository publication
needs: github-release
permissions: {}
uses: meigma/release/.github/workflows/request-package-repository.yml@583937edadfbae183e49f16df46b98e0b36807ba # v0.1.16
with:
package-repository-owner: OWNER
package-repository-name: packages
release-app-client-id: ${{ vars.MEIGMA_RELEASE_APP_CLIENT_ID }}
publish-package-repository: false
secrets:
release-app-private-key: ${{ secrets.MEIGMA_RELEASE_APP_PRIVATE_KEY }}
26 changes: 17 additions & 9 deletions examples/go-release/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Go release example

This directory models a repository named `example` with module `example.com/meigma/release-consumer` and command `./cmd/example`. It contains the minimum source needed to build GitHub Release assets and a multi-architecture OCI image. It is not a complete CI policy: add the consumer repository's own build, test, review, and branch-protection controls.
This directory models a repository named `example` with module `example.com/meigma/release-consumer` and command `./cmd/example`. It contains the minimum source needed to build GitHub Release assets and a multi-architecture OCI image, plus a disabled package-repository dispatch job. It is not a complete CI policy: add the consumer repository's own build, test, review, and branch-protection controls.

See [Configure GitHub Releases](../../docs/how-to/configure-github-releases.md) for release credential setup and [Configure OCI image publication](../../docs/how-to/configure-oci-images.md) for image configuration, publication, and verification. See [Rehearse and recover GitHub Releases](../../docs/how-to/rehearse-and-recover-github-releases.md) before the first publication. Use [Upgrade GitHub Release workflows](../../docs/how-to/upgrade-github-release-workflows.md) to change the pinned revision. The reusable interfaces are defined in the [GitHub Release contract](../../docs/reference/github-release-contract.md) and [OCI image contract](../../docs/reference/oci-image-contract.md).
See [Configure GitHub Releases](../../docs/how-to/configure-github-releases.md) for release credential setup and [Configure OCI image publication](../../docs/how-to/configure-oci-images.md) for image configuration, publication, and verification. [Set up the shared package repository](../../docs/how-to/set-up-package-repository.md) defines the central receiver, producer policy, public keys, and signing prerequisites; [Install release-cli from native package repositories](../../docs/how-to/install-release-cli-from-package-repositories.md) shows the resulting client flow. See [Rehearse and recover GitHub Releases](../../docs/how-to/rehearse-and-recover-github-releases.md) before the first publication. Use [Upgrade GitHub Release workflows](../../docs/how-to/upgrade-github-release-workflows.md) to change the pinned revision. The reusable interfaces are defined in the [GitHub Release contract](../../docs/reference/github-release-contract.md), [OCI image contract](../../docs/reference/oci-image-contract.md), and [package repository contract](../../docs/reference/package-repository-contract.md).

## Files to copy

Expand Down Expand Up @@ -37,8 +37,9 @@ The stage command runs `goreleaser release --clean --skip=publish` under mise's

The GoReleaser configuration packages each Linux build as DEB, RPM, and APK
and emits one SBOM for every archive and native package. These standalone
packages are GitHub Release assets; the example does not configure a package
repository or native package-manager signing.
packages are GitHub Release assets. The example does not configure native
RPM or APK signing. Its package-repository request remains disabled until the
producer is reviewed, allowlisted, and configured with both signing keys.

GoReleaser has no command-line distribution-directory option in this invocation. The consumer's `.goreleaser.yaml` must write the same distribution directory that the workflow passes to `release-cli stage --dist`. This example uses GoReleaser's default `dist` directory and passes `--dist dist`.

Expand All @@ -58,20 +59,27 @@ Replace these project-specific example values:
- `initial-version` value `0.1.0` in `release-please-config.json` if the first intended release differs.
- Manifest value `0.0.0` in `.release-please-manifest.json` if the consumer already has a release. Use its latest released version without the `v` prefix.
- Linker variables `main.version` and `main.commit` in `.goreleaser.yaml` if the consumer command exposes version data through different variables. The copied sample defines both variables and prints `example <version> (<commit>)` for `--version`.
- Package repository owner and repository name in `.github/workflows/release.yml` with the reviewed central receiver.

The example uses one full commit SHA for every reusable workflow reference and
the checksum signing identity. That SHA is the consumer pin for the complete
release unit. The current pin is the `v0.1.3` release revision,
`0fc99489d31d400bc3f69d6636d60e7d3f3d0251`.
The release-asset and OCI jobs use one full commit SHA for their reusable
workflow references and checksum signing identity. That SHA is their consumer
pin for the complete release unit. The current baseline pin is the `v0.1.3`
release revision, `0fc99489d31d400bc3f69d6636d60e7d3f3d0251`.

The disabled package-repository job uses the `v0.1.16` revision,
`583937edadfbae183e49f16df46b98e0b36807ba`, because that revision introduced
the dispatch workflow and multi-version repository fix. Upgrade the complete
release unit to a reviewed current revision before enabling the job.

Keep these contract values unchanged:

- all four reusable workflow references at `0fc99489d31d400bc3f69d6636d60e7d3f3d0251`;
- `checksum-signing-workflow-ref` value `meigma/release/.github/workflows/go-pre-publish.yml@0fc99489d31d400bc3f69d6636d60e7d3f3d0251`;
- disabled package-repository workflow reference at `583937edadfbae183e49f16df46b98e0b36807ba`;
- organization variable `MEIGMA_RELEASE_APP_CLIENT_ID`;
- organization secret `MEIGMA_RELEASE_APP_PRIVATE_KEY`; and
- the locked Go 1.26.6, GoReleaser 2.17.1, Syft 1.51.0, Cosign 3.1.3, GitHub CLI 2.97.0, Melange 0.59.1, and apko 1.2.37 versions unless the shared workflow contract is deliberately updated.

The caller sets both `publish-image: false` and `publish-release: false` so the first run leaves a populated draft without writing to GHCR. After inspecting the draft and `oci-image` artifact, change both inputs to `true` and follow the recovery guide to publish through the same tag and release.
The caller sets `publish-image: false`, `publish-release: false`, and `publish-package-repository: false`. The first run therefore leaves a populated draft without writing to GHCR or dispatching to a central package repository. After inspecting the draft and `oci-image` artifact, change the image and GitHub Release inputs to `true` and follow the recovery guide to publish through the same tag and release. Enable package-repository dispatch only after the release is public, the central receiver allowlists the producer, native RPM and APK signing is configured, and the receiver's protected environment is ready.

The OCI builder retains the signed APK repository, apko lock, SPDX files, and OCI layout in the `oci-image` workflow artifact. The separate publisher verifies that artifact before pushing, signing, and attesting `ghcr.io/owner/repository`.