From 21cb14262db1ab64f0f90e49893eed8b1b406a55 Mon Sep 17 00:00:00 2001 From: abrown38 Date: Fri, 9 Oct 2026 14:56:08 +0000 Subject: [PATCH] Publish release candidates to a testing apt distribution Pre-releases (versions with a hyphen) now upload their .debs to the "testing" distribution instead of "stable", so servers following stable never get an RC from apt upgrade. An RC can take its release notes from its final version's section or Unreleased; the workflow checks the notes with 'gitea-release.sh notes'. Also fix publishing RCs, whose .deb names carry the Debian version (0.2.0~rc.1) and were rejected. Co-Authored-By: Claude Opus 5.5 --- .gitea/workflows/release.yml | 10 +++-- CHANGELOG.md | 12 ++++++ docs/development.md | 31 ++++++++++++++-- docs/install.md | 11 ++++++ scripts/gitea-release.sh | 71 ++++++++++++++++++++++++++++++------ 5 files changed, 116 insertions(+), 19 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index adc04de..1355526 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -2,7 +2,9 @@ # is pushed: # * a Gitea release with the CHANGELOG notes, the linux tarballs, the CLI # for Linux, macOS and Windows, the .debs and SHA256SUMS; -# * the .debs (amd64, arm64) in the owner's Debian package registry. +# * the .debs (amd64, arm64) in the owner's Debian package registry: +# distribution "stable", or "testing" for a pre-release (a version +# with a hyphen), which Gitea also marks as a pre-release. # # Needs a repository secret RELEASE_TOKEN: an access token of a user who # can write to the repository, with the write:repository and write:package @@ -30,8 +32,10 @@ jobs: echo "::error::tag $GITHUB_REF_NAME does not match VERSION ($version)" exit 1 fi - if ! grep -q "^## \[$version\]" CHANGELOG.md; then - echo "::error::CHANGELOG.md has no '## [$version]' section" + # The release notes (a pre-release may use its final version's + # section or Unreleased; see scripts/gitea-release.sh). + if ! scripts/gitea-release.sh notes > /dev/null; then + echo "::error::no release notes for $version in CHANGELOG.md" exit 1 fi diff --git a/CHANGELOG.md b/CHANGELOG.md index dd760af..ed4a653 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,20 @@ All notable changes to Jarl are documented here. The format follows and prints the steps to install Jarl and adopt the configuration (docs/development.md). +### Changed + +- Release candidates (tags like `v0.2.0-rc.1`) publish their `.deb`s to a + separate `testing` distribution of the package registry instead of + `stable`. Servers that follow `stable` can't pick up an RC with + `apt upgrade`. An RC may also take its release notes from the + `## [0.2.0]` or `## [Unreleased]` section of the CHANGELOG instead of + needing its own (docs/development.md, "Release candidates"). + ### Fixed +- Publishing a release candidate failed: `scripts/gitea-release.sh` + rejected its `.deb`s, whose names carry the Debian version + (`0.2.0~rc.1`) instead of `0.2.0-rc.1`. - `jarld import` no longer warns that a zone with `allow-update { none; }` uses dynamic updates (validation already allowed it). - docs/cli.md gives the CLI configuration file's location on macOS diff --git a/docs/development.md b/docs/development.md index cd2b2c1..2f1021f 100644 --- a/docs/development.md +++ b/docs/development.md @@ -185,7 +185,8 @@ files are embedded at build time, so rebuild jarld after changes. ## Releasing 1. Move the "Unreleased" section of `CHANGELOG.md` under - `## [] - `. + `## [] - `. (A release candidate may skip this; see + below.) 2. Put the version in `VERSION`. 3. `make test && JARL_BIND_ROOT=... make e2e` (CI runs only `make test`; it has no BIND). @@ -198,9 +199,31 @@ amd64 and arm64) and runs `scripts/gitea-release.sh`. That creates the Gitea release (the CHANGELOG section as notes; the tarballs, the CLI for Linux, macOS and Windows, the `.deb`s and `SHA256SUMS` attached) and uploads the `.deb`s to the owner's Debian registry, distribution `stable`, -component `main` ([install.md](install.md#from-the-package-registry)). A -version with a hyphen (`1.0.0-rc.1`) becomes a pre-release; its packages -get version `1.0.0~rc.1`, which apt sorts before `1.0.0`. +component `main` ([install.md](install.md#from-the-package-registry)). + +### Release candidates + +A version with a hyphen (`0.2.0-rc.1`, tag `v0.2.0-rc.1`) is a +pre-release: + +* **On Gitea** it is marked "Pre-Release". Gitea doesn't count it as the + latest release: the repository page, `/releases/latest` and the API's + latest release keep pointing at the last stable one. +* **The `.deb`s go to the `testing` distribution**, not `stable`. Their + version is `0.2.0~rc.1`, which apt sorts after `0.1.0` but before + `0.2.0`. So in `stable` an RC would reach every server on its next + `apt upgrade`. Servers that follow only `stable` never see RCs. A test + machine that also lists `testing` gets each RC, and then the final + `0.2.0` from `stable`, which supersedes it. +* **Release notes**: its own `## [0.2.0-rc.1]` section if there is one; + otherwise the section of the version it leads up to (`## [0.2.0]`), or + else `## [Unreleased]`. So you can tag RCs while the changes are still + under Unreleased, and move them under `## [0.2.0]` for the real release. + A stable release still needs its own section. + +`scripts/gitea-release.sh notes` prints the notes a release would get +(`VERSION=0.2.0-rc.1 scripts/gitea-release.sh notes`), and the workflow +runs it as a check before building. Pushing the same tag again (`git tag -f`, `git push -f`) re-runs it, and the script replaces the attachments and packages, so a failed release can diff --git a/docs/install.md b/docs/install.md index 0a2239b..464fdbf 100644 --- a/docs/install.md +++ b/docs/install.md @@ -60,6 +60,17 @@ sudo apt update sudo apt install jarl ``` +Release candidates (`0.2.0-rc.1`) are published to a separate `testing` +distribution, so the `stable` line above only ever installs releases. To +try release candidates on a test machine, add `testing` as well. apt then +installs the newest of either, which is the RC until the final release +replaces it: + +```sh +echo "deb [signed-by=/etc/apt/keyrings/jarl.asc] https://gitea.adbraun.lol/api/packages/adam/debian testing main" \ + | sudo tee -a /etc/apt/sources.list.d/jarl.list +``` + The packages are as visible as the Gitea account that owns them, not the repository: with a public account, apt needs no login even if the repository is private. For a private or limited account, give apt a token diff --git a/scripts/gitea-release.sh b/scripts/gitea-release.sh index 54fd97e..e3d65e4 100755 --- a/scripts/gitea-release.sh +++ b/scripts/gitea-release.sh @@ -11,22 +11,75 @@ # Run by .gitea/workflows/release.yml when a v* tag is pushed. Running it # again for the same version replaces the attachments and packages. # +# A version with a hyphen (0.2.0-rc.1) is a pre-release: Gitea marks the +# release so and doesn't count it as the latest, and its packages go to +# the "testing" distribution, so servers that follow "stable" never get +# a pre-release from apt. +# +# Release notes: the CHANGELOG section "## [VERSION]". A pre-release +# without one of its own uses the section of the version it leads up to +# ("## [0.2.0]"), or else "## [Unreleased]". +# +# 'gitea-release.sh notes' only prints the notes, failing if there are +# none (the workflow checks this before building). +# # Variables: # GITEA_URL e.g. https://gitea.example.com (in Actions: # GITHUB_SERVER_URL) # GITEA_REPO owner/name (in Actions: GITHUB_REPOSITORY) # GITEA_TOKEN a token with write:repository and write:package # VERSION default: the VERSION file -# DEB_DISTRIBUTION the apt distribution (default: stable) +# DEB_DISTRIBUTION the apt distribution (default: stable, testing for +# a pre-release) # DEB_COMPONENT the apt component (default: main) # # Needs curl and jq. set -eu +VERSION=${VERSION:-$(cat VERSION)} +prerelease=false +case $VERSION in *-*) prerelease=true ;; esac + +# section prints the CHANGELOG section headed "## [$1]" (without the +# heading); nothing if there is none. +section() { + awk -v v="$1" ' + /^## \[/ { if (on) exit; on = index($0, "## [" v "]") == 1; next } + on' CHANGELOG.md +} + +# The release notes (see above). A section with only blank lines counts +# as missing. +notes=$(section "$VERSION") +from="[$VERSION]" +if [ "$prerelease" = true ]; then + for s in "${VERSION%%-*}" Unreleased; do + [ -z "$(printf '%s' "$notes" | tr -d '[:space:]')" ] || break + notes=$(section "$s") + from="[$s]" + done +fi +if [ -z "$(printf '%s' "$notes" | tr -d '[:space:]')" ]; then + if [ "$prerelease" = true ]; then + echo "gitea-release: CHANGELOG.md has no '## [$VERSION]', '## [${VERSION%%-*}]' or '## [Unreleased]' section with notes" >&2 + else + echo "gitea-release: CHANGELOG.md has no '## [$VERSION]' section with notes" >&2 + fi + exit 1 +fi +if [ "${1:-}" = notes ]; then + echo "gitea-release: notes for $VERSION from CHANGELOG.md $from" >&2 + printf '%s\n' "$notes" + exit 0 +fi + : "${GITEA_URL:=${GITHUB_SERVER_URL:-}}" "${GITEA_REPO:=${GITHUB_REPOSITORY:-}}" : "${GITEA_URL:?set GITEA_URL}" "${GITEA_REPO:?set GITEA_REPO}" "${GITEA_TOKEN:?set GITEA_TOKEN}" -VERSION=${VERSION:-$(cat VERSION)} -DEB_DISTRIBUTION=${DEB_DISTRIBUTION:-stable} +if [ "$prerelease" = true ]; then + DEB_DISTRIBUTION=${DEB_DISTRIBUTION:-testing} +else + DEB_DISTRIBUTION=${DEB_DISTRIBUTION:-stable} +fi DEB_COMPONENT=${DEB_COMPONENT:-main} GITEA_URL=${GITEA_URL%/} owner=${GITEA_REPO%%/*} @@ -51,21 +104,15 @@ call() { files=$(find dist -maxdepth 1 -type f ! -name SHA256SUMS | sort) [ -n "$files" ] || { echo "gitea-release: nothing in dist/" >&2; exit 1; } +# (Debian package versions have ~ for -: jarl_0.2.0~rc.1_amd64.deb.) +debversion=$(printf '%s' "$VERSION" | tr - '~') for f in $files; do - case $f in *"$VERSION"*) ;; *) echo "gitea-release: $f is not version $VERSION" >&2; exit 1 ;; esac + case $f in *"$VERSION"* | *"$debversion"*) ;; *) echo "gitea-release: $f is not version $VERSION" >&2; exit 1 ;; esac done # shellcheck disable=SC2046 # file names have no spaces (cd dist && sha256sum $(printf '%s\n' "$files" | sed 's|^dist/||')) > dist/SHA256SUMS files="$files dist/SHA256SUMS" -# The release notes: the CHANGELOG section for this version. -notes=$(awk -v v="$VERSION" ' - /^## \[/ { if (on) exit; on = index($0, "## [" v "]") == 1; next } - on' CHANGELOG.md) -[ -n "$notes" ] || notes="See CHANGELOG.md." -prerelease=false -case $VERSION in *-*) prerelease=true ;; esac - # The release (created, or found when publishing again). release=$(call 404 "$api/releases/tags/$tag") id=$(printf '%s\n' "$release" | jq -r '.id // empty')