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')