diff --git a/.agents/skills/apko/SKILL.md b/.agents/skills/apko/SKILL.md index 15e6f8e..fcb8623 100644 --- a/.agents/skills/apko/SKILL.md +++ b/.agents/skills/apko/SKILL.md @@ -1,187 +1,165 @@ --- name: apko description: > - Assemble the runtime OCI image for template-go-api with apko (`apko.yaml`) — the - Dockerfile-free, multi-arch, nonroot image built from the melange-produced apk plus a - minimal Wolfi base. Use when changing image contents, the `image-local`/`stack-up` mise - tasks, the `apko build`/`apko publish` steps in `.github/workflows/release.yml` or - `security-scan.yml`, runtime packages, the nonroot user, OCI annotations, or the - per-build SBOM. Pairs with the `melange` skill (the apk) and the `mise` skill (the CLI). + Maintain the Dockerfile-free template-go-api runtime image. Use when changing + apko.yaml, runtime packages, the nonroot account, local/security image builds, + or the reusable release unit's OCI assembly contract. Pairs with the melange + and mise skills. --- # apko -apko owns exactly one step in this repo: turn the melange-built apk plus a small set of -Wolfi base packages into the runtime image. It is the modern, distroless-equivalent -replacement for `gcr.io/distroless/static-debian12:nonroot` — no Dockerfile, no `RUN`, no -shell. The entire image is declared in `apko.yaml`; everything else (signing, attestation, -the apk itself) belongs to adjacent tools. +apko assembles the runtime filesystem from the signed +`template-go-api` APK and the declared Wolfi packages. Direct local and +security-scan builds invoke `apko build`. The production release delegates +locking, assembly, verification, publication, signing, and attestations to the +full-SHA-selected `meigma/release` unit and its `release-cli`. -## Verified against +## Verified contract -- apko `v1.2.19` (pinned in `mise.toml` as `aqua:chainguard-dev/apko`, locked in `mise.lock`). -- Grounded in the local `apko --help` for this version and the repo files - (`apko.yaml`, `mise.toml`, `compose.yaml`, `.github/workflows/release.yml`, - `.github/workflows/security-scan.yml`), not from memory. -- Run apko through mise so the pinned binary is used: `mise exec -- apko --help`. +- apko is `1.2.37`, pinned as `aqua:chainguard-dev/apko` in `mise.toml` and + locked in `mise.lock`. +- The guidance is grounded in `apko.yaml`, `melange.yaml`, the `image-local` + task, `.github/workflows/{release,security-scan}.yml`, and the v0.1.17 + `meigma/release` image contract. +- The image runs `/usr/bin/template-go-api` as UID/GID 65532 and includes CA + certificates and timezone data. ## Use this skill when -- Adding or removing a runtime dependency (a Wolfi package in `contents.packages`). -- Editing the nonroot account, entrypoint, archs, or OCI annotations in `apko.yaml`. -- Touching `apko build`/`apko publish` in the release or security-scan workflows, or the - `image-local`/`stack-up` mise tasks. -- Debugging the published image (wrong arch set, missing apk, untrusted `@local` package, - SBOM directory errors). - -## apko's lane (do not cross it) - -1. The image is defined **only** in `apko.yaml`. There is no Dockerfile and there must not - be one. Never add `RUN`, `apt`, `apk add`, shell steps, or a base-image `FROM`. -2. Add a runtime dependency by adding a **Wolfi package** to `contents.packages` — nothing - else. Keep the set minimal; every package is CVE surface in a distroless image. -3. apko consumes the apk; it never builds it. Source → apk is the `melange` skill's job. If - the app changed, rebuild the apk first, then apko. -4. apko does not sign or attest. `cosign sign` and the `attest.yml` provenance are separate - release steps. Do not fold them into apko invocations. -5. Keep it nonroot. The image runs as uid/gid 65532 with no shell; do not add a shell, - package manager, or root entrypoint for convenience. -6. Do not reintroduce `apko.lock.json` / `apko lock`. The Wolfi base floats by design (see - below). Pinning is recorded in the per-build SBOM + provenance, not a committed lockfile. - -## How the image is wired (`apko.yaml` anatomy) - -Read `apko.yaml` before changing anything. The load-bearing parts: - -- `contents.repositories`: the Wolfi os repo **and** `@local ./packages`. The `@local` - repository is melange's output directory — that is how the just-built apk is found. -- `contents.keyring`: the Wolfi signing key URL. The **ephemeral melange public key(s)** are - not listed here; they are appended at build/publish time with `--keyring-append` and are - never committed. Without the matching pub key in the keyring, apko refuses the `@local` - apk as unsigned. -- `contents.packages`: `wolfi-baselayout`, `ca-certificates-bundle`, `tzdata`, and - `template-go-api@local`. The `@local` suffix pins the package to the `@local` repository, - i.e. the apk melange just built (not anything from the Wolfi index). -- `accounts`: defines group+user `nonroot` (gid/uid **65532**) and `run-as: 65532`. Wolfi has - no `nonroot` package, so the user is created here. This mirrors `distroless:nonroot`. -- `entrypoint.command: /usr/bin/template-go-api` — where the `go/build` melange pipeline - installs the binary. -- `archs: [amd64, arm64]` — the index architectures. -- `annotations`: OCI labels. `org.opencontainers.image.version` carries the - `# x-release-please-version` marker; release-please bumps it. Do not hand-edit it. - -## Local build (the `image-local` mise task) - -`mise run image-local` builds a single host-arch image and loads it into Docker as -`template-go-api:dev` for `compose.yaml`. The apko step is: +- Adding or removing a runtime Wolfi package. +- Editing the entrypoint, account, architectures, environment, or OCI + annotations in `apko.yaml`. +- Maintaining the `image-local` or security-scan `apko build` command. +- Debugging the reusable release unit's OCI input or output contract. + +## Ownership rules + +1. The runtime filesystem is declared in `apko.yaml`. Do not add a Dockerfile, + `FROM`, `RUN`, a shell, a package manager, or imperative image mutations. +2. GoReleaser owns the canonical release binary. Melange installs the staged + `application` into the signed APK. apko installs that APK; it never compiles + the command. +3. Keep the runtime nonroot. Do not change UID/GID 65532 or the entrypoint for + convenience. +4. Local and scan builds append the generated APK repository and ephemeral + public key on the command line. Do not commit those build-specific paths to + `apko.yaml`. +5. Release Please does not own any value or version marker in `apko.yaml`. +6. Do not call `apko publish` from the consumer release workflow. + `release-cli` owns release assembly and the reusable OCI publisher owns + registry mutation, recursive signing, SBOM attestations, and provenance. + +## `apko.yaml` + +The load-bearing configuration is: + +- Wolfi `os` repository and signing key; +- runtime packages `wolfi-baselayout`, `ca-certificates-bundle`, `tzdata`, and + `template-go-api`; +- entrypoint `/usr/bin/template-go-api`; +- `nonroot` user and group with UID/GID 65532 and `run-as: 65532`; +- `PATH=/usr/bin` and + `SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt`; +- architectures `amd64` and `arm64`; and +- title, description, and source OCI annotations for + `meigma/template-go-api`. + +The file has no committed application version and no license annotation. The +reusable release build supplies release metadata and this template makes no +license claim. + +## Local build + +Run: ```bash -apko build apko.yaml template-go-api:dev image.tar \ +mise run image-local +``` + +The task compiles `.image-local/application`, packages it with Melange, and then +runs the equivalent of: + +```bash +arch="$(mise exec -- go env GOARCH)" +mise exec -- apko build apko.yaml template-go-api:dev image.tar \ --arch "$arch" \ + --repository-append ./packages \ --keyring-append ./melange.rsa.pub -docker load < image.tar -docker tag "template-go-api:dev-$arch" template-go-api:dev ``` -Non-obvious points: +The repository append exposes the package produced by Melange. The keyring +append makes apko trust that package's ephemeral signature. -- **The retag is required, not cosmetic.** A single-arch `apko build` loads into Docker under - an arch-suffixed tag (`template-go-api:dev-amd64` / `-arm64`, using the Go arch name from - `go env GOARCH`). `compose.yaml` expects the plain `template-go-api:dev`, so the task - retags. `security-scan.yml` does the same (`...:security-scan-amd64` → `...:security-scan`). -- `apko build` writes a tarball for `docker load`. The positional output can also be an - `oci-layout-dir/`, but the repo uses a `.tar`. -- `--keyring-append ./melange.rsa.pub` makes apko trust the locally-signed `@local` apk. The - local task mints one ephemeral key (`melange.rsa`); only its single pub key is appended. -- `--arch "$arch"` uses the explicit Go arch name. `host` is also accepted by apko, but the - task passes the resolved value. -- melange must have run first (`packages/` must contain the apk). The task does this in order; - if you run apko by hand, build the apk first — see the `melange` skill. +A single-architecture `apko build` creates the Docker tag +`template-go-api:dev-`. The task loads `image.tar` and retags it as +`template-go-api:dev`, which is the name used by `compose.yaml`. -## CI publish (`release.yml` → multi-arch index) +## Security scan -The `container-image-release` job assembles and pushes the multi-arch image: +The `container-vulnerability-scan` job builds Linux `amd64` into +`template-go-api:security-scan-amd64`, loads the tarball, and retags it as +`template-go-api:security-scan` before Trivy runs. Its apko command appends +`./packages` and `./melange.rsa.pub`, matching the local build contract. -```bash -mkdir -p sbom # apko does NOT create --sbom-path; it must pre-exist -apko publish apko.yaml "$IMAGE_TAG" \ - --arch amd64,arm64 \ - --keyring-append ./melange-amd64.rsa.pub \ - --keyring-append ./melange-arm64.rsa.pub \ - --sbom-path ./sbom \ - | tee apko-out.txt -``` +## Reusable release image path + +The consumer caller has four jobs: `release-assets`, `oci-image`, +`oci-publish`, and `github-release`. The image path is: + +1. GoReleaser creates and signs the release bundle and projects its verified + Linux binaries as the canonical image inputs. +2. `go-oci-build.yml` verifies that artifact handoff. +3. The release unit's `release-cli image build` stages each binary as + `application`, builds signed APK repositories with Melange, creates a + build-local apko lock, and composes an OCI layout with `amd64` and `arm64` + manifests and architecture SBOMs. +4. `release-cli image verify` checks the layout, package contents, entrypoint, + runtime user, annotations, binary digests, and SBOMs. +5. `publish-oci-image.yml` verifies the authoritative artifact and, when + `publish-image` is true, publishes the digest-addressed image, signs it, and + records provenance and per-architecture SBOM attestations. -Non-obvious points: - -- **`--sbom-path` must pre-exist.** apko moves SBOMs into the directory but does not create - it; a missing `sbom/` is a real release failure (fixed in session 015 by `mkdir -p sbom`). -- **Two keyring keys.** Each arch's apk was signed on its own native runner with its own - ephemeral key, so both `melange-amd64.rsa.pub` and `melange-arm64.rsa.pub` must be appended; - apko verifies each per-arch apk against the matching key. -- **`docker login` is a precondition.** `apko publish` authenticates via the Docker keychain. - release.yml runs `docker/login-action` against `ghcr.io` first; without it, the push fails. -- **The digest is parsed from stdout.** apko prints the published refs/digest; release.yml - greps `sha256:[0-9a-f]{64}` and takes the last match. `--image-refs ` can write the - refs to a file instead, but the repo parses stdout. -- The image is pushed even while the GitHub release is still a draft — GHCR has no draft - state. That is expected. -- apko emits its own SBOM (`--sbom-path ./sbom`, spdx by default). The **attested** image SBOM - is generated separately by `syft -o spdx-json` and attached with `actions/attest-sbom`. - Two different SBOMs; do not conflate them. - -After publish, release.yml (adjacent steps, not apko's job): verifies the manifest is exactly -`linux/amd64,linux/arm64` via `docker buildx imagetools inspect`, smoke-tests `--version` and -`openapi | grep "openapi: 3.0.3"`, then `cosign sign --yes` (keyless), syft SBOM attestation, -and finally the isolated `attest.yml` SLSA-L3 provenance. - -## Multi-arch model - -apko does not emulate. It assembles a 2-arch index from per-arch apks that **melange already -built natively** (amd64 on `ubuntu-24.04`, arm64 on `ubuntu-24.04-arm`, no QEMU). The -`archs:` in `apko.yaml` and `--arch amd64,arm64` must line up with the apks present under -`packages/` (Wolfi arch dirs `x86_64`/`aarch64`). If an arch's apk is missing, publish fails. +The initial caller keeps `publish-image: false`. The complete layout still +builds and verifies during rehearsal, but GHCR is not mutated. + +## Locks and package resolution + +`apko.yaml` contains no committed version marker or repository-specific lock. +Direct local and security-scan builds resolve current Wolfi packages. The +release unit creates a build-local lock after it has the exact staged APK +repositories, and its SBOMs and attestations record the release contents. + +Do not commit `apko.lock.json`. A committed lock would bind future builds to a +stale application APK instead of the canonical binary for that release. ## Read-only inspection -Use these to reason about the image without building it: +Before inspecting packages from the local Melange output, include the same +repository and public key as the build: ```bash -apko show-config apko.yaml # the fully-derived config apko will act on -apko show-packages apko.yaml # exact packages + versions that would install +mise exec -- apko show-packages apko.yaml \ + --repository-append ./packages \ + --keyring-append ./melange.rsa.pub ``` -`show-packages` resolves the live Wolfi index, so it shows what the floating base would pull -right now — the right tool to preview a CVE bump or confirm a new package resolves. Both -accept `--keyring-append`/`--repository-append` if you need them to see the `@local` apk. +Use `mise exec -- apko show-config apko.yaml` to inspect the static image +configuration without building an image. -## Why `apko lock` is deliberately unused - -`apko lock` exists (it writes a `.lock.json` of pinned package versions) but the repo does not -use it, on purpose: +## Gotchas -- The app package is a per-build `@local` apk; a committed lock would pin a stale/foreign - checksum for it. -- The Wolfi base (`ca-certificates-bundle`, `tzdata`, …) is meant to float to latest for a - fresh CA bundle/timezones and low CVE surface. Pinning fights that model. -- Reproducibility comes from recording the exact resolved versions in the per-build SBOM + - provenance attestation, not from a lockfile. Do not add `apko.lock.json`. +- apko uses `amd64` and `arm64`; Melange uses `x86_64` and `aarch64`. +- Run Melange before a direct apko build so `packages/` and + `melange.rsa.pub` exist. +- Keep both `--repository-append` and `--keyring-append`. Omitting either makes + the staged application package unavailable or untrusted. +- The local build's arch-suffixed tag must be retagged before Compose uses it. +- Production publication is a reusable release-unit responsibility. Do not add + registry login, `apko publish`, Cosign, Syft, or attestation steps to the + consumer workflow. -## Gotchas +## Command reference -- Single-arch `apko build` Docker-loads under an **arch-suffixed tag**; retag before using it - in compose. (The suffix is the value passed to `--arch`; the repo passes the Go arch - name, e.g. `-amd64`, not the Wolfi `-x86_64`.) -- `--sbom-path` directory must already exist. -- `--keyring-append` is mandatory for the `@local` apk and must cover **every** arch being - published. -- `apko publish` needs a prior `docker login`; `apko build` (tarball) does not. -- `org.opencontainers.image.version` in `apko.yaml` is release-please-owned — never hand-edit. -- Adding a runtime dep means a Wolfi package in `contents.packages`, then rebuild the apk and - the image. There is no Dockerfile to edit. -- apko is pinned by mise; invoke it via `mise exec -- apko …` (or a mise task / shimmed PATH), - not a system install. Bumping apko is a `mise.toml` + `mise lock` change — see the `mise` - skill. - -See [references/apko-commands.md](references/apko-commands.md) for the version-stamped command -and flag reference. +See [references/apko-commands.md](references/apko-commands.md) for the +version-stamped command reference. diff --git a/.agents/skills/apko/references/apko-commands.md b/.agents/skills/apko/references/apko-commands.md index 94cb604..1a8c14b 100644 --- a/.agents/skills/apko/references/apko-commands.md +++ b/.agents/skills/apko/references/apko-commands.md @@ -1,156 +1,106 @@ # apko Command Map -Curated operator reference for `apko v1.2.19` (pinned in `mise.toml` as -`aqua:chainguard-dev/apko`, locked in `mise.lock`). Prefer local `--help` -(`mise exec -- apko --help`) and the official docs if anything here drifts. Only the -flags this repo uses, plus the few an operator reaches for, are listed — every flag below is -real and correctly spelled/typed for this version. +Repository-specific reference for apko `1.2.37`, pinned in `mise.toml` as +`aqua:chainguard-dev/apko` and locked in `mise.lock`. Prefer +`mise exec -- apko --help` when the pinned version changes. -## Global flags +## Supported local entry point -Apply to every `apko` subcommand: +Build and load the complete host-architecture image with: -- `--log-level `: `debug|info|warn|error|fatal|panic` (default `INFO`). -- `-C, --workdir `: working directory (default is the current dir). apko searches for - config, base image, and `@local` paths relative to this. -- `-h, --help`. +```bash +mise run image-local +``` -## `apko build` +The task compiles the Linux application, packages it with Melange, and invokes +apko only after the signed APK repository exists. -Purpose: build an image from `apko.yaml` into a `docker load`-able tarball (or an -`oci-layout-dir/`). Used by the `image-local` mise task and `security-scan.yml`. +## `apko build` -Usage: +Direct local and security-scan paths use: -```bash -apko build [flags] +```text +apko build [flags] ``` -Flags that matter here: - -- `--arch `: architectures to build (e.g. `amd64`, `x86_64`, or `amd64,arm64`). - Accepts `host` for the host arch. Default is all archs in the config. The repo passes a - single resolved arch for local/scan builds. -- `-k, --keyring-append `: extra public keys to trust. **Required** here to trust the - ephemeral melange-signed `@local` apk (`--keyring-append ./melange.rsa.pub`). Repeatable. -- `--sbom`: generate SBOMs (default `true`; disable with `--sbom=false`). -- `--sbom-path `: write SBOMs to this dir (default: the image dir). Must already exist. -- `--sbom-formats `: SBOM formats (default `[spdx]`). -- `--annotations `: OCI annotations as `key:value` (colon-separated). The repo - declares annotations in `apko.yaml` instead. -- `-r, --repository-append `: extra package repositories. -- `-b, --build-repository-append `: extra repositories used only at build time. -- `-p, --package-append `: extra packages beyond `contents.packages`. -- `--build-date `: timestamp (RFC3339) for files inside the image. Repo relies on the - default; set only when you need a specific reproducible timestamp. -- `--offline`: do not fetch packages (cache must be pre-populated). -- `--cache-dir `: apk/index cache directory. -- `--lockfile `: constrain package versions to a `.lock.json`. Not used here (see the - SKILL on why `apko lock` is avoided). -- `--include-paths `: extra paths to resolve input files from. -- `--ignore-signatures`: skip repository signature verification. Do not use; it defeats the - `@local`/Wolfi signing checks. - -Notes: - -- A single-arch build loads into Docker under an **arch-suffixed tag** (`-`, Go arch - name). Retag to the plain `` before use in compose. -- `build` has **no** `--local` or `--image-refs` flag — those are `publish`-only. +Repository arguments: -## `apko publish` - -Purpose: build **and push** the (multi-arch) image to a registry, printing the published -refs/digest to stdout. Used by `release.yml` (`container-image-release`). - -Usage: +| Argument | Repository contract | +| --- | --- | +| `apko.yaml` | Runtime image configuration. | +| `template-go-api:dev` or `template-go-api:security-scan` | Local image tag. | +| `image.tar` or `scan.tar` | Tarball loaded into Docker. | +| `--arch amd64` or `--arch arm64` | One Go/apko architecture. | +| `--repository-append ./packages` | Melange's generated APK repository. | +| `--keyring-append ./melange.rsa.pub` | Public key for the generated APK signature. | -```bash -apko publish [flags] -``` +Both append flags are required. `apko.yaml` intentionally contains neither the +ephemeral repository path nor the ephemeral key. -Flags that matter here: - -- `--arch `: architectures for the index. Repo uses `--arch amd64,arm64`; must match - the per-arch apks present under `packages/`. -- `-k, --keyring-append `: trust keys. Repo appends **both** ephemeral melange pub - keys (`./melange-amd64.rsa.pub`, `./melange-arm64.rsa.pub`) — one per arch. Repeatable. -- `--sbom-path `: write SBOMs here. **apko does not create this dir** — `mkdir -p` - it first (real release bug otherwise). -- `--sbom` (default `true`; `--sbom=false` to disable), `--sbom-formats ` (default `[spdx]`). -- `--image-refs `: write the published refs to a file. Repo parses stdout instead. -- `--local`: publish only to the local Docker daemon (no registry push). Not used here. -- `--annotations `: OCI annotations `key:value`. Repo uses `apko.yaml`. -- `-r, --repository-append` / `-b, --build-repository-append` / `-p, --package-append`: as in - `build`. -- `--build-date`, `--offline`, `--cache-dir`, `--lockfile`, `--ignore-signatures`: as in - `build`. - -Notes: - -- Authenticates via the **Docker keychain** — run `docker login ` first. -- Accepts multiple `` positionals; the repo passes one version tag. -- Pushes even while the GitHub release is a draft (GHCR has no draft state). +The local task passes the host architecture and retags +`template-go-api:dev-` as `template-go-api:dev`. The security scan passes +`amd64` and retags `template-go-api:security-scan-amd64` as +`template-go-api:security-scan`. ## `apko show-config` -Purpose: print the fully-derived config apko will act on (YAML). Read-only. - -Usage: +Inspect the static image configuration: ```bash -apko show-config [flags] +mise exec -- apko show-config apko.yaml ``` -Flags: `-k, --keyring-append`, `-r, --repository-append`, `-b, --build-repository-append`, -`--offline`, `--cache-dir`. No `--arch`. +This does not add the direct-build repository or key because they are +build-specific command-line inputs. ## `apko show-packages` -Purpose: resolve and print the exact packages + versions that would install, without building. -Best preview of what the floating Wolfi base pulls right now (CVE bumps, new packages). - -Usage: +After `mise run image-local` has created `packages/` and +`melange.rsa.pub`, inspect the resolved package set with: ```bash -apko show-packages [flags] +mise exec -- apko show-packages apko.yaml \ + --repository-append ./packages \ + --keyring-append ./melange.rsa.pub ``` -Flags that matter here: - -- `--arch `: which arch's resolution to show (accepts `host`). -- `--format `: predefined name (e.g. `name-version`, `name=version`, `packagelock`, - `packagelock-source`) or a Go template over `.Name`, `.Version`, `.Source` (default - `{{ .Name }} {{ .Version }}`). `packagelock`/`packagelock-source` emit a YAML-list-ready form. -- `-k, --keyring-append`, `-r, --repository-append`, `-b, --build-repository-append`, - `--offline`, `--cache-dir`. Append the melange pub key if you need it to resolve `@local`. +The output includes `wolfi-baselayout`, `ca-certificates-bundle`, `tzdata`, and +the generated `template-go-api` package with the versions resolved for that +invocation. ## `apko lock` -Exists but intentionally **not used** in this repo (see the SKILL). Writes a `.lock.json` that -pins package versions. +The consumer repository does not commit an apko lock. The production +`release-cli image build` command creates a build-local lock after it has +verified and staged the exact GoReleaser binaries and built their APK +repositories. Local and security-scan builds resolve current Wolfi packages. -Usage: +Do not run `apko lock` to create `apko.lock.json` in the repository. Such a lock +would pin an application APK from an earlier build. -```bash -apko lock [flags] -``` +## `apko publish` + +This command exists in apko but is not part of the consumer release path. Do not +add it to `.github/workflows/release.yml`. -Flags: `--arch`, `--output ` (lockfile path), `-k, --keyring-append`, -`-r, --repository-append`, `-b, --build-repository-append`, `--cache-dir`, -`--include-paths`, `--ignore-signatures`. Do not add `apko.lock.json` to this repo. +The pinned `meigma/release` unit owns the production sequence: -## `apko version` +1. `release-cli image build` composes the locked OCI layout. +2. `release-cli image verify` checks its contents and metadata. +3. `publish-oci-image.yml` verifies the authoritative artifact. +4. With `publish-image: true`, the publisher pushes the digest-addressed image, + recursively signs it, records provenance, and attaches per-architecture + SBOM attestations. -Purpose: print the apko version. +During the initial rehearsal, `publish-image: false` leaves the verified layout +in workflow artifacts and does not mutate GHCR. -Usage: +## Version + +Confirm that mise resolves the required binary: ```bash -apko version [--json] +mise exec -- apko version ``` -## Other subcommands (not used here) - -`build-minirootfs`, `clean`, `dot`, `install-keys`, `login`, `completion`, `help`. `apko login` -can authenticate to a registry, but the repo relies on `docker login` + the Docker keychain. -`apko dot` (dependency digraph) and `apko clean` (cache) are occasional debugging aids. +The output must identify apko `1.2.37` for the current repository contract. diff --git a/.agents/skills/melange/SKILL.md b/.agents/skills/melange/SKILL.md index 71e36c5..628e361 100644 --- a/.agents/skills/melange/SKILL.md +++ b/.agents/skills/melange/SKILL.md @@ -1,135 +1,142 @@ --- name: melange description: > - Build the project's signed Wolfi apk with melange. Use when editing melange.yaml or its - go/build pipeline, changing how version/commit/date are stamped into the binary, adding a - build-time package, signing the apk, or debugging `mise run image-local` or the release - `melange-build` job. This is the source-to-signed-apk step that apko later turns into the image. + Package the staged template-go-api Linux binary as a signed Wolfi APK. Use + when editing melange.yaml, changing the staged application contract, or + debugging the local, security-scan, or reusable release image build. --- # Melange -melange has exactly one job in this repo: compile the Go binary into a signed -[Wolfi](https://github.com/wolfi-dev) apk described by `melange.yaml`. That apk is the only -artifact apko assembles into the runtime image. There is no Dockerfile. Ground every command -in `--help` and the repo files below, not memory. +Melange packages an already compiled Linux binary. It does not compile Go in +this repository. `melange.yaml` installs the staged file named `application` as +`/usr/bin/template-go-api`; apko then installs the resulting APK into the image. -## Verified against +## Verified contract -- melange `v0.54.0` (GitCommit `7fb1d6a`), pinned in `mise.toml` as - `aqua:chainguard-dev/melange = "0.54.0"` and locked per-platform in `mise.lock`. Run it via - mise (`mise exec -- melange ...`) or an activated shell; do not install it any other way. -- Grounded in local `melange --help` / `melange build --help` and the repo files: `melange.yaml`, - `mise.toml` (`[tasks.image-local]`), `.github/workflows/release.yml`, `release-dry-run.yml`, - and `security-scan.yml`. -- Sibling skills: `mise` provisions melange and puts it on PATH; `apko` consumes the apk this - step produces. See those skills for their lanes. +- Melange is `0.59.1`, pinned as + `aqua:chainguard-dev/melange` in `mise.toml` and locked in `mise.lock`. +- The guidance is grounded in `melange.yaml`, the `image-local` task in + `mise.toml`, `.github/workflows/security-scan.yml`, and the v0.1.17 + `meigma/release` Go image contract. +- The `mise` skill owns tool installation. The `apko` skill owns the runtime + image configuration and direct local assembly. ## Use this skill when -- Editing `melange.yaml` or the `go/build` pipeline. -- Changing how `version` / `commit` / `date` reach the binary. -- Adding a build-time package or toolchain to the apk. -- Signing an apk or reasoning about the melange-to-apko key handoff. -- Debugging a failed `mise run image-local` or the release/dry-run melange build job. - -## melange's lane (non-negotiable) - -1. melange does ONE thing: source to signed Wolfi apk. It does not build the image (that is - apko) and it does not push anything anywhere. -2. The apk is the single artifact. apko reads it from `./packages` via `@local`; nothing else - consumes, tags, or distributes it. -3. Never hand-edit `package.version` in `melange.yaml`. It carries `# x-release-please-version` - and release-please owns it. Do not run `melange bump` in this repo. -4. Inject `version` / `commit` / `date` ONLY through `--vars-file`. Never edit the `ldflags` - line per build and never add date math inside the sandbox. The defaults under `vars:` - (`0.0.0` / `none` / `unknown`) exist only so a bare build does not fail. -5. Never commit signing keys. `melange*.rsa`, `melange*.rsa.pub`, `melange-vars.yaml`, - `.melange-vars.local.yaml`, and `/packages/` are gitignored. Keys are ephemeral, minted per - build; the private key never leaves the machine that built the apk. -6. No Dockerfile, no `RUN`, no `apt`. Build-time tools come from - `environment.contents.packages` (the `go-1.26` Wolfi package). Runtime dependencies belong - in `apko.yaml`, not here. -7. Always pass `--runner docker`. Do not rely on the platform default runner. - -## melange.yaml anatomy - -- `package`: `name: template-go-api`, `version: "1.0.4"` (release-please marker), `epoch: 0`. -- `environment.contents`: the Wolfi `os` repository + its signing keyring, plus - `packages: [go-1.26]` (the build toolchain). `environment.environment.CGO_ENABLED: "0"`. -- `vars`: build-time defaults overridden by `--vars-file`. -- `pipeline: - uses: go/build` with `packages: ./cmd/template-go-api`, - `output: template-go-api`, `go-package: go-1.26`, `modroot: .`, `strip: "-s -w"`, the - `ldflags` that stamp `main.version/commit/date` from `${{vars.*}}`, and - `extra-args: "-mod=readonly -buildvcs=false"`. The `go/build` builtin auto-adds `-trimpath` - and installs to `/usr/bin/` — apko's entrypoint `/usr/bin/template-go-api` depends on - that path. Embedded assets (SQL migrations, Cedar policies) compile in from the mounted - `--source-dir`. - -## Build the apk locally - -`mise run image-local` is the supported local path; it runs melange then apko. The melange -portion is: +- Editing package metadata or the install destination in `melange.yaml`. +- Changing how a compiled binary is staged as `application`. +- Debugging APK architecture mapping, package signing, or the handoff to apko. +- Reasoning about the reusable release unit's OCI build. -```bash -melange keygen melange.rsa -printf 'version: "dev"\ncommit: "%s"\ndate: "%s"\n' \ - "$(git rev-parse --short HEAD)" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > .melange-vars.local.yaml -melange build melange.yaml \ - --arch "$(go env GOARCH)" \ - --runner docker \ - --signing-key melange.rsa \ - --source-dir . \ - --vars-file .melange-vars.local.yaml -``` +## Ownership rules + +1. GoReleaser produces the canonical Linux release binaries. The reusable + release unit verifies those bytes before the image build. +2. Local and security-scan builds compile the application first. Melange only + packages the staged output. +3. The pipeline must remain a direct install of `application`. Do not add a Go + toolchain package, source compilation, or ldflags to `melange.yaml`. +4. `package.version` is `${{vars.version}}`. Release Please does not own a + version marker in this file. +5. Runtime packages belong in `apko.yaml`. Melange's build environment contains + only the package tools needed to install the staged file. +6. Signing keys are ephemeral. Never commit `melange*.rsa` or + `melange*.rsa.pub`. -This drops a signed apk under `./packages//` (default `--out-dir` is `./packages/`). -`` is the Wolfi arch name, not the Go arch: `amd64` to `x86_64`, `arm64` to `aarch64`. -apko then reads the whole `./packages` directory as the `@local` repository. +## `melange.yaml` -## How the release / CI build differs +The package contract is: -`release.yml` (`melange-build`) and `release-dry-run.yml` run the same `melange build` -invocation under a matrix, one arch per NATIVE runner — `amd64` on `ubuntu-24.04`, `arm64` on -`ubuntu-24.04-arm`. No QEMU. Differences from local: +- package name `template-go-api`; +- target APK architectures `x86_64` and `aarch64`; +- version supplied as the `version` build variable, with `0.0.0` as the local + fallback; +- the Wolfi repository and signing keyring; +- `busybox` in the build environment for `install`; and +- one pipeline command that installs `application` with mode `0755` and + ownership `0:0` at `/usr/bin/template-go-api`. -- Each runner mints its own ephemeral key with a distinct name: `melange keygen melange-.rsa`. -- The vars file (`melange-vars.yaml`) carries the real release `version`, the full - `git rev-parse HEAD`, and the committer date `git show -s --format=%cI HEAD`. -- Each runner uploads `packages//**` plus its `melange-.rsa.pub`. The private key - never leaves the runner; apko later trusts the apk via the uploaded public keys. +There is no license field because this template does not claim a license. -`security-scan.yml` builds only `amd64` for the weekly Trivy scan — the same -`melange build`, but it mints a single `melange.rsa` key (not the per-arch -`melange-.rsa` naming above). +## Local image build -## Signing and the apko handoff +Run the repository task: + +```bash +mise run image-local +``` -`--signing-key` signs the apk during the build. apko must trust that signature to install the -`@local` apk, so the matching public key is appended to apko's keyring with -`--keyring-append`. Locally that is `--keyring-append ./melange.rsa.pub`; in the release job apko -appends both arches' keys (`./melange-amd64.rsa.pub`, `./melange-arm64.rsa.pub`). Omit the public -key and apko rejects the apk as untrusted. See the `apko` skill. +The task performs these operations in order: + +1. Map Go `amd64` to APK `x86_64`, or Go `arm64` to APK `aarch64`. +2. Compile `./cmd/template-go-api` with + `CGO_ENABLED=0 GOOS=linux GOARCH=` into + `.image-local/application`. The Go build stamps the development version, + commit, and date. +3. Generate an ephemeral `melange.rsa` key. +4. Write `{"version":"0.0.0"}` to `.melange-vars.local.json`. +5. Run `melange build` with the selected APK architecture, + `--runner docker`, `--source-dir .image-local`, `--out-dir packages`, + `--vars-file .melange-vars.local.json`, and the ephemeral signing key. +6. Pass `packages/` and `melange.rsa.pub` to apko. + +The staged input is deliberately outside the Go source tree. Changing +`--source-dir` back to `.` would widen the package build input and obscure the +binary handoff. + +## Security scan + +The `container-vulnerability-scan` job builds only Linux `amd64`. It compiles +the binary into `scan-input/application`, maps the package architecture to +`x86_64`, and invokes Melange with `--source-dir scan-input`, +`--out-dir packages`, and `--vars-file melange-vars.json`. The job then builds +and scans a local apko image. It does not use a separate release rehearsal +workflow. + +## Reusable release image build + +`.github/workflows/release.yml` delegates the production OCI path to the +full-SHA-pinned `meigma/release` unit: + +1. `go-pre-publish.yml` runs GoReleaser and projects the verified Linux + `amd64` and `arm64` binaries as the canonical OCI inputs. +2. `go-oci-build.yml` verifies the artifact handoff and invokes its matching + `release-cli`. +3. `release-cli image build` stages each verified binary as `application`, + creates the ephemeral Melange signing key, builds both APK repositories, + locks the apko inputs, and composes the OCI layout and architecture SBOMs. +4. The reusable OCI publisher owns registry publication, signing, and + attestations. + +Do not add a release-only Melange job to this repository or rebuild the Go +command in Melange. A second compilation would break the canonical-binary +contract. + +## Signing handoff + +Direct local and scan builds pass `--signing-key melange.rsa` to Melange. apko +must receive the matching public key with +`--keyring-append ./melange.rsa.pub` and the generated repository with +`--repository-append ./packages`. + +In the reusable release path, `release-cli` owns key generation, paths, APK +verification, and the apko handoff. The private key remains in its scratch +directory. ## Gotchas -- `--runner docker` is required wherever melange runs: it needs a Linux build sandbox, and on - macOS/Docker Desktop the Docker runner provides it. Docker must be running. Valid runners are - `bubblewrap`, `docker`, `qemu`; this repo always uses `docker`. -- Wolfi arch name is not the Go arch in output paths: `amd64`→`x86_64`, `arm64`→`aarch64`. Match - these when globbing `packages//**`. -- melange produces an SBOM for the apk (`--namespace` sets its package-URL namespace). The - image-level SBOM and SLSA provenance are produced later by syft + `attest.yml`, NOT by melange. - Do not add `--generate-provenance` to chase that; the repo does not use it. -- Build metadata flows version/commit/date through `--vars-file` into `ldflags` only. Neither - local nor CI uses melange's `--build-date` flag (that controls in-image file timestamps for - reproducibility, a different concern from the ldflag date). -- Do not override `output:` in the pipeline without updating `apko.yaml`'s `entrypoint.command` - and `contents.packages` — the binary path `/usr/bin/template-go-api` is contractual. -- To add a build-time tool or a different Go toolchain, edit `environment.contents.packages` - (Wolfi package names) in `melange.yaml`. Do not install inside the sandbox. +- Melange uses APK architecture names: `x86_64` and `aarch64`. Go and apko use + `amd64` and `arm64`. +- The direct local and scan invocations require `--runner docker`; Docker must + be running. +- `application` must be a compiled Linux executable before Melange starts. +- Package version changes arrive through `vars.version`; do not run + `melange bump`. +- Image signing, SBOM attestations, and provenance are release-unit + responsibilities. Do not add local provenance or attestation workflow files. ## Command reference -See [references/melange-commands.md](references/melange-commands.md) for the version-stamped -command and flag reference. +See [references/melange-commands.md](references/melange-commands.md) for the +version-stamped command reference. diff --git a/.agents/skills/melange/references/melange-commands.md b/.agents/skills/melange/references/melange-commands.md index 4343853..d1872c5 100644 --- a/.agents/skills/melange/references/melange-commands.md +++ b/.agents/skills/melange/references/melange-commands.md @@ -1,151 +1,84 @@ # Melange Command Map -Curated operator reference for `melange v0.54.0`. Prefer local `melange --help` / -`melange --help` output and the official docs (https://github.com/chainguard-dev/melange) -if anything here drifts. Every flag below is copied from the pinned version's `--help`; flags -not used by this repo are marked so. +Repository-specific reference for Melange `0.59.1`, pinned in `mise.toml` as +`aqua:chainguard-dev/melange` and locked in `mise.lock`. Prefer +`mise exec -- melange --help` when the pinned version changes. -## Global flags +## Supported entry point -Apply across all `melange` commands: - -- `--log-level string`: log level — `debug`, `info`, `warn`, `error` (default `INFO`). -- `-h`, `--help`: help for the command. - -## `melange build` - -Purpose: build a package (the signed apk) from a YAML configuration file. This is the repo's -core operation. - -Usage: +Build the complete local image through the mise task: ```bash -melange build [config.yaml] [flags] +mise run image-local ``` -Flags that matter here: - -- `--arch strings`: architectures to build for (e.g. `x86_64,arm64`). The repo passes a single - Go-style arch per invocation (`amd64` or `arm64`); default is all arches in the config. -- `--runner string`: runner used to execute build steps — `bubblewrap`, `docker`, or `qemu`. - The repo always passes `docker` (required on macOS; needs Docker running). -- `--signing-key string`: key used to sign the produced apk. -- `--source-dir string`: directory of included sources mounted into the build (the repo passes - `.` so embedded SQL migrations and Cedar policies compile in). -- `--vars-file string`: file of preloaded build vars; overrides `vars:` in the config. The repo - injects `version` / `commit` / `date` this way. -- `-k`, `--keyring-append strings`: extra keys to include in the build environment keyring (for - pulling from signed repositories; the apko keyring handoff is a separate concern). -- `--out-dir string`: directory packages are written to (default `./packages/`). The apk lands - in `//`, where the Wolfi arch is `x86_64`/`aarch64`, not `amd64`/`arm64`. -- `-r`, `--repository-append strings`: extra repositories for the build environment. -- `--package-append strings`: extra packages to install into the build environment. -- `--namespace string`: namespace for package URLs in the generated apk SBOM (default `unknown`). -- `--generate-index`: whether to generate `APKINDEX.tar.gz` (default `true`). -- `--cache-dir string`: cached inputs directory (default `./melange-cache/`). -- `--debug`: enable debug logging of build pipelines. -- `--debug-runner`: keep the builder container after success/failure for inspection. -- `-i`, `--interactive`: attach a tty to the builder pod on failure. - -Not used by this repo (do not add without reason): - -- `--generate-provenance`: emits SLSA provenance as a separate `.attest.tar.gz` next to the apk. - This repo produces image-level provenance via `attest.yml` instead, so this stays off. -- `--build-date string`: timestamp for files inside the image (reproducibility). Distinct from - the ldflag `date`, which comes from `--vars-file`. The repo leaves this at default. - -Notes: - -- Canonical repo invocation: `melange build melange.yaml --arch --runner docker - --signing-key .rsa --source-dir . --vars-file .yaml`. -- The output public key (the `.rsa.pub` matching `--signing-key`) is handed to `apko build` / - `apko publish` via `--keyring-append` so apko trusts the `@local` apk. - -## `melange keygen` - -Purpose: generate an RSA keypair for package signing. - -Usage: - -```bash -melange keygen [key.rsa] [flags] -``` - -Flags: - -- `--key-size int`: size of the RSA key in bits (default `4096`). - -Notes: +This task compiles `.image-local/application` before invoking Melange. Do not +run Melange against the repository source as a substitute for that compilation. -- Writes `.rsa` (private) and `.rsa.pub` (public). In this repo keys are ephemeral - and gitignored; CI uses per-arch names (`melange-amd64.rsa`, `melange-arm64.rsa`). - -## `melange sign` - -Purpose: sign an existing `.apk` on disk in place with the provided key. - -Usage: - -```bash -melange sign [--signing-key=key.rsa] package.apk -melange sign [--signing-key=key.rsa] *.apk -``` +## `melange build` -Flags: +`melange build` packages the staged `application` file according to +`melange.yaml`. -- `-k`, `--signing-key string`: signing key (default `local-melange.rsa`). +The local task uses these arguments: -Notes: +| Argument | Repository contract | +| --- | --- | +| `melange.yaml` | Package configuration. | +| `--arch x86_64` or `--arch aarch64` | APK architecture mapped from Go `amd64` or `arm64`. | +| `--runner docker` | Linux package sandbox used by local and security-scan builds. | +| `--signing-key melange.rsa` | Ephemeral APK signing key. | +| `--source-dir .image-local` | Directory containing the compiled file named `application`. | +| `--out-dir packages` | Repository passed to apko with `--repository-append ./packages`. | +| `--vars-file .melange-vars.local.json` | JSON containing the package `version` variable. | -- The repo signs during `build` via `--signing-key`, so this standalone command is rarely - needed. Use it only to re-sign a prebuilt apk. +The security scan uses the same command shape with +`--source-dir scan-input`, `--arch x86_64`, and +`--vars-file melange-vars.json`. -## `melange sign-index` +`melange.yaml` contains no source-build pipeline. It installs `application` as +`/usr/bin/template-go-api` with mode `0755` and ownership `0:0`. -Purpose: sign an APK repository index (`APKINDEX.tar.gz`). +## `melange keygen` -Usage: +Direct local and scan builds create an ephemeral package-signing key: ```bash -melange sign-index [--signing-key=key.rsa] -melange sign-index [--signing-key=key.rsa] --force +mise exec -- melange keygen melange.rsa ``` -Flags: +The command writes `melange.rsa` and `melange.rsa.pub`. Pass the private key to +`melange build --signing-key` and the public key to +`apko build --keyring-append`. Both files are build artifacts and must remain +uncommitted. -- `-f`, `--force`: overwrite the existing index with a newly signed one. -- `--signing-key string`: signing key (default `melange.rsa`). - -Note: the default key name here (`melange.rsa`) differs from `sign`'s default -(`local-melange.rsa`). Not part of the repo's build flow. - -## `melange package-version` - -Purpose: print the target package id for a config, i.e. -`{{ .Package.Name }}-{{ .Package.Version }}-r{{ .Package.Epoch }}`. - -Usage: - -```bash -melange package-version [config.yaml] -``` +The reusable release unit does not depend on this fixed filename. +`release-cli image build` creates and manages its own key under the build +scratch directory. -Notes: +## Architecture names -- Read-only; useful for scripting the expected apk filename. It is sugar over `melange query`. +| Go and apko | Melange and APK | +| --- | --- | +| `amd64` | `x86_64` | +| `arm64` | `aarch64` | -## `melange bump` +Pass the APK name to `melange build --arch`. Passing the Go name would diverge +from the repository's local and security-scan commands. -Purpose: update a melange YAML to a new package version. +## Package version -Notes: +`package.version` reads `${{vars.version}}`. Local and scan builds supply +`0.0.0`; the reusable release image build supplies the stable release version. +Release Please does not edit `melange.yaml`, and this repository does not use +`melange bump`. -- Do NOT use in this repo. `package.version` carries `# x-release-please-version` and is owned - by release-please. Flags are not captured here; run `melange bump --help` if ever needed - outside this repo. +## Release ownership -## Other subcommands (present, unused here) +The production caller does not run a repository-local Melange build job. +`go-oci-build.yml` invokes the full-SHA-selected `release-cli`, which stages the +GoReleaser binaries as `application` and runs Melange for both architectures. +The reusable release unit then owns apko locking and assembly, image +verification, publication, signing, SBOM attestations, and provenance. -`compile`, `index`, `initramfs`, `license-check`, `lint` (experimental), `query`, `scan`, -`source`, `test`, `update-cache`, `version`, `completion`. None are part of the repo's build -path; consult `melange --help` before relying on any of them. +Do not add standalone provenance generation to the local Melange command. diff --git a/.agents/skills/mise/SKILL.md b/.agents/skills/mise/SKILL.md index c06b221..769b0bb 100644 --- a/.agents/skills/mise/SKILL.md +++ b/.agents/skills/mise/SKILL.md @@ -1,176 +1,160 @@ --- name: mise description: > - Operate mise as the single source of truth for tool versions and integrity in - template-go-api. Use when touching mise.toml or mise.lock, bumping or adding a - pinned tool (go, python, uv, golangci-lint, sqlc, mockery, goose, moon, melange, - apko, cosign), resolving "command not found"/PATH problems, fixing locked/trust - failures, or wiring mise into moon, the CI workflow, or the local container tasks. + Operate mise as the source of truth for tool versions and integrity in + template-go-api. Use when changing mise.toml or mise.lock, updating Go or a + development/release CLI, resolving PATH or trust failures, or maintaining the + local image tasks. --- # mise -mise owns the lifecycle of every pinned tool and the project's tool-related env in -this repo. It replaced Proto (`.prototools`, `.moon/proto/*`) and the bespoke -`.moon/proto/sqlc.sha256` digest. Treat `mise.toml` + `mise.lock` as the only place -a toolchain version is declared; everything else (moon, CI, the container build) -consumes what mise puts on PATH. +mise provisions the language runtimes and CLIs used by local development, Moon, +CI, the reusable release unit, and the local image build. Treat `mise.toml` and +`mise.lock` as one reviewed unit. Do not declare a second version in Moon or +install a project tool outside mise. -## Verified against +## Verified contract -- `mise 2026.6.14` (`macos-arm64`), grounded in the captured `--help` for - `install/use/ls/lock/exec/run/trust/outdated/upgrade/settings/current/activate/env/which` - and `mise doctor --help`, plus this repo's `mise.toml`, `mise.lock`, `moon.yml`, - `.moon/toolchains.yml`, `.github/workflows/ci.yml`, and `README.md`. -- Advice is grounded in the local CLI and these files, not memory. Re-verify on a - mise minor/major bump. +- mise is `2026.6.14` in the repository workflows. +- Release tools are Go `1.26.6`, GoReleaser `2.17.1`, GitHub CLI `2.97.0`, + Syft `1.51.0`, Cosign `3.1.3`, Melange `0.59.1`, and apko `1.2.37`. +- The guidance is grounded in `mise.toml`, `mise.lock`, `go.mod`, `moon.yml`, + `.github/workflows/{ci,release,security-scan}.yml`, `melange.yaml`, and + `apko.yaml`. + +The full-SHA-pinned `meigma/release` unit installs its matching `release-cli` +through the unit's sibling setup action. `release-cli` is not a separate +consumer pin in `mise.toml`. Do not add an independent CLI version: the selected +release-unit commit defines the workflows, setup action, CLI stamp, and checksum +signer identities together. ## Use this skill when -- Bumping or adding a tool, or reviewing a diff that touches `mise.toml`/`mise.lock`. -- A tool is missing from PATH, or `mise install` fails closed under `locked`. -- mise prompts for trust (commonly inside a `.wt/` worktree that nests under the repo). -- Explaining how moon, `ci.yml`, or `mise run image-local` get their binaries. - -## mise's lane (non-negotiables) - -mise manages **tool + env lifecycle only**, plus the two local container tasks -below. State these as rules: - -1. mise is **not the task runner and not the CI gate** — that is moon. Do not move - build/lint/test/codegen into mise tasks. -2. **Every tool an engineer needs goes through mise.** Never `go install`, - `go tool`, `brew install`, `apt`, `npm -g`, `pipx`, `cargo install`, or a manual - download for project tooling. Add it to `[tools]` and `mise lock` instead. -3. **Force the verifying backend.** Pin CLIs with an explicit `aqua:` (or `github:`) - ref, e.g. `"aqua:sqlc-dev/sqlc" = "1.31.1"`. A bare/asdf/npm/cargo/pipx backend - resolves without a recorded checksum — never let a tool land that way. -4. **Bump = edit `mise.toml`, then `mise lock`, then commit both together.** Never - hand-edit `mise.lock` (`# @generated`) and never commit one without the other. -5. The only mise *tasks* in this repo are `image-local` and `stack-up` (local - container conveniences). Do not add general-purpose tasks here. - -## How mise is wired here - -`mise.toml`: - -- `[tools]`: `go = "1.26.4"`, `python = "3.14.3"` (core backends), and nine CLIs - pinned via explicit `aqua:` refs (`golangci/golangci-lint`, `sqlc-dev/sqlc`, - `vektra/mockery`, `pressly/goose`, `astral-sh/uv`, `moonrepo/moon`, - `chainguard-dev/melange`, `chainguard-dev/apko`, `sigstore/cosign`). -- `[env] GOTOOLCHAIN = "local"`: never auto-download a Go toolchain other than the - pinned one; matches `go.mod`'s `go 1.26.4`. mise `[env]` is **not** carried by the - CI action's shims, so `ci.yml` also sets `GOTOOLCHAIN: local` at job level — keep - both in sync. -- `[settings] lockfile = true` (read/write `mise.lock`) and `locked = true` (the - integrity gate; equivalent to the `--locked` flag / `MISE_LOCKED=1`). - -moon consumes mise, it does not duplicate it: `.moon/toolchains.yml` declares no -language toolchain and `moon.yml` sets `toolchains.default: system`, so every moon -task command is a bare binary (`go`, `golangci-lint`, `sqlc`, `mockery`) resolved -from PATH. `moon.yml` also lists `mise.toml` + `mise.lock` as inputs of -build/openapi/sqlc/mockery/lint, so a tool bump re-triggers those tasks (and -invalidates the result cache of the cacheable ones — build and openapi; -sqlc/mockery/lint already run with `cache: false`). See the `worktrunk` skill for worktree mechanics and the `melange`/`apko` -skills for the container build those pinned tools feed. - -CI (`.github/workflows/ci.yml`) installs via -`jdx/mise-action@… with: version: 2026.6.14, cache: true`. The action installs -every tool from `mise.toml` honoring `mise.lock` (locked → fail closed), including -`moon`, and prepends the shim dir to PATH so moon's `system` tasks find the -binaries. CI uses mise-action, **not** `moonrepo/setup-toolchain`. - -## The lockfile, precisely - -`mise.lock` is `# @generated`. Per tool it records a `[[tools.""]]` block -(`version`, `backend`) and one `[tools.""."platforms."]` table for each -of the four platforms: `linux-x64`, `linux-arm64`, `macos-x64`, `macos-arm64`. - -- Every platform entry carries a `url`. **`locked = true` requires a pre-resolved - `url` per platform** and fails closed otherwise (per `mise install --help`: it - prevents API calls to GitHub/aqua at install time). -- Most entries also carry `checksum = "sha256:…"`, which is enforced. One exception - in this repo: **`aqua:sqlc-dev/sqlc` has a `url` but no `checksum`** on any - platform, because sqlc publishes no upstream checksum — this is exactly why the - old committed `.moon/proto/sqlc.sha256` existed and could be retired. Do not treat - a missing sqlc checksum as a bug to "fix". -- A subset additionally records a `provenance` field, reflecting the verification - the aqua registry applies for that tool: `provenance = "github-attestations"` on - `uv`, `golangci-lint`, and `python`; `provenance = "cosign"` on `cosign`. The - remaining tools (`go`, `melange`, `apko`, `moon`, `goose`, `mockery`, `sqlc`) - carry no `provenance` field. Do **not** claim every tool is attestation-verified; - the always-on guarantees are the pinned `url` and (where present) the `checksum`. - -## Bumping a tool (the canonical operation) +- Bumping or adding a tool in `mise.toml` and `mise.lock`. +- Diagnosing a missing tool, an unexpected executable on PATH, or a locked + installation failure. +- Maintaining `image-local` or `stack-up`. +- Explaining how Moon, CI, or the reusable release workflows obtain tools. -```bash -# 1. edit the version in mise.toml (keep the aqua: ref) -# 2. re-resolve url/checksum for all four platforms -mise lock --platform linux-x64,linux-arm64,macos-x64,macos-arm64 -# 3. commit mise.toml + mise.lock together -``` +## Ownership rules + +1. mise manages tool and environment lifecycle. Moon remains the development + task runner and CI gate. +2. Add project tools with an explicit verifying backend such as `aqua:`. Do not + use `go install`, Homebrew, npm globals, pipx, Cargo, or manual downloads for + repository tooling. +3. Edit `mise.toml`, run `mise lock` for all four supported platforms, and + commit `mise.toml` and the generated `mise.lock` together. +4. Never hand-edit `mise.lock`. +5. Keep general build, format, lint, test, and code-generation tasks in + `moon.yml`. The only mise tasks are the container conveniences + `image-local` and `stack-up`. + +## Repository wiring + +`mise.toml` pins: + +- Go `1.26.6` and Python `3.14.3`; +- `golangci-lint`, sqlc, mockery, goose, uv, and Moon for development; and +- GoReleaser `2.17.1`, GitHub CLI `2.97.0`, Syft `1.51.0`, Cosign `3.1.3`, + Melange `0.59.1`, and apko `1.2.37` for the release and image paths. + +`GOTOOLCHAIN=local` prevents Go from downloading a different toolchain and must +match the `go 1.26.6` directive in `go.mod`. The CI job also sets this +environment because mise-action shims do not propagate the repository's +`[env]` block into that workflow context. + +Moon uses `toolchains.default: system`, so its commands resolve the executables +that mise places on PATH. `moon.yml` includes `mise.toml` and `mise.lock` in the +relevant input groups so tool changes invalidate affected task results. + +The normal CI workflow uses `jdx/mise-action` to install the locked toolset. +Reusable release workflows consume the same consumer pins but use the +full-SHA-selected `release-cli` from `meigma/release`. + +## Lockfile behavior + +`mise.lock` is generated. Each tool records a version, backend, and platform +entry for: -- `mise outdated` (add `--bump` to see latest across major lines, `-J` for JSON) - shows what could move before you decide. -- `mise upgrade --bump` is the one-shot equivalent (edits `mise.toml` and - re-locks), but the repo's committed convention is the explicit edit + `mise lock` - so the version change is a reviewable diff. -- After locking, confirm all four platform tables are present for each changed tool - before committing; do not ship a partial lock entry. +- `linux-x64`; +- `linux-arm64`; +- `macos-x64`; and +- `macos-arm64`. -## Adding a tool +`settings.locked = true` requires a pre-resolved URL for the current platform. +Checksums and provenance fields depend on the selected backend and upstream +release metadata. Do not claim every tool has provenance verification. In this +repository, sqlc has locked URLs but no upstream checksum; that is expected. -1. Add `"aqua:/" = ""` (or another verifying backend) to - `[tools]` in `mise.toml`. -2. `mise lock --platform linux-x64,linux-arm64,macos-x64,macos-arm64` to populate - url/checksum for all platforms. -3. If a moon task uses it, add it to that task's input fileGroup as appropriate; - `mise.toml`/`mise.lock` are already inputs of the main task groups. -4. `mise install` locally to materialize it, then commit both files. +## Bump a tool -## Worktree trust gotcha +1. Edit the exact version in `mise.toml` without changing its backend. +2. Re-resolve every supported platform: -`.wt/` worktrees nest **under** the repo, so mise's upward config search loads both -the worktree's config and the parent repo's `mise.toml`. When mise prompts, trust -both: + ```bash + mise lock --platform linux-x64,linux-arm64,macos-x64,macos-arm64 + ``` + +3. Confirm the changed tool has four platform entries. +4. Commit `mise.toml` and `mise.lock` together. + +Use `mise outdated` to inspect available updates. `mise upgrade --bump` +can edit and lock in one operation, but the explicit edit plus `mise lock` +sequence produces the repository's expected review shape. + +## Local image tasks + +`mise run image-local` is the supported local build. It: + +1. resolves the host Go architecture and maps it to the corresponding APK + architecture; +2. compiles a static Linux binary with development version, commit, and date + values into `.image-local/application`; +3. asks Melange to package that staged file as `template-go-api`; +4. asks apko to assemble the host-architecture image; and +5. loads and retags it as `template-go-api:dev`. + +The task passes Melange `--source-dir .image-local`, `--out-dir packages`, and +`--vars-file .melange-vars.local.json`. It passes apko +`--repository-append ./packages` and `--keyring-append ./melange.rsa.pub`. +Melange does not compile Go. + +`mise run stack-up` depends on `image-local` and then runs +`docker compose up`. Docker must be running because Melange uses its Docker +runner and apko produces an image tarball for Docker. + +The gitignored local outputs are `.image-local/`, +`.melange-vars.local.json`, `melange*.rsa*`, `packages/`, and `image.tar`. +Do not commit them. + +## Worktree trust + +Worktrees under `.wt/` can cause mise to discover both the worktree config and +the parent checkout config. Trust the current directory and its parents, then +inspect the result: ```bash -mise trust --all # trust this dir and its parents -mise trust --show # inspect trust status without changing it +mise trust --all +mise trust --show ``` -The main checkout `/Users/josh/code/meigma/template-go-api` is already trusted. - -## Inspection / read-only ops +## Inspection ```bash -mise ls # installed + active tool versions (-J for JSON) -mise current # active versions only, script-friendly -mise which sqlc # resolved bin path; --version for just the version -mise outdated # what could bump -mise doctor # diagnose install/PATH problems (doctor path prints PATH) -mise exec -- sqlc version # run a pinned tool ad hoc, no shell activation +mise ls +mise current +mise which goreleaser +mise outdated +mise doctor +mise exec -- goreleaser --version ``` -## Gotchas - -- `mise install` installs but does **not** activate — tools are not on PATH until - `mise activate` runs in the shell, or you go through `mise exec` / `mise run` / - shims. CI relies on mise-action prepending the shim dir; locally use - `eval "$(mise activate zsh)"` once, or prefix one-off commands with `mise exec --`. -- `mise.local.toml` / `.mise.local.toml` are gitignored per-developer overrides. - Never commit them and never put project pins there — project pins belong in the - committed `mise.toml`. -- `mise.toml` and `mise.lock` are committed and authoritative; the gitignored - `melange*.rsa*`, `melange-vars.yaml`, `.melange-vars.local.yaml`, and the - `packages/`/`image.tar` artifacts come from `mise run image-local` and must stay - uncommitted (see the `melange`/`apko` skills). -- `[tasks.image-local]` passes `--runner docker` to melange (melange needs a Linux - build sandbox). `[tasks.stack-up]` does not call melange itself — it `depends` on - `image-local`, then runs `docker compose up`. Either way Docker must be running on - macOS. +`mise install` installs tools but does not activate them in the current shell. +Use shell activation, `mise exec --`, `mise run`, or mise shims. ## Command reference -See [references/mise-commands.md](references/mise-commands.md) for the version-stamped -command and flag map. +See [references/mise-commands.md](references/mise-commands.md) for the +version-stamped command and flag map. diff --git a/.agents/skills/mise/references/mise-commands.md b/.agents/skills/mise/references/mise-commands.md index a6c071d..269cdb8 100644 --- a/.agents/skills/mise/references/mise-commands.md +++ b/.agents/skills/mise/references/mise-commands.md @@ -36,7 +36,7 @@ Usage: ```bash mise install # install everything in mise.toml (honors mise.lock) -mise install go@1.26.4 # install a specific version +mise install go@1.26.6 # install a specific version ``` Flags that matter here: @@ -129,7 +129,7 @@ Usage: ```bash mise exec -- sqlc version -mise exec go@1.26.4 -- go version # override one tool ad hoc +mise exec go@1.26.6 -- go version # override one tool ad hoc ``` Flags that matter here: diff --git a/.github/scripts/configure_github_repo.py b/.github/scripts/configure_github_repo.py index 9245f83..73441aa 100644 --- a/.github/scripts/configure_github_repo.py +++ b/.github/scripts/configure_github_repo.py @@ -173,7 +173,7 @@ def create_ruleset(self, owner: str, repo: str, payload: dict[str, Any]) -> dict return self._request_json("POST", f"/repos/{owner}/{repo}/rulesets", payload, expected_statuses={201}) def update_ruleset(self, owner: str, repo: str, ruleset_id: int, payload: dict[str, Any]) -> dict[str, Any]: - return self._request_json("PATCH", f"/repos/{owner}/{repo}/rulesets/{ruleset_id}", payload) + return self._request_json("PUT", f"/repos/{owner}/{repo}/rulesets/{ruleset_id}", payload) def get_pages_site(self, owner: str, repo: str) -> dict[str, Any] | None: try: diff --git a/.github/scripts/stage_ghd_release_assets.py b/.github/scripts/stage_ghd_release_assets.py deleted file mode 100644 index 64e6210..0000000 --- a/.github/scripts/stage_ghd_release_assets.py +++ /dev/null @@ -1,338 +0,0 @@ -#!/usr/bin/env python3 -"""Stage and validate ghd release assets generated by GoReleaser.""" - -from __future__ import annotations - -import argparse -import hashlib -import json -import os -import shutil -import sys -import tomllib -from pathlib import Path -from typing import Any - - -EXPECTED_PLATFORMS = ( - ("darwin", "amd64"), - ("darwin", "arm64"), - ("linux", "amd64"), - ("linux", "arm64"), -) -EXPECTED_ASSET_COUNT = 9 - - -class StageError(RuntimeError): - """Raised when release asset staging cannot continue.""" - - -def parse_args(argv: list[str] | None = None) -> argparse.Namespace: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--tag", required=True, help="Release tag, for example v1.2.3") - parser.add_argument("--artifacts", default=Path("dist/artifacts.json"), type=Path) - parser.add_argument("--config", default=Path("ghd.toml"), type=Path) - parser.add_argument("--output", default=Path("dist/release-assets"), type=Path) - parser.add_argument("--binary-name", default="template-go-api") - return parser.parse_args(argv) - - -def main(argv: list[str] | None = None) -> int: - args = parse_args(argv) - try: - stage_release_assets( - tag=args.tag, - artifacts_path=args.artifacts, - config_path=args.config, - output_dir=args.output, - binary_name=args.binary_name, - ) - except StageError as exc: - print(f"error: {exc}", file=sys.stderr) - return 1 - return 0 - - -def stage_release_assets( - *, - tag: str, - artifacts_path: Path, - config_path: Path, - output_dir: Path, - binary_name: str, -) -> None: - version = release_version(tag) - repository = os.environ.get("GITHUB_REPOSITORY") - if not repository: - raise StageError("GITHUB_REPOSITORY must be set") - - config = load_toml(config_path) - validate_ghd_config(config, binary_name=binary_name, repository=repository) - - artifacts = load_artifacts(artifacts_path) - stage_artifacts(artifacts, output_dir=output_dir, binary_name=binary_name) - - expected_binaries = verify_expected_assets( - output_dir=output_dir, - binary_name=binary_name, - version=version, - ) - verify_checksums(output_dir, expected_binaries) - - for path in sorted(path for path in output_dir.iterdir() if path.is_file()): - print(path.as_posix()) - - -def release_version(tag: str) -> str: - if not tag.startswith("v") or tag == "v": - raise StageError(f"could not resolve release version from {tag}") - return tag[1:] - - -def load_toml(path: Path) -> dict[str, Any]: - try: - with path.open("rb") as config_file: - data = tomllib.load(config_file) - except FileNotFoundError as exc: - raise StageError(f"missing ghd config {path}") from exc - except tomllib.TOMLDecodeError as exc: - raise StageError(f"invalid TOML in {path}: {exc}") from exc - - if not isinstance(data, dict): - raise StageError(f"ghd config {path} must be a TOML table") - return data - - -def load_artifacts(path: Path) -> list[dict[str, Any]]: - try: - data = json.loads(path.read_text(encoding="utf-8")) - except FileNotFoundError as exc: - raise StageError(f"missing GoReleaser artifacts file {path}") from exc - except json.JSONDecodeError as exc: - raise StageError(f"invalid JSON in {path}: {exc}") from exc - - if not isinstance(data, list): - raise StageError(f"{path} must contain a JSON array") - - artifacts: list[dict[str, Any]] = [] - for index, item in enumerate(data): - if not isinstance(item, dict): - raise StageError(f"{path} entry {index} must be an object") - artifacts.append(item) - return artifacts - - -def validate_ghd_config(config: dict[str, Any], *, binary_name: str, repository: str) -> None: - provenance = config.get("provenance") - if not isinstance(provenance, dict): - raise StageError("ghd.toml must define a [provenance] table") - - expected_signer = f"{repository}/.github/workflows/attest.yml" - actual_signer = provenance.get("signer_workflow") - if actual_signer != expected_signer: - raise StageError( - f"ghd.toml signer_workflow must be {expected_signer}, got {actual_signer}" - ) - - package = find_package(config, binary_name) - if package.get("tag_pattern") != "v${version}": - raise StageError( - f"ghd.toml package {binary_name!r} must use tag_pattern = \"v${{version}}\"" - ) - - binaries = package.get("binaries") - if not isinstance(binaries, list) or not any( - isinstance(binary, dict) and binary.get("path") == binary_name for binary in binaries - ): - raise StageError( - f"ghd.toml package {binary_name!r} must include binary path {binary_name!r}" - ) - - assets = package.get("assets") - if not isinstance(assets, list): - raise StageError(f"ghd.toml package {binary_name!r} must include assets") - - actual_patterns = { - asset.get("pattern") - for asset in assets - if isinstance(asset, dict) and isinstance(asset.get("pattern"), str) - } - expected_patterns = { - f"{binary_name}_${{version}}_{goos}_{goarch}" for goos, goarch in EXPECTED_PLATFORMS - } - missing_patterns = sorted(expected_patterns - actual_patterns) - if missing_patterns: - raise StageError(f"ghd.toml is missing expected asset pattern(s): {', '.join(missing_patterns)}") - - -def find_package(config: dict[str, Any], binary_name: str) -> dict[str, Any]: - packages = config.get("packages") - if not isinstance(packages, list): - raise StageError("ghd.toml must define at least one [[packages]] entry") - - matches = [ - package - for package in packages - if isinstance(package, dict) and package.get("name") == binary_name - ] - if len(matches) != 1: - raise StageError(f"ghd.toml must define exactly one package named {binary_name!r}") - - return matches[0] - - -def stage_artifacts( - artifacts: list[dict[str, Any]], - *, - output_dir: Path, - binary_name: str, -) -> None: - output_dir.mkdir(parents=True, exist_ok=True) - asset_prefix = f"{binary_name}_" - staged_names: set[str] = set() - - for artifact in artifacts: - artifact_type = artifact.get("type") - artifact_name = artifact.get("name") - artifact_path = artifact.get("path") - - if not isinstance(artifact_type, str) or not isinstance(artifact_name, str): - continue - - destination_name: str | None = None - executable = False - if artifact_type == "Binary" and artifact_name.startswith(asset_prefix): - destination_name = artifact_name - executable = True - elif artifact_type == "SBOM" and artifact_name.startswith(asset_prefix): - if not isinstance(artifact_path, str): - raise StageError(f"SBOM artifact {artifact_name!r} is missing a path") - destination_name = Path(artifact_path).name - elif artifact_type == "Checksum" and artifact_name == "checksums.txt": - destination_name = "checksums.txt" - - if destination_name is None: - continue - if not isinstance(artifact_path, str): - raise StageError(f"{artifact_type} artifact {artifact_name!r} is missing a path") - if destination_name in staged_names: - raise StageError(f"duplicate staged release asset name {destination_name!r}") - - source = Path(artifact_path) - if not source.is_absolute(): - source = Path.cwd() / source - if not source.is_file(): - raise StageError(f"missing GoReleaser artifact {artifact_path}") - - destination = output_dir / destination_name - shutil.copy2(source, destination) - if executable: - destination.chmod(0o755) - staged_names.add(destination_name) - - -def verify_expected_assets(*, output_dir: Path, binary_name: str, version: str) -> set[str]: - expected_files = {"checksums.txt"} - expected_binaries: set[str] = set() - for goos, goarch in EXPECTED_PLATFORMS: - binary = f"{binary_name}_{version}_{goos}_{goarch}" - sbom = f"{binary}.sbom.json" - expected_files.update({binary, sbom}) - expected_binaries.add(binary) - - binary_path = output_dir / binary - if not binary_path.is_file(): - raise StageError(f"missing expected binary asset {binary}") - if not os.access(binary_path, os.X_OK): - raise StageError(f"expected binary asset is not executable: {binary}") - - sbom_path = output_dir / sbom - if not sbom_path.is_file(): - raise StageError(f"missing expected SBOM asset {sbom}") - - staged_files = {path.name for path in output_dir.iterdir() if path.is_file()} - if "checksums.txt" not in staged_files: - raise StageError("missing staged checksums.txt") - - if len(staged_files) != EXPECTED_ASSET_COUNT: - staged = ", ".join(sorted(staged_files)) - raise StageError( - f"expected {EXPECTED_ASSET_COUNT} draft release assets, " - f"found {len(staged_files)}: {staged}" - ) - - unexpected_files = sorted(staged_files - expected_files) - if unexpected_files: - raise StageError(f"unexpected staged release asset(s): {', '.join(unexpected_files)}") - - return expected_binaries - - -def verify_checksums(output_dir: Path, expected_binaries: set[str]) -> None: - checksums = parse_checksums(output_dir / "checksums.txt") - missing_checksums = sorted(expected_binaries - checksums.keys()) - if missing_checksums: - raise StageError(f"missing checksum entry for expected binary asset(s): {', '.join(missing_checksums)}") - - for name, expected_digest in sorted(checksums.items()): - path = output_dir / name - if not path.is_file(): - raise StageError(f"checksum references unstaged file {name}") - - actual_digest = sha256_file(path) - if actual_digest != expected_digest.lower(): - raise StageError( - f"checksum mismatch for {name}: expected {expected_digest}, got {actual_digest}" - ) - - -def parse_checksums(path: Path) -> dict[str, str]: - try: - lines = path.read_text(encoding="utf-8").splitlines() - except FileNotFoundError as exc: - raise StageError("missing staged checksums.txt") from exc - - checksums: dict[str, str] = {} - for line_number, line in enumerate(lines, start=1): - stripped = line.strip() - if not stripped: - continue - - parts = stripped.split(None, 1) - if len(parts) != 2: - raise StageError(f"invalid checksums.txt line {line_number}: {line}") - - digest, raw_name = parts - name = checksum_file_name(raw_name) - if len(digest) != 64 or any(character not in "0123456789abcdefABCDEF" for character in digest): - raise StageError(f"invalid sha256 digest for {name} on checksums.txt line {line_number}") - if name in checksums: - raise StageError(f"duplicate checksum entry for {name}") - checksums[name] = digest - - if not checksums: - raise StageError("checksums.txt does not contain any checksum entries") - return checksums - - -def checksum_file_name(raw_name: str) -> str: - name = raw_name.strip() - if name.startswith("*"): - name = name[1:] - - path = Path(name) - if path.is_absolute() or path.name != name or ".." in path.parts: - raise StageError(f"checksum entry must name a staged file, got {raw_name!r}") - return name - - -def sha256_file(path: Path) -> str: - digest = hashlib.sha256() - with path.open("rb") as source: - for chunk in iter(lambda: source.read(1024 * 1024), b""): - digest.update(chunk) - return digest.hexdigest() - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.github/scripts/test_configure_github_repo.py b/.github/scripts/test_configure_github_repo.py index cdca893..64e9683 100644 --- a/.github/scripts/test_configure_github_repo.py +++ b/.github/scripts/test_configure_github_repo.py @@ -160,5 +160,34 @@ def test_apply_create_pages_runs_follow_up_update(self) -> None: self.assertEqual(api.updated_pages, [{"https_enforced": True}]) +class ConfigureGitHubRepoRulesetTest(unittest.TestCase): + def test_update_ruleset_uses_put(self) -> None: + api = configure.GitHubApi("token") + captured: dict[str, Any] = {} + + def capture( + method: str, + path: str, + payload: dict[str, Any] | None = None, + expected_statuses: set[int] | None = None, + ) -> dict[str, Any]: + del expected_statuses + captured["method"] = method + captured["path"] = path + captured["payload"] = payload + return {"id": 7} + + api._request_json = capture # type: ignore[method-assign] + payload = {"name": "tags"} + + result = api.update_ruleset("meigma", "template-go-api", 7, payload) + + self.assertEqual(result, {"id": 7}) + self.assertEqual(captured["method"], "PUT") + self.assertEqual(captured["path"], "/repos/meigma/template-go-api/rulesets/7") + self.assertEqual(captured["payload"], payload) + + + if __name__ == "__main__": unittest.main() diff --git a/.github/scripts/test_stage_ghd_release_assets.py b/.github/scripts/test_stage_ghd_release_assets.py deleted file mode 100644 index d938ced..0000000 --- a/.github/scripts/test_stage_ghd_release_assets.py +++ /dev/null @@ -1,246 +0,0 @@ -from __future__ import annotations - -import contextlib -import hashlib -import importlib.util -import io -import os -import stat -import tempfile -import unittest -from pathlib import Path - - -SCRIPT_PATH = Path(__file__).with_name("stage_ghd_release_assets.py") -SPEC = importlib.util.spec_from_file_location("stage_ghd_release_assets", SCRIPT_PATH) -assert SPEC is not None -assert SPEC.loader is not None -stage_ghd_release_assets = importlib.util.module_from_spec(SPEC) -SPEC.loader.exec_module(stage_ghd_release_assets) - - -PLATFORMS = ( - ("darwin", "amd64"), - ("darwin", "arm64"), - ("linux", "amd64"), - ("linux", "arm64"), -) - - -@contextlib.contextmanager -def working_directory(path: Path): - original = Path.cwd() - os.chdir(path) - try: - yield - finally: - os.chdir(original) - - -@contextlib.contextmanager -def github_repository(value: str): - original = os.environ.get("GITHUB_REPOSITORY") - os.environ["GITHUB_REPOSITORY"] = value - try: - yield - finally: - if original is None: - os.environ.pop("GITHUB_REPOSITORY", None) - else: - os.environ["GITHUB_REPOSITORY"] = original - - -class StageGhdReleaseAssetsTest(unittest.TestCase): - def test_stages_expected_assets(self) -> None: - with fixture() as root: - result, stdout, stderr = run_script(root) - - self.assertEqual(result, 0, stderr) - staged = sorted(path.name for path in (root / "dist/release-assets").iterdir()) - self.assertEqual( - staged, - [ - "checksums.txt", - "template-go-api_1.2.3_darwin_amd64", - "template-go-api_1.2.3_darwin_amd64.sbom.json", - "template-go-api_1.2.3_darwin_arm64", - "template-go-api_1.2.3_darwin_arm64.sbom.json", - "template-go-api_1.2.3_linux_amd64", - "template-go-api_1.2.3_linux_amd64.sbom.json", - "template-go-api_1.2.3_linux_arm64", - "template-go-api_1.2.3_linux_arm64.sbom.json", - ], - ) - linux_binary = root / "dist/release-assets/template-go-api_1.2.3_linux_amd64" - mode = linux_binary.stat().st_mode - self.assertTrue(mode & stat.S_IXUSR) - self.assertIn("dist/release-assets/template-go-api_1.2.3_linux_arm64", stdout) - - def test_fails_on_missing_checksum_entry(self) -> None: - with fixture(missing_checksum="template-go-api_1.2.3_linux_arm64") as root: - result, _, stderr = run_script(root) - - self.assertEqual(result, 1) - self.assertIn("missing checksum entry", stderr) - self.assertIn("template-go-api_1.2.3_linux_arm64", stderr) - - def test_fails_on_checksum_mismatch(self) -> None: - override = ("template-go-api_1.2.3_linux_amd64", "0" * 64) - with fixture(checksum_override=override) as root: - result, _, stderr = run_script(root) - - self.assertEqual(result, 1) - self.assertIn("checksum mismatch for template-go-api_1.2.3_linux_amd64", stderr) - - def test_fails_on_wrong_signer_workflow(self) -> None: - with fixture(signer="other/repo/.github/workflows/release.yml") as root: - result, _, stderr = run_script(root) - - self.assertEqual(result, 1) - self.assertIn("signer_workflow", stderr) - - def test_fails_on_missing_os_arch_asset(self) -> None: - with fixture(omit_artifact=("linux", "arm64", "Binary")) as root: - result, _, stderr = run_script(root) - - self.assertEqual(result, 1) - self.assertIn("missing expected binary asset template-go-api_1.2.3_linux_arm64", stderr) - - def test_fails_on_unexpected_asset_count(self) -> None: - with fixture(extra_binary=True) as root: - result, _, stderr = run_script(root) - - self.assertEqual(result, 1) - self.assertIn("expected 9 draft release assets, found 10", stderr) - - -def run_script(root: Path) -> tuple[int, str, str]: - stdout = io.StringIO() - stderr = io.StringIO() - with working_directory(root), github_repository("meigma/template-go-api"): - with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr): - result = stage_ghd_release_assets.main(["--tag", "v1.2.3"]) - return result, stdout.getvalue(), stderr.getvalue() - - -@contextlib.contextmanager -def fixture( - *, - signer: str = "meigma/template-go-api/.github/workflows/attest.yml", - missing_checksum: str | None = None, - checksum_override: tuple[str, str] | None = None, - omit_artifact: tuple[str, str, str] | None = None, - extra_binary: bool = False, -): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - (root / "dist").mkdir() - write_ghd_toml(root / "ghd.toml", signer) - - artifacts: list[dict[str, str]] = [] - checksum_entries: dict[str, str] = {} - for goos, goarch in PLATFORMS: - binary_name = f"template-go-api_1.2.3_{goos}_{goarch}" - sbom_name = f"{binary_name}.sbom.json" - - binary_path = root / "dist" / binary_name - binary_path.write_bytes(f"{binary_name}\n".encode()) - checksum_entries[binary_name] = sha256(binary_path) - if omit_artifact != (goos, goarch, "Binary"): - artifacts.append({ - "type": "Binary", - "name": binary_name, - "path": f"dist/{binary_name}", - }) - - sbom_path = root / "dist" / sbom_name - sbom_path.write_text(f'{{"name": "{sbom_name}"}}\n', encoding="utf-8") - if omit_artifact != (goos, goarch, "SBOM"): - artifacts.append({ - "type": "SBOM", - "name": binary_name, - "path": f"dist/{sbom_name}", - }) - - if extra_binary: - extra_name = "template-go-api_1.2.3_freebsd_amd64" - extra_path = root / "dist" / extra_name - extra_path.write_bytes(b"extra\n") - artifacts.append({"type": "Binary", "name": extra_name, "path": f"dist/{extra_name}"}) - - if checksum_override is not None: - checksum_entries[checksum_override[0]] = checksum_override[1] - if missing_checksum is not None: - checksum_entries.pop(missing_checksum) - - checksums_path = root / "dist/checksums.txt" - checksums_path.write_text( - "".join(f"{digest} {name}\n" for name, digest in sorted(checksum_entries.items())), - encoding="utf-8", - ) - artifacts.append({ - "type": "Checksum", - "name": "checksums.txt", - "path": "dist/checksums.txt", - }) - - artifacts_path = root / "dist/artifacts.json" - artifacts_path.write_text(format_artifacts_json(artifacts), encoding="utf-8") - yield root - - -def write_ghd_toml(path: Path, signer: str) -> None: - path.write_text( - f'''version = 1 - -[provenance] -signer_workflow = "{signer}" - -[[packages]] -name = "template-go-api" -description = "Meigma Go repository template starter CLI." -tag_pattern = "v${{version}}" - -[[packages.assets]] -os = "darwin" -arch = "amd64" -pattern = "template-go-api_${{version}}_darwin_amd64" - -[[packages.assets]] -os = "darwin" -arch = "arm64" -pattern = "template-go-api_${{version}}_darwin_arm64" - -[[packages.assets]] -os = "linux" -arch = "amd64" -pattern = "template-go-api_${{version}}_linux_amd64" - -[[packages.assets]] -os = "linux" -arch = "arm64" -pattern = "template-go-api_${{version}}_linux_arm64" - -[[packages.binaries]] -path = "template-go-api" -''', - encoding="utf-8", - ) - - -def sha256(path: Path) -> str: - return hashlib.sha256(path.read_bytes()).hexdigest() - - -def format_artifacts_json(artifacts: list[dict[str, str]]) -> str: - lines = ["[\n"] - for index, artifact in enumerate(artifacts): - suffix = "," if index < len(artifacts) - 1 else "" - fields = ", ".join(f'"{key}": "{value}"' for key, value in artifact.items()) - lines.append(f" {{{fields}}}{suffix}\n") - lines.append("]\n") - return "".join(lines) - - -if __name__ == "__main__": - unittest.main() diff --git a/.github/workflows/attest.yml b/.github/workflows/attest.yml deleted file mode 100644 index 3a36c23..0000000 --- a/.github/workflows/attest.yml +++ /dev/null @@ -1,84 +0,0 @@ -# Reusable workflow that generates artifact PROVENANCE in isolation from the build -# jobs that call it. A reusable workflow runs in its own execution context with its -# own OIDC identity — the caller can inject neither steps nor secrets — so the -# signing material is unreachable by the build's user-defined steps. That isolation -# is the SLSA Build L3 requirement; GitHub artifact attestations generated in-job -# are only L2. Provenance is still written to GitHub's attestation API, so -# `gh attestation verify` keeps working — but the signer-workflow is now this file: -# -# gh attestation verify --repo \ -# --signer-workflow /.github/workflows/attest.yml --source-ref refs/tags/ -# -# SLSA note: L3 makes the signed provenance unforgeable; it does not, by itself, -# make the build hermetic. The build job still computes the checksums/digest it -# passes in — L3's guarantee is that the signature cannot be tampered with because -# the key lives only in this isolated workflow. - -name: Attest (reusable) - -on: - workflow_call: - inputs: - checksums-artifact: - description: Name of an uploaded artifact containing checksums.txt (binary provenance). - type: string - required: false - default: '' - subject-name: - description: Fully-qualified image name without tag/digest (container provenance). - type: string - required: false - default: '' - subject-digest: - description: Image digest in sha256:... form (container provenance). - type: string - required: false - default: '' - push-to-registry: - description: Attach the image provenance attestation to the registry. - type: boolean - required: false - default: false - -permissions: {} - -jobs: - attest: - name: Attest - runs-on: ubuntu-24.04 - permissions: - id-token: write # OIDC token minted HERE, isolated from the build job - attestations: write # write provenance to GitHub's attestation API - contents: read - packages: write # used only for the image attestation (push-to-registry) - steps: - - name: Download checksums - if: ${{ inputs.checksums-artifact != '' }} - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: ${{ inputs.checksums-artifact }} - - - name: Attest binary checksums - if: ${{ inputs.checksums-artifact != '' }} - uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0 - with: - subject-checksums: checksums.txt - - # The image provenance is attached to the registry as a referrer, so this - # isolated job needs its own GHCR login — the build job's docker login does - # not carry into the reusable workflow's separate runner. - - name: Log in to GitHub Container Registry - if: ${{ inputs.subject-digest != '' }} - uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ github.token }} - - - name: Attest image provenance - if: ${{ inputs.subject-digest != '' }} - uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 - with: - subject-name: ${{ inputs.subject-name }} - subject-digest: ${{ inputs.subject-digest }} - push-to-registry: ${{ inputs.push-to-registry }} diff --git a/.github/workflows/release-dry-run.yml b/.github/workflows/release-dry-run.yml deleted file mode 100644 index 4961c73..0000000 --- a/.github/workflows/release-dry-run.yml +++ /dev/null @@ -1,254 +0,0 @@ -# This workflow rehearses the release paths without uploading GitHub release -# assets or publishing/signing container images. It is intended for release PRs -# and manual checks before changing release automation. -# -# Keep the pull_request trigger broad so required check contexts still report -# on normal PRs. The jobs intentionally skip unless the PR head branch is a -# Release Please branch, which avoids spending runner time on routine changes. - -name: Release Dry Run - -on: - pull_request: - branches: - - master - workflow_dispatch: - -permissions: {} - -jobs: - binary-release-dry-run: - name: Binary Release Dry Run - if: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please--') }} - runs-on: ubuntu-24.04 - permissions: - contents: read - steps: - - name: Check out repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 - with: - go-version-file: go.mod - cache: true - - - name: Install Syft - uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 - - - name: Create synthetic release tag - env: - DRY_RUN_TAG: v0.0.0-dryrun.${{ github.run_id }}.${{ github.run_attempt }} - run: | - git tag -f "$DRY_RUN_TAG" - - - name: Run GoReleaser without publishing - uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3 - with: - distribution: goreleaser - version: '~> v2' - args: release --clean --skip=publish - env: - GITHUB_TOKEN: ${{ github.token }} - GORELEASER_CURRENT_TAG: v0.0.0-dryrun.${{ github.run_id }}.${{ github.run_attempt }} - - - name: Validate ghd-compatible dry-run artifacts - run: | - set -euo pipefail - - version="0.0.0-dryrun.${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT}" - binary_name="template-go-api" - asset_prefix="${binary_name}_" - expected_signer="${GITHUB_REPOSITORY}/.github/workflows/attest.yml" - actual_signer="$(awk -F'"' '/signer_workflow/ { print $2; exit }' ghd.toml)" - - if [ "$actual_signer" != "$expected_signer" ]; then - echo "ghd.toml signer_workflow must be $expected_signer, got $actual_signer" >&2 - exit 1 - fi - - grep -Fq "name = \"$binary_name\"" ghd.toml - grep -Fq "tag_pattern = \"v\${version}\"" ghd.toml - grep -Fq "path = \"$binary_name\"" ghd.toml - - jq -e --arg asset_prefix "$asset_prefix" ' - [ - .[] - | select( - (.type == "Binary" and (.name | startswith($asset_prefix))) or - (.type == "SBOM" and (.name | startswith($asset_prefix))) or - (.type == "Checksum" and .name == "checksums.txt") - ) - ] as $artifacts - | ($artifacts | map(select(.type == "Binary")) | length) == 4 - and ($artifacts | map(select(.type == "SBOM")) | length) == 4 - and ($artifacts | map(select(.type == "Checksum")) | length) == 1 - ' dist/artifacts.json - - test -f dist/checksums.txt - - for os in darwin linux; do - for arch in amd64 arm64; do - expected="${binary_name}_${version}_${os}_${arch}" - expected_sbom="${expected}.sbom.json" - expected_pattern="pattern = \"${binary_name}_\${version}_${os}_${arch}\"" - - jq -e --arg name "$expected" '.[] | select(.type == "Binary" and .name == $name)' dist/artifacts.json >/dev/null - jq -e --arg name "$expected_sbom" '.[] | select(.type == "SBOM" and .name == $name)' dist/artifacts.json >/dev/null - grep -Fq "$expected_pattern" ghd.toml - awk -v name="$expected" '$2 == name && length($1) == 64 && $1 ~ /^[[:xdigit:]]+$/ { found = 1 } END { exit(found ? 0 : 1) }' dist/checksums.txt - done - done - - - name: Smoke test dry-run binary - run: | - set -euo pipefail - - version="0.0.0-dryrun.${GITHUB_RUN_ID}.${GITHUB_RUN_ATTEMPT}" - binary_name="template-go-api" - host_os="$(go env GOHOSTOS)" - host_arch="$(go env GOHOSTARCH)" - expected="${binary_name}_${version}_${host_os}_${host_arch}" - bin="$(jq -r --arg name "$expected" '.[] | select(.type == "Binary" and .name == $name) | .path' dist/artifacts.json)" - - if [ -z "$bin" ] || [ "$bin" = "null" ] || [ ! -f "$bin" ]; then - echo "failed to locate host binary artifact $expected" >&2 - jq . dist/artifacts.json >&2 - exit 1 - fi - - output="$("$bin" --version)" - printf '%s\n' "$output" - - case "$output" in - "$binary_name "*"$version"*) ;; - *) echo "unexpected version output: $output" >&2; exit 1 ;; - esac - - # Rehearse the container build: build per-arch signed apks on native runners and - # assemble the image with apko, WITHOUT pushing, signing, or attesting. - melange-build-dry-run: - name: Melange Build Dry Run (${{ matrix.arch }}) - if: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please--') }} - runs-on: ${{ matrix.runner }} - permissions: - contents: read - strategy: - fail-fast: false - matrix: - include: - - arch: amd64 - runner: ubuntu-24.04 - apkdir: x86_64 - - arch: arm64 - runner: ubuntu-24.04-arm - apkdir: aarch64 - steps: - - name: Check out repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - fetch-depth: 0 - - - name: Setup mise - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 - with: - version: 2026.6.14 - cache: true - - - name: Resolve dry-run metadata - id: dry-run - env: - DRY_RUN_VERSION: 0.0.0-dryrun.${{ github.run_id }}.${{ github.run_attempt }} - run: | - set -euo pipefail - echo "version=${DRY_RUN_VERSION}" >> "$GITHUB_OUTPUT" - echo "commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - echo "date=$(git show -s --format=%cI HEAD)" >> "$GITHUB_OUTPUT" - - - name: Write melange vars file - env: - VERSION: ${{ steps.dry-run.outputs.version }} - COMMIT: ${{ steps.dry-run.outputs.commit }} - DATE: ${{ steps.dry-run.outputs.date }} - run: | - printf 'version: "%s"\ncommit: "%s"\ndate: "%s"\n' "$VERSION" "$COMMIT" "$DATE" > melange-vars.yaml - - - name: Generate ephemeral signing key - run: melange keygen "melange-${{ matrix.arch }}.rsa" - - - name: Build signed apk - run: | - melange build melange.yaml \ - --arch ${{ matrix.arch }} \ - --runner docker \ - --signing-key "melange-${{ matrix.arch }}.rsa" \ - --source-dir . \ - --vars-file melange-vars.yaml - - - name: Upload apk and public key - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: apk-${{ matrix.arch }} - path: | - packages/${{ matrix.apkdir }}/** - melange-${{ matrix.arch }}.rsa.pub - if-no-files-found: error - retention-days: 1 - - container-image-dry-run: - name: Container Image Dry Run - needs: - - melange-build-dry-run - if: ${{ always() && (github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please--')) }} - runs-on: ubuntu-24.04 - permissions: - contents: read - steps: - - name: Check melange dry-run result - env: - MELANGE_RESULT: ${{ needs.melange-build-dry-run.result }} - run: | - set -euo pipefail - - if [ "$MELANGE_RESULT" != "success" ]; then - echo "melange dry-run result was $MELANGE_RESULT" >&2 - exit 1 - fi - - - name: Check out repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - fetch-depth: 0 - - - name: Setup mise - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 - with: - version: 2026.6.14 - cache: true - - - name: Download per-arch apks and public keys - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - pattern: apk-* - path: . - merge-multiple: true - - - name: Assemble image and smoke test (no push) - run: | - set -euo pipefail - - apko build apko.yaml template-go-api:dry-run image.tar \ - --arch amd64 \ - --keyring-append ./melange-amd64.rsa.pub \ - --keyring-append ./melange-arm64.rsa.pub - - docker load < image.tar - docker tag template-go-api:dry-run-amd64 template-go-api:dry-run - - docker run --rm template-go-api:dry-run --version - docker run --rm template-go-api:dry-run openapi | grep -Fq "openapi: 3.0.3" diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 0810f44..34de2df 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -1,8 +1,7 @@ # Required release app settings: -# - vars.MEIGMA_RELEASE_APP_ID +# - vars.MEIGMA_RELEASE_APP_CLIENT_ID # - secrets.MEIGMA_RELEASE_APP_PRIVATE_KEY -# - protected-tag ruleset bypass for the release app, because this workflow -# creates protected v* tags after the release PR is merged. +# - protected-tag ruleset bypass for the release app, if protected v* tags are enabled. name: Release Please @@ -18,6 +17,7 @@ jobs: release-please: name: Release Please runs-on: ubuntu-24.04 + timeout-minutes: 10 permissions: contents: write pull-requests: write @@ -27,10 +27,11 @@ jobs: id: release-app uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: - app-id: ${{ vars.MEIGMA_RELEASE_APP_ID }} + client-id: ${{ vars.MEIGMA_RELEASE_APP_CLIENT_ID }} private-key: ${{ secrets.MEIGMA_RELEASE_APP_PRIVATE_KEY }} - name: Run Release Please + id: release uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 with: token: ${{ steps.release-app.outputs.token }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d47e4c4..1f6005d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,444 +1,77 @@ -# This workflow is tag-triggered by Release Please. It publishes binary release -# assets with GoReleaser, builds the container image with melange (signed apk) + -# apko (minimal multi-arch image), signs the image with keyless cosign, attests -# both artifact classes through GitHub-native attestations, and leaves draft -# publication as a human decision after inspection. - name: Release on: push: tags: - - v* - workflow_dispatch: - inputs: - tag: - description: Tag to release - required: true - type: string + - 'v*' permissions: {} -env: - IMAGE_NAME: ghcr.io/meigma/template-go-api +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false jobs: - resolve-release: - name: Resolve Release - runs-on: ubuntu-24.04 - permissions: - contents: write - outputs: - tag: ${{ steps.release-tag.outputs.tag }} - version: ${{ steps.release-tag.outputs.version }} - steps: - - name: Resolve release tag - id: release-tag - env: - DISPATCH_TAG: ${{ inputs.tag }} - run: | - set -euo pipefail - - if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then - tag="$DISPATCH_TAG" - else - tag="${GITHUB_REF_NAME}" - fi - - if [[ ! "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$ ]]; then - echo "Release tag must match vX.Y.Z or vX.Y.Z-prerelease: $tag" >&2 - exit 1 - fi - - version="${tag#v}" - - echo "tag=$tag" >> "$GITHUB_OUTPUT" - echo "version=$version" >> "$GITHUB_OUTPUT" - - - name: Wait for draft release - env: - GH_TOKEN: ${{ github.token }} - RELEASE_TAG: ${{ steps.release-tag.outputs.tag }} - run: | - for attempt in $(seq 1 30); do - if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --json isDraft --jq '.isDraft' 2>/dev/null | grep -qx true; then - exit 0 - fi - echo "Draft release $RELEASE_TAG is not ready yet; retrying..." - sleep 10 - done - - echo "Draft release $RELEASE_TAG was not found or is not a draft." >&2 - exit 1 - - binary-release-assets: - name: Binary Release Assets - runs-on: ubuntu-24.04 - needs: - - resolve-release + release-assets: + name: Build release assets + if: github.event.deleted == false permissions: - contents: write - steps: - - name: Check out repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - fetch-depth: 0 - - - name: Check out requested tag - if: ${{ github.event_name == 'workflow_dispatch' }} - env: - RELEASE_TAG: ${{ needs.resolve-release.outputs.tag }} - run: git checkout --detach "$RELEASE_TAG" - - - name: Set up Go - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 - with: - go-version-file: go.mod - cache: true - - - name: Install Syft - uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 - - - name: Run GoReleaser without publishing - uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3 - with: - distribution: goreleaser - version: '~> v2' - args: release --clean --skip=publish - env: - GITHUB_TOKEN: ${{ github.token }} - - - name: Stage and validate ghd release assets - env: - RELEASE_TAG: ${{ needs.resolve-release.outputs.tag }} - run: python3 .github/scripts/stage_ghd_release_assets.py --tag "$RELEASE_TAG" - - - name: Smoke test release binary - env: - RELEASE_TAG: ${{ needs.resolve-release.outputs.tag }} - run: | - set -euo pipefail - - version="${RELEASE_TAG#v}" - binary_name="template-go-api" - host_os="$(go env GOHOSTOS)" - host_arch="$(go env GOHOSTARCH)" - bin="dist/release-assets/${binary_name}_${version}_${host_os}_${host_arch}" - - if [ ! -x "$bin" ]; then - echo "failed to locate executable host binary artifact $bin" >&2 - find dist/release-assets -maxdepth 1 -type f -print | sort >&2 - exit 1 - fi - - output="$("$bin" --version)" - printf '%s\n' "$output" - - case "$output" in - "$binary_name "*"$version"*) ;; - *) echo "unexpected version output: $output" >&2; exit 1 ;; - esac - - - name: Upload assets to draft release - env: - GH_TOKEN: ${{ github.token }} - RELEASE_TAG: ${{ needs.resolve-release.outputs.tag }} - run: gh release upload "$RELEASE_TAG" dist/release-assets/* --clobber - - - name: Upload checksums for isolated attestation - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: release-checksums - path: dist/release-assets/checksums.txt - if-no-files-found: error - retention-days: 1 - - # Generate binary provenance in an ISOLATED reusable workflow (SLSA L3): the - # signing OIDC token is minted in attest.yml, unreachable by the build steps. - attest-binaries: - needs: - - binary-release-assets - permissions: - id-token: write - attestations: write + attestations: read contents: read - # attest.yml is shared with attest-image, whose attest job declares - # packages: write for the image attestation's push-to-registry. A reusable - # workflow cannot request more permissions than its caller grants, so this - # caller must also grant packages even though the binary attestation never - # pushes to a registry (unused here, but required to satisfy the shared job). - packages: write - uses: ./.github/workflows/attest.yml + id-token: write + uses: meigma/release/.github/workflows/go-pre-publish.yml@0dee66ff6c4cc7e28d7bb65e97a37d701e0eff4a with: - checksums-artifact: release-checksums + sign-and-notarize-macos: false + sign-native-packages: false - # Build the per-arch signed apk on a native runner (no QEMU). Each runner mints - # its own ephemeral melange key (distinct filename) and uploads its apk plus its - # PUBLIC key; the private key never leaves the runner. - melange-build: - name: Melange Build (${{ matrix.arch }}) - runs-on: ${{ matrix.runner }} - needs: - - resolve-release - - binary-release-assets + oci-image: + name: Build OCI image + needs: release-assets permissions: + actions: read + attestations: read contents: read - strategy: - fail-fast: false - matrix: - include: - - arch: amd64 - runner: ubuntu-24.04 - apkdir: x86_64 - - arch: arm64 - runner: ubuntu-24.04-arm - apkdir: aarch64 - steps: - - name: Check out repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - fetch-depth: 0 - - - name: Check out requested tag - if: ${{ github.event_name == 'workflow_dispatch' }} - env: - RELEASE_TAG: ${{ needs.resolve-release.outputs.tag }} - run: git checkout --detach "$RELEASE_TAG" - - - name: Setup mise - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 - with: - version: 2026.6.14 - cache: true - - - name: Resolve build metadata - id: meta - env: - RELEASE_VERSION: ${{ needs.resolve-release.outputs.version }} - run: | - set -euo pipefail - echo "version=${RELEASE_VERSION}" >> "$GITHUB_OUTPUT" - echo "commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - echo "date=$(git show -s --format=%cI HEAD)" >> "$GITHUB_OUTPUT" - - - name: Write melange vars file - env: - VERSION: ${{ steps.meta.outputs.version }} - COMMIT: ${{ steps.meta.outputs.commit }} - DATE: ${{ steps.meta.outputs.date }} - run: | - printf 'version: "%s"\ncommit: "%s"\ndate: "%s"\n' "$VERSION" "$COMMIT" "$DATE" > melange-vars.yaml - - - name: Generate ephemeral signing key - run: melange keygen "melange-${{ matrix.arch }}.rsa" - - - name: Build signed apk - run: | - melange build melange.yaml \ - --arch ${{ matrix.arch }} \ - --runner docker \ - --signing-key "melange-${{ matrix.arch }}.rsa" \ - --source-dir . \ - --vars-file melange-vars.yaml - - - name: Upload apk and public key - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: apk-${{ matrix.arch }} - path: | - packages/${{ matrix.apkdir }}/** - melange-${{ matrix.arch }}.rsa.pub - if-no-files-found: error - retention-days: 1 + uses: meigma/release/.github/workflows/go-oci-build.yml@0dee66ff6c4cc7e28d7bb65e97a37d701e0eff4a + with: + artifact-id: ${{ needs.release-assets.outputs.oci-input-artifact-id }} + artifact-digest: ${{ needs.release-assets.outputs.oci-input-artifact-digest }} - container-image-release: - name: Container Image Release - runs-on: ubuntu-24.04 - needs: - - resolve-release - - binary-release-assets - - melange-build + oci-publish: + name: Publish OCI image + needs: oci-image permissions: + actions: read + artifact-metadata: write + attestations: write contents: read - packages: write id-token: write - attestations: write - artifact-metadata: write - outputs: - image-name: ${{ steps.publish.outputs.name }} - image-digest: ${{ steps.publish.outputs.digest }} - steps: - - name: Check out repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - fetch-depth: 0 - - - name: Check out requested tag - if: ${{ github.event_name == 'workflow_dispatch' }} - env: - RELEASE_TAG: ${{ needs.resolve-release.outputs.tag }} - run: git checkout --detach "$RELEASE_TAG" - - - name: Setup mise - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 - with: - version: 2026.6.14 - cache: true - - - name: Install Syft - uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 - - - name: Download per-arch apks and public keys - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - pattern: apk-* - path: . - merge-multiple: true - - - name: Log in to GitHub Container Registry - uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ github.token }} - - - name: Publish multi-arch image - id: publish - env: - IMAGE_TAG: ${{ env.IMAGE_NAME }}:${{ needs.resolve-release.outputs.tag }} - run: | - set -euo pipefail - - # apko --sbom-path moves the generated SBOMs into this directory, which - # must already exist (apko does not create it). - mkdir -p sbom - - apko publish apko.yaml "$IMAGE_TAG" \ - --arch amd64,arm64 \ - --keyring-append ./melange-amd64.rsa.pub \ - --keyring-append ./melange-arm64.rsa.pub \ - --sbom-path ./sbom \ - | tee apko-out.txt - - digest="$(grep -oE 'sha256:[0-9a-f]{64}' apko-out.txt | tail -n1)" - if [[ ! "$digest" =~ ^sha256:[0-9a-f]{64}$ ]]; then - echo "failed to parse published image digest from apko output" >&2 - exit 1 - fi - - echo "name=${IMAGE_NAME}" >> "$GITHUB_OUTPUT" - echo "digest=${digest}" >> "$GITHUB_OUTPUT" - echo "tag=${IMAGE_TAG}" >> "$GITHUB_OUTPUT" - echo "ref=${IMAGE_NAME}@${digest}" >> "$GITHUB_OUTPUT" - - - name: Verify multi-arch manifest - env: - IMAGE_TAG: ${{ steps.publish.outputs.tag }} - run: | - set -euo pipefail - - manifest_json="$(docker buildx imagetools inspect "$IMAGE_TAG" --format '{{json .}}')" - platforms="$(jq -r ' - [ - .manifest.manifests[] - | select(.platform.os == "linux" and (.platform.architecture == "amd64" or .platform.architecture == "arm64")) - | "\(.platform.os)/\(.platform.architecture)" - ] - | unique - | join(",") - ' <<< "$manifest_json")" - - if [ "$platforms" != "linux/amd64,linux/arm64" ]; then - echo "unexpected manifest platforms: $platforms" >&2 - exit 1 - fi - - - name: Smoke test release image - env: - IMAGE_REF: ${{ steps.publish.outputs.ref }} - run: | - set -euo pipefail - - docker run --rm "$IMAGE_REF" --version - docker run --rm "$IMAGE_REF" openapi | grep -Fq "openapi: 3.0.3" - - - name: Sign image (keyless, Sigstore/Fulcio via OIDC) - env: - IMAGE_REF: ${{ steps.publish.outputs.ref }} - run: cosign sign --yes "$IMAGE_REF" - - - name: Generate image SBOM (Go module granularity) - env: - IMAGE_REF: ${{ steps.publish.outputs.ref }} - run: syft "$IMAGE_REF" -o spdx-json=image.spdx.json - - - name: Attest container image SBOM - uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0 - with: - subject-name: ${{ steps.publish.outputs.name }} - subject-digest: ${{ steps.publish.outputs.digest }} - sbom-path: image.spdx.json - push-to-registry: true + packages: write + uses: meigma/release/.github/workflows/publish-oci-image.yml@0dee66ff6c4cc7e28d7bb65e97a37d701e0eff4a + with: + artifact-id: ${{ needs.oci-image.outputs.artifact-id }} + artifact-digest: ${{ needs.oci-image.outputs.artifact-digest }} + image-digest: ${{ needs.oci-image.outputs.image-digest }} + publish-image: false - # Generate container provenance in the ISOLATED reusable workflow (SLSA L3). - attest-image: + github-release: + name: Publish GitHub Release needs: - - container-image-release + - release-assets + - oci-publish permissions: - id-token: write + actions: read + artifact-metadata: write attestations: write - packages: write contents: read - uses: ./.github/workflows/attest.yml + id-token: write + uses: meigma/release/.github/workflows/publish-github-release.yml@0dee66ff6c4cc7e28d7bb65e97a37d701e0eff4a with: - subject-name: ${{ needs.container-image-release.outputs.image-name }} - subject-digest: ${{ needs.container-image-release.outputs.image-digest }} - push-to-registry: true - - release-inspection-summary: - name: Release Inspection Summary - runs-on: ubuntu-24.04 - needs: - - resolve-release - - binary-release-assets - - container-image-release - - attest-binaries - - attest-image - permissions: {} - steps: - - name: Write inspection summary - env: - RELEASE_TAG: ${{ needs.resolve-release.outputs.tag }} - RELEASE_VERSION: ${{ needs.resolve-release.outputs.version }} - IMAGE_NAME: ${{ needs.container-image-release.outputs.image-name }} - IMAGE_DIGEST: ${{ needs.container-image-release.outputs.image-digest }} - run: | - { - echo "## Draft release ready for inspection" - echo - echo "Release: $RELEASE_TAG" - echo - echo "Binary verification commands:" - echo - echo '```sh' - echo "gh release view $RELEASE_TAG --repo $GITHUB_REPOSITORY --json isDraft,assets" - echo "asset=\"template-go-api_${RELEASE_VERSION}_\$(go env GOOS)_\$(go env GOARCH)\"" - echo "gh attestation verify \"dist/release-assets/\${asset}\" --repo \"$GITHUB_REPOSITORY\" --signer-workflow \"$GITHUB_REPOSITORY/.github/workflows/attest.yml\" --source-ref \"refs/tags/$RELEASE_TAG\" --deny-self-hosted-runners" - echo "ghd download \"$GITHUB_REPOSITORY/template-go-api@${RELEASE_VERSION}\" --output \"\$(mktemp -d)\"" - echo '```' - echo - echo "Container verification commands:" - echo - echo '```sh' - echo "docker login ghcr.io" - echo "docker pull \"${IMAGE_NAME}:${RELEASE_TAG}\"" - echo "docker run --rm \"${IMAGE_NAME}:${RELEASE_TAG}\" --version" - echo "gh attestation verify \"oci://${IMAGE_NAME}@${IMAGE_DIGEST}\" --repo \"$GITHUB_REPOSITORY\" --signer-workflow \"$GITHUB_REPOSITORY/.github/workflows/attest.yml\" --source-ref \"refs/tags/$RELEASE_TAG\" --deny-self-hosted-runners" - echo "cosign verify \"${IMAGE_NAME}@${IMAGE_DIGEST}\" --certificate-identity-regexp \"^https://github.com/${GITHUB_REPOSITORY}/.github/workflows/release.yml@.*\" --certificate-oidc-issuer https://token.actions.githubusercontent.com" - echo '```' - echo - echo "Publish or reject the draft release manually after inspection. The container image is already available at ${IMAGE_NAME}:${RELEASE_TAG} because GHCR does not have a draft release state." - } >> "$GITHUB_STEP_SUMMARY" + artifact-id: ${{ needs.release-assets.outputs.artifact-id }} + artifact-digest: ${{ needs.release-assets.outputs.artifact-digest }} + checksum-signing-workflow-ref: meigma/release/.github/workflows/go-pre-publish.yml@0dee66ff6c4cc7e28d7bb65e97a37d701e0eff4a + require-oci-image: true + oci-image-reference: ${{ needs.oci-publish.outputs.image-reference }} + release-app-client-id: ${{ vars.MEIGMA_RELEASE_APP_CLIENT_ID }} + publish-release: false + secrets: + release-app-private-key: ${{ secrets.MEIGMA_RELEASE_APP_PRIVATE_KEY }} diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 66701dc..4758a7b 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -39,19 +39,24 @@ jobs: version: 2026.6.14 cache: true - - name: Build local scan image (melange + apko) + - name: Build local scan image (Go + melange + apko) env: VERSION: ${{ steps.scan.outputs.version }} COMMIT: ${{ steps.scan.outputs.commit }} DATE: ${{ steps.scan.outputs.date }} run: | set -euo pipefail - printf 'version: "%s"\ncommit: "%s"\ndate: "%s"\n' "$VERSION" "$COMMIT" "$DATE" > melange-vars.yaml + mkdir -p scan-input + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath \ + -ldflags="-s -w -buildid= -X main.version=$VERSION -X main.commit=$COMMIT -X main.date=$DATE" \ + -o scan-input/application ./cmd/template-go-api + printf '{"version":"0.0.0"}\n' > melange-vars.json melange keygen melange.rsa - melange build melange.yaml --arch amd64 --runner docker \ - --signing-key melange.rsa --source-dir . --vars-file melange-vars.yaml + melange build melange.yaml --arch x86_64 --runner docker \ + --signing-key melange.rsa --source-dir scan-input --out-dir packages \ + --vars-file melange-vars.json apko build apko.yaml template-go-api:security-scan scan.tar \ - --arch amd64 --keyring-append ./melange.rsa.pub + --arch amd64 --repository-append ./packages --keyring-append ./melange.rsa.pub docker load < scan.tar docker tag template-go-api:security-scan-amd64 template-go-api:security-scan diff --git a/.gitignore b/.gitignore index e01befb..f15b412 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,8 @@ melange*.rsa melange*.rsa.pub melange-vars.yaml .melange-vars.local.yaml +.melange-vars.local.json +.image-local/ image.tar *.oci *.spdx.json diff --git a/.goreleaser.yaml b/.goreleaser.yaml index eb891de..c5b6b72 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,10 +1,14 @@ +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json version: 2 project_name: template-go-api -before: - hooks: - - go test ./... +gomod: + proxy: true + env: + - GOTOOLCHAIN=local + - GOPROXY=https://proxy.golang.org,direct + - GOSUMDB=sum.golang.org builds: - id: template-go-api @@ -15,6 +19,7 @@ builds: goos: - darwin - linux + - windows goarch: - amd64 - arm64 @@ -25,23 +30,103 @@ builds: - -X main.version={{ .Version }} - -X main.commit={{ .FullCommit }} - -X main.date={{ .CommitDate }} - mod_timestamp: '{{ .CommitTimestamp }}' + mod_timestamp: "{{ .CommitTimestamp }}" + +notarize: + macos: + - enabled: '{{ eq .Env.MACOS_NOTARIZE_ENABLED "true" }}' + ids: + - template-go-api + sign: + certificate: "{{ .Env.MACOS_SIGN_P12 }}" + password: "{{ .Env.MACOS_SIGN_PASSWORD }}" + notarize: + issuer_id: "{{ .Env.MACOS_NOTARY_ISSUER_ID }}" + key_id: "{{ .Env.MACOS_NOTARY_KEY_ID }}" + key: "{{ .Env.MACOS_NOTARY_KEY }}" + wait: true + timeout: 20m archives: - id: template-go-api ids: - template-go-api formats: - - binary + - tar.gz + format_overrides: + - goos: windows + formats: + - zip name_template: >- {{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }} +nfpms: + - id: release + ids: + - template-go-api + vendor: Meigma + homepage: https://github.com/meigma/template-go-api + maintainer: Meigma + description: Meigma Go web API server template. + formats: + - deb + - rpm + - apk + bindir: /usr/bin + rpm: + signature: + key_file: "{{ .Env.RELEASE_RPM_SIGNING_KEY_FILE }}" + apk: + signature: + key_file: "{{ .Env.RELEASE_APK_SIGNING_KEY_FILE }}" + key_name: template-go-api-001 + +homebrew_casks: + - name: template-go-api + ids: + - template-go-api + binaries: + - template-go-api + repository: + owner: meigma + name: homebrew-tap + homepage: https://github.com/meigma/template-go-api + description: Meigma Go web API server template + url: + template: "https://github.com/meigma/template-go-api/releases/download/{{ .Tag }}/{{ .ArtifactName }}" + skip_upload: true + +scoops: + - name: template-go-api + ids: + - template-go-api + repository: + owner: meigma + name: scoop-bucket + homepage: https://github.com/meigma/template-go-api + description: Meigma Go web API server template + url_template: "https://github.com/meigma/template-go-api/releases/download/{{ .Tag }}/{{ .ArtifactName }}" + skip_upload: true + checksum: name_template: checksums.txt sboms: - - id: binary - artifacts: binary + - id: archives + artifacts: archive + - id: packages + artifacts: package + +signs: + - id: checksums + cmd: cosign + artifacts: checksum + signature: "${artifact}.sigstore.json" + args: + - sign-blob + - "--bundle=${signature}" + - "${artifact}" + - --yes changelog: disable: true diff --git a/DELETE_ME.md b/DELETE_ME.md index cd86669..c0fcaef 100644 --- a/DELETE_ME.md +++ b/DELETE_ME.md @@ -15,14 +15,14 @@ It is only here to orient the initial project owner. - OpenTelemetry distributed tracing (opt-in via `--tracing-enabled`): inbound HTTP server spans (otelhttp, named by operation) and PostgreSQL query spans (otelpgx), exported over OTLP/HTTP and configured through the standard `OTEL_*` env vars. Off by default since it needs a collector. See the README's [Tracing](README.md#tracing) section. - A Cobra/Viper entrypoint under `cmd/template-go-api` and `internal/cli` exposing `serve` (default), `version`, `openapi`, and `migrate`. - Moon tasks for `format`, `lint`, `build`, `test`, and `check`, plus `sqlc` / `sqlc-check` (regenerate and drift-guard the typed query layer), `mockery` / `mockery-check` (regenerate and drift-guard the testify mocks), `migrate` (run database migrations), and `test-integration` (container-backed adapter tests). -- `golangci-lint`, `sqlc`, `goose`, and `mockery` wired through Proto and Moon. +- `golangci-lint`, `sqlc`, `goose`, and `mockery` wired through mise and Moon. - CI that delegates to `moon ci --summary minimal` with pinned actions, dependency caches, and minimal token permissions. - A scheduled container vulnerability scan that uploads SARIF results to GitHub code scanning. - Dependabot coverage for GitHub Actions, Docker base images, Go modules, and the docs uv project. - MkDocs Material docs under `docs/` that render the exported OpenAPI spec as an API reference (neoteroi OAD), published to GitHub Pages, with a CI drift-guard that fails if the committed spec falls out of sync with the code. - Repository settings for signed commits, squash-only merges, immutable releases, private vulnerability reporting, and protected tags. -- Release workflows for Release Please, GoReleaser binary assets, GHCR container images, checksums, SBOMs, and GitHub artifact attestations. -- A root `ghd.toml` package manifest so released binaries can be installed with `ghd`. +- Release Please plus a full-SHA-pinned `meigma/release` caller for verified + GitHub Release assets and a signed, attested, multi-architecture GHCR image. ## How It Works @@ -43,8 +43,10 @@ The workflow caches Go modules, Go build artifacts, golangci-lint state, and uv' The `GitHub Pages` workflow builds the MkDocs site on pull requests and deploys the default-branch `docs/build` output to Pages. The repository settings manifest defaults Pages to workflow-based publishing with HTTPS enforcement. -The release machinery is intentionally enabled in the template repository so the starter app proves Release Please, GoReleaser binary releases, native-runner container image builds, artifact validation, and attestations before generated projects inherit the setup. -The nominal generated-project path is an HTTP service with both a downloadable binary and a container image. If the new project is binary-only, container-only, or a pure Go library, trim the release files as described below before the first release. +The release machinery is enabled so the template proves the supported +`meigma/release` contract before generated projects inherit it: one Go +application, downloadable GitHub Release assets, and a Linux `amd64`/`arm64` +OCI image. ## First Setup Checklist @@ -56,12 +58,18 @@ The nominal generated-project path is an HTTP service with both a downloadable b 2. Choose the project shape. - Most applications should keep both the binary and container paths. For other shapes: + The reusable release unit supports one Go application with downloadable + binaries and an OCI image. For other shapes: - - Binary plus container: keep the default layout and update names. - - Binary only: keep GoReleaser and `ghd.toml`; remove the container release jobs and `melange.yaml`/`apko.yaml` if the project will not ship images. - - Container only: keep `melange.yaml`/`apko.yaml` and the container jobs; remove GoReleaser release assets and `ghd.toml` if users should not install a standalone binary. - - Library only: remove the CLI, `melange.yaml`/`apko.yaml`, GoReleaser, `ghd.toml`, and publish workflow pieces. Keep Release Please only if the library should still get changelogs, tags, and draft GitHub releases. + - Binary plus OCI image: keep the default release caller. + - Binary only: remove the `oci-image` and `oci-publish` jobs, set + `require-oci-image: false`, and remove `melange.yaml`, `apko.yaml`, the + `image-local` task, and the container security scan. + - Container only: the maintained release unit is not a container-only + contract. Keep its canonical binary path or replace the release workflow + with a container-specific design. + - Library only: remove the CLI and artifact workflow. Keep Release Please + only if the library needs changelogs, tags, and draft GitHub Releases. 3. For a binary-producing project, rename the binary directory: @@ -77,7 +85,10 @@ The nominal generated-project path is an HTTP service with both a downloadable b rg "template-go-api|TEMPLATE_GO_API|github.com/meigma/template-go-api" ``` - Update Go imports, Moon metadata, README text, docs text, and CLI environment variable prefixes. For release-bearing projects, also update `.goreleaser.yaml`, `release-please-config.json`, `ghd.toml`, `melange.yaml`, `apko.yaml`, and `.github/workflows/release*.yml` as applicable. + Update Go imports, Moon metadata, README text, docs text, and CLI environment + variable prefixes. For release-bearing projects, also update + `.goreleaser.yaml`, `release-please-config.json`, `melange.yaml`, `apko.yaml`, + and `.github/workflows/release.yml`. Update `docs/mkdocs.yml` with the generated repository's GitHub Pages URL, usually `https://OWNER.github.io/REPO/`. Remember the `TEMPLATE_GO_API_` environment-variable prefix is set in `internal/cli/root.go` (`SetEnvPrefix`); rename it to match the new module. @@ -126,40 +137,41 @@ The nominal generated-project path is an HTTP service with both a downloadable b go mod tidy ``` -8. Configure releases for the chosen shape. - - For the nominal binary plus container case: - - - Update `.goreleaser.yaml`: `project_name`, build `id`, `main`, binary name, archive name template, and any linked package paths. - - Update `ghd.toml`: `provenance.signer_workflow`, package name, description, asset patterns, and installed binary path. - - Update `apko.yaml` (entrypoint, OCI labels/annotations, packages) and `melange.yaml` (the `go/build` pipeline) — e.g. the binary path, image labels, or the runtime command if this is a service instead of a CLI. - - Update `.github/workflows/release.yml`: `IMAGE_NAME`, binary validation names, container labels, summary commands, and verification examples. - - Update `.github/workflows/release-dry-run.yml`: binary validation names, local container image name, and smoke-test commands. - - Update `.github/workflows/security-scan.yml`: local container image name and scan category. - - Update `.github/repository-settings.toml` only if required status-check names change. - - For binary-only projects: - - - Keep `.goreleaser.yaml`, `ghd.toml`, `Release Please`, `Binary Release Dry Run`, and the binary asset portions of `release.yml`. - - Remove the `container-image-release` job, container verification summary text, and `Container Image Dry Run`. - - Remove `melange.yaml`, `apko.yaml`, the `image-local`/`stack-up` mise tasks, and `.github/workflows/security-scan.yml` if no container build remains. - - Remove `Container Image Dry Run` from required branch checks. - - For container-only projects: - - - Keep `Release Please`, `Container Image Dry Run`, `container-image-release`, `melange.yaml`, and `apko.yaml`. - - Remove `.goreleaser.yaml`, `ghd.toml`, `binary-release-assets`, binary verification summary text, and `Binary Release Dry Run`. - - Change `container-image-release` so it depends only on `resolve-release`. - - Remove `Binary Release Dry Run` from required branch checks. - - For library-only projects: - - - Keep Release Please if version tags and changelogs are useful. - - Delete `.github/workflows/release.yml`, `.github/workflows/release-dry-run.yml`, `.github/workflows/attest.yml`, `.github/workflows/security-scan.yml`, `.goreleaser.yaml`, `ghd.toml`, `melange.yaml`, and `apko.yaml` unless the library publishes some other artifact. - - Remove release dry-run checks from `.github/repository-settings.toml`. - - If the library should not create releases at all, delete `.github/workflows/release-please.yml`, `release-please-config.json`, `.release-please-manifest.json`, and `CHANGELOG.md`. - - In every release-bearing project, configure the release app credentials, protected-tag bypass, and repository package permissions before the first release. Run the release dry-run workflow after these edits and before merging the first release PR. +8. Configure and rehearse releases. + + - Use the `meigma/release` v0.1.17 unit at full commit SHA + `0dee66ff6c4cc7e28d7bb65e97a37d701e0eff4a`. Keep every + `uses: meigma/release/...` reference and + `checksum-signing-workflow-ref` in `.github/workflows/release.yml` on that + same SHA. Do not mix release-unit revisions. + - Update `.goreleaser.yaml` for the application name, build ID, command path, + binary name, archive and package metadata, and release asset names. + - Update `melange.yaml` for the package metadata and + `/usr/bin/template-go-api` destination. Melange must install the staged + `application` file; it must not compile Go. + - Update `apko.yaml` for the package name, entrypoint, runtime packages, and + OCI annotations. Keep the nonroot UID/GID 65532 and the required CA + certificates and timezone data unless the application contract changes. + - Update the local `image-local` task and + `.github/workflows/security-scan.yml` names and metadata. Both paths must + compile the application first, stage it as `application`, and then package + it with Melange and apko. + - Configure the `MEIGMA_RELEASE_APP_CLIENT_ID` repository variable and + `MEIGMA_RELEASE_APP_PRIVATE_KEY` secret. Install the App on the repository + and grant it protected-tag bypass if `v*` tags are protected. + - Keep `publish-image: false` and `publish-release: false` for the first + stable candidate. Merge the Release Please PR, then inspect the populated + draft, workflow artifacts, checksums, attestations, and OCI digest. + - After the rehearsal succeeds, enable `publish-image` and + `publish-release` together in a reviewed change. The shipped caller then + publishes the GHCR image and GitHub Release. + + Homebrew, Scoop, and native package-repository publication are optional; no + optional publisher caller jobs ship in this repository. Follow the v0.1.17 + [Add Homebrew and Scoop](https://github.com/meigma/release/blob/v0.1.17/docs/how-to/add-homebrew-and-scoop.md) + and + [Operate a native package repository](https://github.com/meigma/release/blob/v0.1.17/docs/how-to/operate-a-native-package-repository.md) + guides before adding those publishers. 9. Run the full local check: diff --git a/README.md b/README.md index 1119770..782f892 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ It ships a runnable, hexagonal API server — built on [chi](https://github.com/go-chi/chi) and [Huma](https://huma.rocks) with a `todo` example resource — plus the shared Meigma repository baseline: Moon tasks, pinned CI, Dependabot, baseline security -settings, and an enabled Release Please and GoReleaser release layer. +settings, and Release Please plus the reusable `meigma/release` release unit. Persistence is a PostgreSQL adapter behind the domain's `todo.Repository` port ([pgx](https://github.com/jackc/pgx) + [sqlc](https://sqlc.dev) typed queries, @@ -19,22 +19,21 @@ your own resource and keep the persistence wiring. - [mise](https://mise.jdx.dev) — provisions every pinned tool from `mise.toml` + `mise.lock`: Go, Moon, Python + uv (for the MkDocs docs project), the - `golangci-lint`/`sqlc`/`mockery`/`goose` CLIs, and `melange`/`apko`/`cosign` for - releases. Run `mise install` once; there is nothing else to install by hand. -- Docker (to run a local PostgreSQL for the server — see [Persistence](#persistence) - and [Local stack](#local-stack-docker-compose) — and for the container-backed - integration tests) - -Tool versions live in `mise.toml`; `mise.lock` records a per-platform download URL -and checksum for each (and, for the aqua-backed CLIs, cosign/SLSA/GitHub-attestation -verification). `mise install` runs with `locked = true`, so it **fails closed** if a -tool lacks a pre-resolved, checksummed entry for the current platform — replacing the -former Proto `checksum-url` pins and the bespoke `.moon/proto/sqlc.sha256` digest -(sqlc publishes no upstream checksum). Moon runs every task against these tools as -`system` binaries on PATH and manages no toolchain itself. To bump a tool, edit its -version in `mise.toml`, run + development CLIs, and GoReleaser, GitHub CLI, Syft, Cosign, Melange, and apko + for release and image work. Run `mise install` once; there is nothing else to + install by hand. +- Docker (for local image builds, the Compose stack, the container-backed + integration tests, and a local PostgreSQL server; see + [Persistence](#persistence) and [Local stack](#local-stack-docker-compose)) + +Tool versions live in `mise.toml`; `mise.lock` records pre-resolved +per-platform downloads and the verification metadata available from each +backend. `mise install` runs with `locked = true`, so it fails closed when a +tool has no locked URL for the current platform. Moon runs tasks against these +tools as `system` binaries on PATH and manages no toolchain itself. To bump a +tool, edit its version in `mise.toml`, run `mise lock --platform linux-x64,linux-arm64,macos-x64,macos-arm64`, and commit -`mise.toml` + `mise.lock`. +`mise.toml` and `mise.lock` together. > **New repository from this template?** Work through [DELETE_ME.md](DELETE_ME.md) > first — it covers renaming the module, binary, image, and env prefix, and @@ -656,14 +655,16 @@ and OpenAPI drift guard. ## Container Image -The image is built **without a Dockerfile**: -[melange](https://github.com/chainguard-dev/melange) compiles the binary into a -signed [Wolfi](https://github.com/wolfi-dev) apk (`melange.yaml`), and -[apko](https://github.com/chainguard-dev/apko) assembles it into a minimal, -multi-arch, non-root runtime image (`apko.yaml`) — the modern equivalent of the -former distroless image (uid 65532, ca-certificates, tzdata, no shell). Each -architecture builds natively (no QEMU). Build and run it locally with the bundled -mise task (it uses melange's Docker runner, so Docker must be running): +The image is built **without a Dockerfile**. GoReleaser produces the canonical +Linux release binaries. [Melange](https://github.com/chainguard-dev/melange) +packages those exact bytes as signed [Wolfi](https://github.com/wolfi-dev) APKs, +and [apko](https://github.com/chainguard-dev/apko) assembles the runtime image. +The local and scheduled security-scan paths first compile the application, stage +the binary as `application`, and then run the same Melange/apko package path. + +The runtime image is multi-architecture and nonroot: `/usr/bin/template-go-api` +runs as UID/GID 65532 with CA certificates and timezone data. Build and run a +host-architecture image with Docker: ```sh mise run image-local # build the host-arch image, load as template-go-api:dev @@ -678,11 +679,10 @@ a PostgreSQL reachable from inside the container — on Docker Desktop, [Running with PostgreSQL](#running-with-postgresql)). For a fully wired API + PostgreSQL run, use the Compose stack (`mise run stack-up`) instead. -The Wolfi base intentionally floats to the latest packages (fresh CA bundle and -timezones, low CVE surface); the exact resolved versions are recorded in the -per-build SBOM and provenance attestation rather than pinned. `version`, `commit`, -and `date` are stamped into the binary via melange `--vars-file` — the release -workflow supplies the real values, and `mise run image-local` uses `dev`. +The image resolves its base packages during each build. The generated SBOM and +provenance record the resolved contents. GoReleaser stamps `version`, `commit`, +and `date` into release binaries; `mise run image-local` supplies development +values when it compiles the staged `application`. ## CI and Security @@ -692,28 +692,50 @@ The docs workflow builds the MkDocs site on pull requests and deploys `docs/buil The scheduled security scan workflow builds the local container image weekly, scans it for high/critical fixed vulnerabilities, and uploads SARIF results to GitHub code scanning. Dependabot covers GitHub Actions, Docker base images, the root Go module, and the docs uv project. -The build CLIs are pinned in `mise.toml` and locked in `mise.lock`, which records a per-platform download URL and checksum for every tool. `mise install` runs with `locked = true`, so it fails closed if any tool lacks a pre-resolved, checksummed entry for the current platform; the aqua-backed CLIs (`golangci-lint`, `sqlc`, `mockery`, `goose`, `moon`, `melange`, `apko`, `cosign`) additionally verify cosign signatures, SLSA provenance, and GitHub artifact attestations at install time. This supersedes the former Proto `checksum-url` pins and the repository-committed `.moon/proto/sqlc.sha256` digest that existed only because sqlc publishes no upstream checksum. +The build CLIs are pinned in `mise.toml` and locked in `mise.lock`, which records +per-platform download URLs and, where the upstream source provides them, +checksums and provenance metadata. `mise install` runs with `locked = true`, so +it fails closed if a tool lacks a pre-resolved URL for the current platform. Repository settings live in `.github/repository-settings.toml`. They default to immutable releases, private vulnerability reporting, signed commits, squash-only merges, GitHub Pages workflow publishing, and protected tags. ## Release Layer -Release automation is enabled for the template application so this repository proves the full binary and container release lifecycle before generated projects inherit it. -Repositories generated from the template should update the release app credentials, package names, asset patterns, container image name, and `ghd.toml` signer workflow before cutting their first release. +Release automation is enabled so the template proves the shipped GitHub Release +and OCI image paths before generated projects inherit them. The release path is: -- Release Please opens and maintains the release PR. -- Release Please creates a draft GitHub release and tag after merge. -- Release Dry Run rehearses the GoReleaser binary path and the native-runner melange/apko container build path on pull requests. -- GoReleaser builds binaries, checksums, and SBOMs without publishing directly. -- The release workflow uploads assets to the draft release; a separate, isolated reusable workflow (`attest.yml`) generates the GitHub-hosted provenance attestation for the binary checksums. -- The release workflow builds amd64 and arm64 apks with melange on native GitHub-hosted runners, assembles and publishes `ghcr.io/meigma/template-go-api:vX.Y.Z` as a multi-platform manifest with apko, signs it with keyless cosign, and attaches a syft SBOM attestation; the isolated `attest.yml` workflow then creates the GitHub-native provenance attestation for the manifest digest. -- Generating both provenance attestations in the isolated `attest.yml` reusable workflow (not in the build job) keeps the signing identity unreachable by build steps — the SLSA Build L3 isolation requirement — while staying on GitHub's attestation API (verify with `gh attestation verify --signer-workflow …/attest.yml`). -- A human inspects the draft release before publication. - -The root `ghd.toml` matches the default GoReleaser output so generated projects can be installed with `ghd` once the release workflow runs. +- Release Please maintains the release PR and creates a stable + `vMAJOR.MINOR.PATCH` tag plus a draft GitHub Release after merge. +- `.github/workflows/release.yml` calls the `meigma/release` v0.1.17 unit. Every + reusable workflow and checksum signer reference is pinned to the same full + commit SHA, `0dee66ff6c4cc7e28d7bb65e97a37d701e0eff4a`. +- GoReleaser builds the GitHub Release archives, native packages, checksums, + SBOMs, and checksum Sigstore bundle. Its canonical Linux binaries are the only + inputs to the OCI build. +- The release unit's `release-cli` stages those binaries as `application`, uses + Melange and apko to assemble the `linux/amd64` and `linux/arm64` image, and + owns image verification, GHCR publication, signing, and attestations. +- The GitHub Release publisher verifies and uploads the assets, then publishes + the draft only after the required OCI path succeeds. + +The initial caller keeps `publish-image: false` and `publish-release: false` for +a stable-candidate rehearsal. Generated repositories must update application, +package, and image metadata; configure the +`MEIGMA_RELEASE_APP_CLIENT_ID` repository variable and +`MEIGMA_RELEASE_APP_PRIVATE_KEY` secret; inspect the draft and workflow +artifacts; and enable both publication inputs only after the rehearsal succeeds. +See [`DELETE_ME.md`](DELETE_ME.md) for the adoption steps. + +GitHub Release and OCI publication are shipped. Homebrew, Scoop, and native +package-repository publication are optional and are not caller jobs in this +repository. Add them only by following the v0.1.17 +[Add Homebrew and Scoop](https://github.com/meigma/release/blob/v0.1.17/docs/how-to/add-homebrew-and-scoop.md) +and +[Operate a native package repository](https://github.com/meigma/release/blob/v0.1.17/docs/how-to/operate-a-native-package-repository.md) +guides. ## Contributing diff --git a/apko.yaml b/apko.yaml index 03faf26..4ff9c49 100644 --- a/apko.yaml +++ b/apko.yaml @@ -1,28 +1,17 @@ -# Minimal, multi-arch, nonroot runtime image assembled from the locally-built -# template-go-api apk (melange.yaml) plus the Wolfi base packages. Mirrors the -# former gcr.io/distroless/static-debian12:nonroot posture (uid 65532, ca-certs, -# tzdata, no shell). The Wolfi base intentionally floats to the latest (fresh CA -# bundle/timezones, low CVE surface); the exact resolved versions are recorded in -# the per-build SBOM + provenance attestation rather than pinned in a lockfile. contents: repositories: - https://packages.wolfi.dev/os - - "@local ./packages" keyring: - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub - # Per-arch ephemeral melange public keys are appended at build/publish time via - # --keyring-append (filenames differ per arch; never committed). packages: - wolfi-baselayout - ca-certificates-bundle - tzdata - - template-go-api@local + - template-go-api entrypoint: command: /usr/bin/template-go-api -# Create the nonroot user/group (uid/gid 65532) and run as it — Wolfi has no -# `nonroot` package; the user is defined here, mirroring distroless:nonroot. accounts: groups: - groupname: nonroot @@ -33,6 +22,10 @@ accounts: gid: 65532 run-as: 65532 +environment: + PATH: /usr/bin + SSL_CERT_FILE: /etc/ssl/certs/ca-certificates.crt + archs: - amd64 - arm64 @@ -41,4 +34,3 @@ annotations: org.opencontainers.image.title: template-go-api org.opencontainers.image.description: Meigma Go web API server template org.opencontainers.image.source: https://github.com/meigma/template-go-api - org.opencontainers.image.version: "1.0.4" # x-release-please-version diff --git a/go.mod b/go.mod index 8b7baa5..504f186 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/meigma/template-go-api -go 1.26.4 +go 1.26.6 require ( github.com/cedar-policy/cedar-go v1.8.0 diff --git a/melange.yaml b/melange.yaml index 34587c6..9846d25 100644 --- a/melange.yaml +++ b/melange.yaml @@ -1,15 +1,14 @@ -# Builds the template-go-api binary into a signed Wolfi apk; apko.yaml assembles it -# into the minimal multi-arch runtime image. version/commit/date are stamped into the -# binary via -ldflags, mirroring GoReleaser and the former Dockerfile. -# -# package.version is bumped by release-please (extra-files in release-please-config.json). -# The build-time version/commit/date are injected via --vars-file (see the release -# workflow and the `image-local` mise task), so no date math runs inside the sandbox. package: name: template-go-api - version: "1.0.4" # x-release-please-version + version: ${{vars.version}} epoch: 0 description: Meigma Go web API server template + target-architecture: + - x86_64 + - aarch64 + +vars: + version: 0.0.0 environment: contents: @@ -18,25 +17,8 @@ environment: keyring: - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub packages: - - go-1.26 - environment: - CGO_ENABLED: "0" - -# Defaults; overridden at build time via `--vars-file` with the real release values. -vars: - version: "0.0.0" - commit: "none" - date: "unknown" + - busybox pipeline: - # go/build auto-adds -trimpath and installs to /usr/bin/. The embedded - # assets (SQL migrations + Cedar policies) compile in from the mounted source. - - uses: go/build - with: - packages: ./cmd/template-go-api - output: template-go-api - go-package: go-1.26 - modroot: . - strip: "-s -w" - ldflags: "-buildid= -X main.version=${{vars.version}} -X main.commit=${{vars.commit}} -X main.date=${{vars.date}}" - extra-args: "-mod=readonly -buildvcs=false" + - runs: | + install -Dm755 -o 0 -g 0 application "${{targets.destdir}}/usr/bin/template-go-api" diff --git a/mise.lock b/mise.lock index d19a28b..23a8405 100644 --- a/mise.lock +++ b/mise.lock @@ -1,5 +1,29 @@ # @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html +[[tools."aqua:anchore/syft"]] +version = "1.51.0" +backend = "aqua:anchore/syft" + +[tools."aqua:anchore/syft"."platforms.linux-arm64"] +checksum = "sha256:6c0466811541ea03add5213a60a1562f0851e4c0b0ecfdee1a694a9455285900" +url = "https://github.com/anchore/syft/releases/download/v1.51.0/syft_1.51.0_linux_arm64.tar.gz" +url_api = "https://api.github.com/repos/anchore/syft/releases/assets/508878652" + +[tools."aqua:anchore/syft"."platforms.linux-x64"] +checksum = "sha256:2a2e837a2c8d59ec9af5472ee22d3b04ee463c4e44476ecf993fd1e5ab6ebc7f" +url = "https://github.com/anchore/syft/releases/download/v1.51.0/syft_1.51.0_linux_amd64.tar.gz" +url_api = "https://api.github.com/repos/anchore/syft/releases/assets/508878656" + +[tools."aqua:anchore/syft"."platforms.macos-arm64"] +checksum = "sha256:4f37f4c7fefce0a68e4cf71ba3f5f9829a99e65d89b29f7ee41b8c2c10ea8c59" +url = "https://github.com/anchore/syft/releases/download/v1.51.0/syft_1.51.0_darwin_arm64.tar.gz" +url_api = "https://api.github.com/repos/anchore/syft/releases/assets/508878653" + +[tools."aqua:anchore/syft"."platforms.macos-x64"] +checksum = "sha256:cddf9a044145caf0a1a3194d00d1dd51a1666f4814f2919cdb4768a0c062ad95" +url = "https://github.com/anchore/syft/releases/download/v1.51.0/syft_1.51.0_darwin_amd64.tar.gz" +url_api = "https://api.github.com/repos/anchore/syft/releases/assets/508878649" + [[tools."aqua:astral-sh/uv"]] version = "0.11.0" backend = "aqua:astral-sh/uv" @@ -7,62 +31,102 @@ backend = "aqua:astral-sh/uv" [tools."aqua:astral-sh/uv"."platforms.linux-arm64"] checksum = "sha256:658be4b8ec905635f1295468d4d5120d9e1ab1722eec9a104473ce993590babe" url = "https://github.com/astral-sh/uv/releases/download/0.11.0/uv-aarch64-unknown-linux-musl.tar.gz" +url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/380020571" provenance = "github-attestations" [tools."aqua:astral-sh/uv"."platforms.linux-x64"] checksum = "sha256:bf6b0757c73d1726faa2a819b155d4d864919a95766720215d78fdcd09d42d26" url = "https://github.com/astral-sh/uv/releases/download/0.11.0/uv-x86_64-unknown-linux-musl.tar.gz" +url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/380020617" provenance = "github-attestations" [tools."aqua:astral-sh/uv"."platforms.macos-arm64"] checksum = "sha256:0c0f32c6a3473c5928aff96c3233715edfc79290e892f255cac93710cde7b91a" url = "https://github.com/astral-sh/uv/releases/download/0.11.0/uv-aarch64-apple-darwin.tar.gz" +url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/380020558" provenance = "github-attestations" [tools."aqua:astral-sh/uv"."platforms.macos-x64"] checksum = "sha256:31aaec764166af8885cf99321fd6ed24fef80225a6f26ed1ae8ce04111688a7e" url = "https://github.com/astral-sh/uv/releases/download/0.11.0/uv-x86_64-apple-darwin.tar.gz" +url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/380020610" provenance = "github-attestations" [[tools."aqua:chainguard-dev/apko"]] -version = "1.2.19" +version = "1.2.37" backend = "aqua:chainguard-dev/apko" [tools."aqua:chainguard-dev/apko"."platforms.linux-arm64"] -checksum = "sha256:d1ba1cb5028a58b080e8ae1c168b67bb363b1a5029069c4ac1394951796db7d6" -url = "https://github.com/chainguard-dev/apko/releases/download/v1.2.19/apko_1.2.19_linux_arm64.tar.gz" +checksum = "sha256:43e94fcda75df76e0b9fe78a98b2277ce4cc7eabe246e02df7c6f52c75b50f5d" +url = "https://github.com/chainguard-dev/apko/releases/download/v1.2.37/apko_1.2.37_linux_arm64.tar.gz" +url_api = "https://api.github.com/repos/chainguard-dev/apko/releases/assets/517375968" [tools."aqua:chainguard-dev/apko"."platforms.linux-x64"] -checksum = "sha256:86c7c860ed0b429f9dba0317d4646d382503280f6d8bb7f568a3934515b01b04" -url = "https://github.com/chainguard-dev/apko/releases/download/v1.2.19/apko_1.2.19_linux_amd64.tar.gz" +checksum = "sha256:9fa4ed893d0d87483a5f709fd5e2f1a1e6f93e40b35195949df2d9cf4f7093cc" +url = "https://github.com/chainguard-dev/apko/releases/download/v1.2.37/apko_1.2.37_linux_amd64.tar.gz" +url_api = "https://api.github.com/repos/chainguard-dev/apko/releases/assets/517375966" [tools."aqua:chainguard-dev/apko"."platforms.macos-arm64"] -checksum = "sha256:1aaf12b7440d772934ca860102675780a9fa7dbbd9769c2b731977932d9d6762" -url = "https://github.com/chainguard-dev/apko/releases/download/v1.2.19/apko_1.2.19_darwin_arm64.tar.gz" +checksum = "sha256:097ebf8e1f19278bca2e5fd788b7df002b40f25b2cbcda90165e5b78982b8c5b" +url = "https://github.com/chainguard-dev/apko/releases/download/v1.2.37/apko_1.2.37_darwin_arm64.tar.gz" +url_api = "https://api.github.com/repos/chainguard-dev/apko/releases/assets/517375993" [tools."aqua:chainguard-dev/apko"."platforms.macos-x64"] -checksum = "sha256:98cb42ce222b0e0aa856fdc7ec0bba55b7866810f7067772eb9f03faa1378228" -url = "https://github.com/chainguard-dev/apko/releases/download/v1.2.19/apko_1.2.19_darwin_amd64.tar.gz" +checksum = "sha256:7edc3c7c5839b7c8352c78e11f510537ff261f0bdcaf7b70c8fe985a1e3d5119" +url = "https://github.com/chainguard-dev/apko/releases/download/v1.2.37/apko_1.2.37_darwin_amd64.tar.gz" +url_api = "https://api.github.com/repos/chainguard-dev/apko/releases/assets/517375996" [[tools."aqua:chainguard-dev/melange"]] -version = "0.54.0" +version = "0.59.1" backend = "aqua:chainguard-dev/melange" [tools."aqua:chainguard-dev/melange"."platforms.linux-arm64"] -checksum = "sha256:1c1dc145c473adaf86ab52d18b0f748db2dcc299005886d1c00135bea65c9310" -url = "https://github.com/chainguard-dev/melange/releases/download/v0.54.0/melange_0.54.0_linux_arm64.tar.gz" +checksum = "sha256:aa5d221f92a248ba9aa490d1441c733417ad4c02f32fa9a13b8800acb42946f9" +url = "https://github.com/chainguard-dev/melange/releases/download/v0.59.1/melange_0.59.1_linux_arm64.tar.gz" +url_api = "https://api.github.com/repos/chainguard-dev/melange/releases/assets/514836314" [tools."aqua:chainguard-dev/melange"."platforms.linux-x64"] -checksum = "sha256:1c25b9c1bc2f862e32a5f8bac47c9993dcb5a11051b5295e724e95426d3b6bd4" -url = "https://github.com/chainguard-dev/melange/releases/download/v0.54.0/melange_0.54.0_linux_amd64.tar.gz" +checksum = "sha256:90f76f3e5fcb90ddc4c932ad352982bda98d58001d2bcccb982dd7fb2e608a1f" +url = "https://github.com/chainguard-dev/melange/releases/download/v0.59.1/melange_0.59.1_linux_amd64.tar.gz" +url_api = "https://api.github.com/repos/chainguard-dev/melange/releases/assets/514836359" [tools."aqua:chainguard-dev/melange"."platforms.macos-arm64"] -checksum = "sha256:4402934be1032c3a265dcf41dde097b825017664e295b85182695f39f2d92c4e" -url = "https://github.com/chainguard-dev/melange/releases/download/v0.54.0/melange_0.54.0_darwin_arm64.tar.gz" +checksum = "sha256:6485fcff49ca60fc3f75a773eb1f7f81562cf394b86cc9fe8371f7d55e8f9b98" +url = "https://github.com/chainguard-dev/melange/releases/download/v0.59.1/melange_0.59.1_darwin_arm64.tar.gz" +url_api = "https://api.github.com/repos/chainguard-dev/melange/releases/assets/514836312" [tools."aqua:chainguard-dev/melange"."platforms.macos-x64"] -checksum = "sha256:e7b44654803809bf9f79daec70097bbede9a5d33c1d925888ef1e19d5b4cb1a7" -url = "https://github.com/chainguard-dev/melange/releases/download/v0.54.0/melange_0.54.0_darwin_amd64.tar.gz" +checksum = "sha256:0a1080caa973c9a10b9e331542e927531710810647c4f3b9ec9e53bf22f2e960" +url = "https://github.com/chainguard-dev/melange/releases/download/v0.59.1/melange_0.59.1_darwin_amd64.tar.gz" +url_api = "https://api.github.com/repos/chainguard-dev/melange/releases/assets/514836357" + +[[tools."aqua:cli/cli"]] +version = "2.97.0" +backend = "aqua:cli/cli" + +[tools."aqua:cli/cli"."platforms.linux-arm64"] +checksum = "sha256:73ea440ecad9c9e284429997ee6f93577bc6f7bc6fba357ef62c53ad8fb641a5" +url = "https://github.com/cli/cli/releases/download/v2.97.0/gh_2.97.0_linux_arm64.tar.gz" +url_api = "https://api.github.com/repos/cli/cli/releases/assets/496108278" +provenance = "github-attestations" + +[tools."aqua:cli/cli"."platforms.linux-x64"] +checksum = "sha256:a2c9b8497e1f85b1ad0dfcb78b5a622e098801b8e461e459e88e1ee12f018112" +url = "https://github.com/cli/cli/releases/download/v2.97.0/gh_2.97.0_linux_amd64.tar.gz" +url_api = "https://api.github.com/repos/cli/cli/releases/assets/496108269" +provenance = "github-attestations" + +[tools."aqua:cli/cli"."platforms.macos-arm64"] +checksum = "sha256:a58b8fd77b417a38f47a0b54d1370c59b0fcdb324ccc9ca002b0998f7c4c999e" +url = "https://github.com/cli/cli/releases/download/v2.97.0/gh_2.97.0_macOS_arm64.zip" +url_api = "https://api.github.com/repos/cli/cli/releases/assets/496108298" +provenance = "github-attestations" + +[tools."aqua:cli/cli"."platforms.macos-x64"] +checksum = "sha256:63298c998cc2a924c9e254c6af6a1caad6ece281122687a91f079bc0a462700e" +url = "https://github.com/cli/cli/releases/download/v2.97.0/gh_2.97.0_macOS_amd64.zip" +url_api = "https://api.github.com/repos/cli/cli/releases/assets/496108300" +provenance = "github-attestations" [[tools."aqua:golangci/golangci-lint"]] version = "2.12.2" @@ -71,21 +135,53 @@ backend = "aqua:golangci/golangci-lint" [tools."aqua:golangci/golangci-lint"."platforms.linux-arm64"] checksum = "sha256:44cd40a8c76c86755375adfeea52cfd3533cb43d7bd647771e0ae065e166df3a" url = "https://github.com/golangci/golangci-lint/releases/download/v2.12.2/golangci-lint-2.12.2-linux-arm64.tar.gz" +url_api = "https://api.github.com/repos/golangci/golangci-lint/releases/assets/413470996" provenance = "github-attestations" [tools."aqua:golangci/golangci-lint"."platforms.linux-x64"] checksum = "sha256:8df580d2670fed8fa984aac0507099af8df275e665215f5c7a2ae3943893a553" url = "https://github.com/golangci/golangci-lint/releases/download/v2.12.2/golangci-lint-2.12.2-linux-amd64.tar.gz" +url_api = "https://api.github.com/repos/golangci/golangci-lint/releases/assets/413471054" provenance = "github-attestations" [tools."aqua:golangci/golangci-lint"."platforms.macos-arm64"] checksum = "sha256:a9c54498731b3128f79e090be6110f3e5fffccc617b08142ed244d4126c73f29" url = "https://github.com/golangci/golangci-lint/releases/download/v2.12.2/golangci-lint-2.12.2-darwin-arm64.tar.gz" +url_api = "https://api.github.com/repos/golangci/golangci-lint/releases/assets/413470950" provenance = "github-attestations" [tools."aqua:golangci/golangci-lint"."platforms.macos-x64"] checksum = "sha256:f6f06d94b6241521c53d15450c5209b028270bf966f842afb11c030c79f5bc16" url = "https://github.com/golangci/golangci-lint/releases/download/v2.12.2/golangci-lint-2.12.2-darwin-amd64.tar.gz" +url_api = "https://api.github.com/repos/golangci/golangci-lint/releases/assets/413470980" +provenance = "github-attestations" + +[[tools."aqua:goreleaser/goreleaser"]] +version = "2.17.1" +backend = "aqua:goreleaser/goreleaser" + +[tools."aqua:goreleaser/goreleaser"."platforms.linux-arm64"] +checksum = "sha256:702f03769ac8bcb0e47839c82243cc614ae995633599a98c63062e13ea85f829" +url = "https://github.com/goreleaser/goreleaser/releases/download/v2.17.1/goreleaser_Linux_arm64.tar.gz" +url_api = "https://api.github.com/repos/goreleaser/goreleaser/releases/assets/490565875" +provenance = "github-attestations" + +[tools."aqua:goreleaser/goreleaser"."platforms.linux-x64"] +checksum = "sha256:a99bbc7ae0d8d897b07c4c497a9b62f222558804715ef219d1af05a7e417bc80" +url = "https://github.com/goreleaser/goreleaser/releases/download/v2.17.1/goreleaser_Linux_x86_64.tar.gz" +url_api = "https://api.github.com/repos/goreleaser/goreleaser/releases/assets/490565870" +provenance = "github-attestations" + +[tools."aqua:goreleaser/goreleaser"."platforms.macos-arm64"] +checksum = "sha256:f49d4fe67d283b5b5130c380c983087dca0a4d4ec15b6637b18fe1ab096780d8" +url = "https://github.com/goreleaser/goreleaser/releases/download/v2.17.1/goreleaser_Darwin_all.tar.gz" +url_api = "https://api.github.com/repos/goreleaser/goreleaser/releases/assets/490565848" +provenance = "github-attestations" + +[tools."aqua:goreleaser/goreleaser"."platforms.macos-x64"] +checksum = "sha256:f49d4fe67d283b5b5130c380c983087dca0a4d4ec15b6637b18fe1ab096780d8" +url = "https://github.com/goreleaser/goreleaser/releases/download/v2.17.1/goreleaser_Darwin_all.tar.gz" +url_api = "https://api.github.com/repos/goreleaser/goreleaser/releases/assets/490565848" provenance = "github-attestations" [[tools."aqua:moonrepo/moon"]] @@ -95,14 +191,17 @@ backend = "aqua:moonrepo/moon" [tools."aqua:moonrepo/moon"."platforms.linux-arm64"] checksum = "sha256:553c2ed2202a0f2376cd21d49cd6436d8ff1cbbce537b2d97681adf7beaca61e" url = "https://github.com/moonrepo/moon/releases/download/v2.3.5/moon_cli-aarch64-unknown-linux-musl.tar.xz" +url_api = "https://api.github.com/repos/moonrepo/moon/releases/assets/458831448" [tools."aqua:moonrepo/moon"."platforms.linux-x64"] checksum = "sha256:5639b8780dc4281776524a48f645b814754f8a5373c3d3958cbf6c2336f6a317" url = "https://github.com/moonrepo/moon/releases/download/v2.3.5/moon_cli-x86_64-unknown-linux-musl.tar.xz" +url_api = "https://api.github.com/repos/moonrepo/moon/releases/assets/458831476" [tools."aqua:moonrepo/moon"."platforms.macos-arm64"] checksum = "sha256:2b981e6827833771a51ac4766e9907d12e111bfa7272ab99b5dce28793401b8e" url = "https://github.com/moonrepo/moon/releases/download/v2.3.5/moon_cli-aarch64-apple-darwin.tar.xz" +url_api = "https://api.github.com/repos/moonrepo/moon/releases/assets/458831440" [tools."aqua:moonrepo/moon"."platforms.macos-x64"] checksum = "sha256:ffc0bf6e808f2c08f97593adec269c41fc6396e1399f546f13bb2f878efb6ce7" @@ -115,41 +214,49 @@ backend = "aqua:pressly/goose" [tools."aqua:pressly/goose"."platforms.linux-arm64"] checksum = "sha256:638af56b2ed33ff33cc3f30447f447b1c8e5894c6252fbda1e459adec94ba0fe" url = "https://github.com/pressly/goose/releases/download/v3.27.1/goose_linux_arm64" +url_api = "https://api.github.com/repos/pressly/goose/releases/assets/404813415" [tools."aqua:pressly/goose"."platforms.linux-x64"] checksum = "sha256:c5f1e5cd3b8e5da05592c2714b079d78ec846ddc7ec1f70d474c0449e79f6ab4" url = "https://github.com/pressly/goose/releases/download/v3.27.1/goose_linux_x86_64" +url_api = "https://api.github.com/repos/pressly/goose/releases/assets/404813372" [tools."aqua:pressly/goose"."platforms.macos-arm64"] checksum = "sha256:d7867b7a9d1117024b17afdb043c573909e595d2c29dcee13bd96c341eb98ff2" url = "https://github.com/pressly/goose/releases/download/v3.27.1/goose_darwin_arm64" +url_api = "https://api.github.com/repos/pressly/goose/releases/assets/404813370" [tools."aqua:pressly/goose"."platforms.macos-x64"] checksum = "sha256:24f3b4dc3c792a7afba348de12245af8b4199cfd0ac6a673f7ab483b2ad48f08" url = "https://github.com/pressly/goose/releases/download/v3.27.1/goose_darwin_x86_64" +url_api = "https://api.github.com/repos/pressly/goose/releases/assets/404813371" [[tools."aqua:sigstore/cosign"]] -version = "3.1.1" +version = "3.1.3" backend = "aqua:sigstore/cosign" [tools."aqua:sigstore/cosign"."platforms.linux-arm64"] -checksum = "sha256:2ec865872e331c32fd12b08dae15332d3f92c0aa029219589684a4903ca85d11" -url = "https://github.com/sigstore/cosign/releases/download/v3.1.1/cosign-linux-arm64" +checksum = "sha256:c5d324e091826b0d7a78eb16fef316450b4eb9aaec045611c08ba06f5e73220a" +url = "https://github.com/sigstore/cosign/releases/download/v3.1.3/cosign-linux-arm64" +url_api = "https://api.github.com/repos/sigstore/cosign/releases/assets/503286299" provenance = "cosign" [tools."aqua:sigstore/cosign"."platforms.linux-x64"] -checksum = "sha256:ae1ecd212663f3693ad9edf8b1a183900c9a52d3155ba6e354237f9a0f6463fc" -url = "https://github.com/sigstore/cosign/releases/download/v3.1.1/cosign-linux-amd64" +checksum = "sha256:4629c757b7618056f8ddd7e2625ae9fdd94c0372a65049520bc7d9df9efc7f71" +url = "https://github.com/sigstore/cosign/releases/download/v3.1.3/cosign-linux-amd64" +url_api = "https://api.github.com/repos/sigstore/cosign/releases/assets/503286005" provenance = "cosign" [tools."aqua:sigstore/cosign"."platforms.macos-arm64"] -checksum = "sha256:94b42a9e697be95675f6160ab031a9a5f1ec1e646d6f648d7b2f5cd59ececbc5" -url = "https://github.com/sigstore/cosign/releases/download/v3.1.1/cosign-darwin-arm64" +checksum = "sha256:5cf948c2f4dfe59687bdd0b8523709067383e03982cc543475c8a7dc70e92a76" +url = "https://github.com/sigstore/cosign/releases/download/v3.1.3/cosign-darwin-arm64" +url_api = "https://api.github.com/repos/sigstore/cosign/releases/assets/503286205" provenance = "cosign" [tools."aqua:sigstore/cosign"."platforms.macos-x64"] -checksum = "sha256:14d2678dfbfde18798151e86fbd91ebdadbb7424b18412a42a155dd8a2df4c7a" -url = "https://github.com/sigstore/cosign/releases/download/v3.1.1/cosign-darwin-amd64" +checksum = "sha256:2347488e5d5b25336644024dfeca5601b190e91197a71a917bda44744aff106c" +url = "https://github.com/sigstore/cosign/releases/download/v3.1.3/cosign-darwin-amd64" +url_api = "https://api.github.com/repos/sigstore/cosign/releases/assets/503286177" provenance = "cosign" [[tools."aqua:sqlc-dev/sqlc"]] @@ -175,38 +282,42 @@ backend = "aqua:vektra/mockery" [tools."aqua:vektra/mockery"."platforms.linux-arm64"] checksum = "sha256:27bcb1d78a4e78488aaa9f90864f6281456f12a92548d5deb0e0af719478f418" url = "https://github.com/vektra/mockery/releases/download/v3.7.1/mockery_3.7.1_Linux_arm64.tar.gz" +url_api = "https://api.github.com/repos/vektra/mockery/releases/assets/445750704" [tools."aqua:vektra/mockery"."platforms.linux-x64"] checksum = "sha256:79e6a7374d444f7e47aab79a30993ec403d0a573212c2c707c4bffede5f1780f" url = "https://github.com/vektra/mockery/releases/download/v3.7.1/mockery_3.7.1_Linux_x86_64.tar.gz" +url_api = "https://api.github.com/repos/vektra/mockery/releases/assets/445750702" [tools."aqua:vektra/mockery"."platforms.macos-arm64"] checksum = "sha256:ec94a61c80e55bcfa868c59b51a662dcb47e451ef041f3f274630b58a2647fb1" url = "https://github.com/vektra/mockery/releases/download/v3.7.1/mockery_3.7.1_Darwin_arm64.tar.gz" +url_api = "https://api.github.com/repos/vektra/mockery/releases/assets/445750703" [tools."aqua:vektra/mockery"."platforms.macos-x64"] checksum = "sha256:ccd4aed104cf886e4049b4ba5fafdf340a839c48b9d1b093927513051cc4df57" url = "https://github.com/vektra/mockery/releases/download/v3.7.1/mockery_3.7.1_Darwin_x86_64.tar.gz" +url_api = "https://api.github.com/repos/vektra/mockery/releases/assets/445750705" [[tools.go]] -version = "1.26.4" +version = "1.26.6" backend = "core:go" [tools.go."platforms.linux-arm64"] -checksum = "sha256:ef758ae7c6cf9267c9c0ef080b8965f453d89ab2d25d9eb22de4405925238768" -url = "https://dl.google.com/go/go1.26.4.linux-arm64.tar.gz" +checksum = "sha256:d0507e9e9d7fe012aae570108cbd76c15de879e17130ab8cb90d4d7445cb1f2e" +url = "https://dl.google.com/go/go1.26.6.linux-arm64.tar.gz" [tools.go."platforms.linux-x64"] -checksum = "sha256:1153d3d50e0ac764b447adfe05c2bcf08e889d42a02e0fe0259bd47f6733ad7f" -url = "https://dl.google.com/go/go1.26.4.linux-amd64.tar.gz" +checksum = "sha256:708effb774be8237570d0add163225abbdfaf4fca28b2611df167beba4feef89" +url = "https://dl.google.com/go/go1.26.6.linux-amd64.tar.gz" [tools.go."platforms.macos-arm64"] -checksum = "sha256:b62ad2b6d7d2464f12a5bcad7ff47f19d08325773b5efd21610e445a05a9bf53" -url = "https://dl.google.com/go/go1.26.4.darwin-arm64.tar.gz" +checksum = "sha256:2dc95ce4675829f2df0e86b28bcef3283635902062a5f0580ca659bf570f3204" +url = "https://dl.google.com/go/go1.26.6.darwin-arm64.tar.gz" [tools.go."platforms.macos-x64"] -checksum = "sha256:05dc9b5f9997744520aaebb3d5deaa7c755371aebbfb7f97c2511a9f3367538d" -url = "https://dl.google.com/go/go1.26.4.darwin-amd64.tar.gz" +checksum = "sha256:08b65a63f244115121ced6c3b55ad38d801a7442acad5c949a17aad84ae6d684" +url = "https://dl.google.com/go/go1.26.6.darwin-amd64.tar.gz" [[tools.python]] version = "3.14.3" diff --git a/mise.toml b/mise.toml index f4e7d93..f0bfc60 100644 --- a/mise.toml +++ b/mise.toml @@ -16,7 +16,7 @@ [tools] # Language runtimes (core backend). -go = "1.26.4" +go = "1.26.6" python = "3.14.3" # Go dev-tool CLIs. Explicit `aqua:` refs force the VERIFYING backend (checksum + @@ -30,17 +30,19 @@ python = "3.14.3" # Docs (MkDocs) toolchain — moved off moon's former unstable_python/unstable_uv. "aqua:astral-sh/uv" = "0.11.0" -# Build / CI / release / supply-chain CLIs. moon itself is now mise-managed (CI -# installs it via mise instead of moonrepo/setup-toolchain). melange/apko/cosign -# are consumed by the container build + image signing in the release workflow. +# Build / CI / release / supply-chain CLIs. The reusable release unit resolves +# these exact tools through mise and refuses unlocked replacements. "aqua:moonrepo/moon" = "2.3.5" -"aqua:chainguard-dev/melange" = "0.54.0" -"aqua:chainguard-dev/apko" = "1.2.19" -"aqua:sigstore/cosign" = "3.1.1" +"aqua:goreleaser/goreleaser" = "2.17.1" +"aqua:cli/cli" = "2.97.0" +"aqua:anchore/syft" = "1.51.0" +"aqua:sigstore/cosign" = "3.1.3" +"aqua:chainguard-dev/melange" = "0.59.1" +"aqua:chainguard-dev/apko" = "1.2.37" [env] # Pin the Go toolchain: never auto-download a toolchain other than the one above. -# The version here is authoritative and matches go.mod's `go 1.26.4`. +# The version here is authoritative and matches go.mod's `go 1.26.6`. GOTOOLCHAIN = "local" [settings] @@ -50,26 +52,36 @@ GOTOOLCHAIN = "local" lockfile = true locked = true -# Local container image for the day-one stack. CI builds the release image via the -# melange/apko jobs in .github/workflows/release.yml; these tasks are the local -# equivalent. melange needs Linux, so `--runner docker` makes `image-local` work on -# macOS too (Docker Desktop runs the Linux build container). +# Local equivalent of the reusable OCI build's package-and-compose path. +# Go builds the canonical Linux binary; melange packages those exact bytes. [tasks.image-local] description = "Build the apko image for the host arch and load it into Docker as template-go-api:dev" run = ''' set -euo pipefail -arch="$(go env GOARCH)" -rm -rf packages image.tar +go_arch="$(go env GOARCH)" +case "$go_arch" in + amd64) apk_arch="x86_64" ;; + arm64) apk_arch="aarch64" ;; + *) echo "unsupported host architecture: $go_arch" >&2; exit 1 ;; +esac + +rm -rf .image-local packages image.tar +mkdir -p .image-local +commit="$(git rev-parse --short HEAD 2>/dev/null || echo none)" +build_date="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +CGO_ENABLED=0 GOOS=linux GOARCH="$go_arch" go build -trimpath \ + -ldflags="-s -w -buildid= -X main.version=dev -X main.commit=$commit -X main.date=$build_date" \ + -o .image-local/application ./cmd/template-go-api + melange keygen melange.rsa -printf 'version: "dev"\ncommit: "%s"\ndate: "%s"\n' \ - "$(git rev-parse --short HEAD 2>/dev/null || echo none)" \ - "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > .melange-vars.local.yaml -melange build melange.yaml --arch "$arch" --signing-key melange.rsa --runner docker \ - --source-dir . --vars-file .melange-vars.local.yaml -apko build apko.yaml template-go-api:dev image.tar --arch "$arch" --keyring-append ./melange.rsa.pub +printf '{"version":"0.0.0"}\n' > .melange-vars.local.json +melange build melange.yaml --arch "$apk_arch" --signing-key melange.rsa --runner docker \ + --source-dir .image-local --out-dir packages --vars-file .melange-vars.local.json +apko build apko.yaml template-go-api:dev image.tar --arch "$go_arch" \ + --repository-append ./packages --keyring-append ./melange.rsa.pub docker load < image.tar -docker tag "template-go-api:dev-$arch" template-go-api:dev -echo "loaded template-go-api:dev (host arch $arch)" +docker tag "template-go-api:dev-$go_arch" template-go-api:dev +echo "loaded template-go-api:dev (host arch $go_arch)" ''' [tasks.stack-up] diff --git a/moon.yml b/moon.yml index c54ed03..76ededa 100644 --- a/moon.yml +++ b/moon.yml @@ -49,7 +49,6 @@ fileGroups: - 'mise.lock' releaseConfig: - '.goreleaser.yaml' - - 'ghd.toml' - 'release-please-config.json' - '.release-please-manifest.json' - '.github/workflows/release*.yml' diff --git a/release-please-config.json b/release-please-config.json index 858924c..bdbd0b3 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -10,8 +10,7 @@ "packages": { ".": { "package-name": "template-go-api", - "changelog-path": "CHANGELOG.md", - "extra-files": ["melange.yaml", "apko.yaml"] + "changelog-path": "CHANGELOG.md" } }, "changelog-sections": [