diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dbe013c..256c55b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -180,19 +180,25 @@ jobs: - name: Remove per-arch tags from GHCR # Side-effect tags for imagetools assembly only — not part of the public # version surface (deploy/docker-compose.yml pins X.Y.Z, not X.Y.Z-amd64). + # imagetools has no "rm" subcommand; delete via the GitHub Packages API. + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail - repo="ghcr.io/${{ github.repository }}" version="${{ needs.prepare.outputs.version }}" + owner="${{ github.repository_owner }}" + pkg="${{ github.event.repository.name }}" + api="/users/${owner}/packages/container/${pkg}/versions" for suffix in amd64 arm64; do - tag="${repo}:${version}-${suffix}" - set +e - out=$(docker buildx imagetools rm "$tag" 2>&1) - rc=$? - set -e - if [ "$rc" -eq 0 ]; then - printf '%s\n' "$out" - else - echo "::warning::could not remove ${tag}: ${out}" >&2 + tag="${version}-${suffix}" + mapfile -t ids < <(gh api "$api" --paginate \ + --jq ".[] | select([.metadata.container.tags[]] | index(\"${tag}\")) | .id") + if [ "${#ids[@]}" -eq 0 ]; then + echo "no GHCR package version for tag ${tag}" + continue fi + for id in "${ids[@]}"; do + echo "deleting GHCR package version ${id} (tag ${tag})" + gh api -X DELETE "${api}/${id}" + done done diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c0f1e1..c35e6ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,11 @@ Plex. Upgrading from 1.2.x is a tag bump; no migration. the same Resync on demand. The `internal/backup` package comment now matches this behaviour. +- ci (GHCR): per-arch package tags (`X.Y.Z-amd64`, `X.Y.Z-arm64`) are dropped + after the manifest merge via the GitHub Packages API. The merge job had called + `docker buildx imagetools rm`, which is not a valid subcommand — cleanup failed + with a warning and the side-effect tags stayed in the registry. + ### Changed - docs: operator and as-built docs aligned with the code after a full @@ -139,11 +144,14 @@ Plex. Upgrading from 1.2.x is a tag bump; no migration. bare git tag push no longer starts the build. `release.yml` listens for `release: published`, checks out that tag (not `main` HEAD), e2e-gates each native arch build, merges `X.Y.Z-amd64` and `X.Y.Z-arm64` into one manifest, - then removes the per-arch tags from GHCR so operators see only - `ghcr.io/mixeme/selfpost:X.Y.Z` (what `deploy/docker-compose.yml` pins). A - dispatch whose version input is missing or not `X.Y.Z` fails in `prepare`. - [development.md](docs/development.md) documents draft vs published releases - and why deleting a release tag converts it back to draft. + then removes the per-arch tags from GHCR via the GitHub Packages API so + operators see only `ghcr.io/mixeme/selfpost:X.Y.Z` (what + `deploy/docker-compose.yml` pins). A dispatch whose version input is missing + or not `X.Y.Z` fails in `prepare`. [development.md](docs/development.md) + documents draft vs published releases, why deleting a release tag converts + it back to draft, and Gitea → GitHub tag-mirror pitfalls (do not prune release + tags on GitHub; a mirrored `v1.0.0` still runs that tag's `on: push: tags` + workflow). - test: the authorization and sign-in surfaces that had no tests now have them. The login limiter is covered for its ceiling, its per-address scope, the reset diff --git a/docs/development.md b/docs/development.md index 3296ceb..382ded3 100644 --- a/docs/development.md +++ b/docs/development.md @@ -220,6 +220,27 @@ GHCR package versions, not the git tag. Push workflow and source changes to **github.com/mixeme/selfpost** before publishing — Actions reads that repo, not Gitea. +**Gitea → GitHub tag mirror.** If every tag push from Gitea is mirrored to +GitHub, two things follow: + +1. **GitHub Release tags must not be deleted on GitHub.** Many mirror setups + prune remote tags that are absent on Gitea (or re-push with `--force` / + `--prune`). Deleting `v1.0.0` / `v1.3.0` on GitHub converts a published + Release back to draft. Mirror **branches and new tags forward**; do not + delete release tags on the GitHub side. GHCR cleanup is package versions in + the UI — not `git push github --delete` and not tag prune on the mirror. + +2. **Tag push runs the workflow file at that tag's commit**, not `main`. `v1.0.0` + still points at a commit whose `release.yml` has `on: push: tags` and no + per-arch GHCR cleanup — every mirror (re)push of that tag can republish + `1.0.0-amd64` / `1.0.0-arm64`. Tags from `v1.3.0` onward only run + `release.yml` on **Publish release** (`release: published`), so mirroring + those tags alone does not start the image build. + + Safe mirror: push tags to GitHub without deleting existing ones; keep release + tags on Gitea; publish the GitHub Release on github.com after the mirror has + the tag. + Ordinary commits **do not** publish an image. The compose pin and the git tag must match (`1.0.0` / `v1.0.0` for the first published release). Intermediate CHANGELOG sections (`0.2.0`…`0.6.0`) record development history before that cut. @@ -324,7 +345,7 @@ prepare (version from release tag or workflow_dispatch input; checkout vX.Y.Z) → push ghcr.io/...:X.Y.Z-amd64 | X.Y.Z-arm64 → merge → docker buildx imagetools create → unified manifest X.Y.Z - → imagetools rm → drop X.Y.Z-amd64 and X.Y.Z-arm64 from GHCR + → GitHub Packages API → drop X.Y.Z-amd64 and X.Y.Z-arm64 from GHCR ``` Native per-arch matrix (no QEMU): running the full Postfix/OpenDKIM stack under