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 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-09 14:56:08 +00:00
1 parent b22ffee1bb
commit 21cb14262d
5 files changed
+116 -19

No files matched your search

+7 -3
View File
@@ -2,7 +2,9 @@
# is pushed: # is pushed:
# * a Gitea release with the CHANGELOG notes, the linux tarballs, the CLI # * a Gitea release with the CHANGELOG notes, the linux tarballs, the CLI
# for Linux, macOS and Windows, the .debs and SHA256SUMS; # 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 # 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 # 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)" echo "::error::tag $GITHUB_REF_NAME does not match VERSION ($version)"
exit 1 exit 1
fi fi
if ! grep -q "^## \[$version\]" CHANGELOG.md; then # The release notes (a pre-release may use its final version's
echo "::error::CHANGELOG.md has no '## [$version]' section" # 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 exit 1
fi fi
+12
View File
@@ -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 and prints the steps to install Jarl and adopt the configuration
(docs/development.md). (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 ### 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; }` - `jarld import` no longer warns that a zone with `allow-update { none; }`
uses dynamic updates (validation already allowed it). uses dynamic updates (validation already allowed it).
- docs/cli.md gives the CLI configuration file's location on macOS - docs/cli.md gives the CLI configuration file's location on macOS
+27 -4
View File
@@ -185,7 +185,8 @@ files are embedded at build time, so rebuild jarld after changes.
## Releasing ## Releasing
1. Move the "Unreleased" section of `CHANGELOG.md` under 1. Move the "Unreleased" section of `CHANGELOG.md` under
`## [<version>] - <date>`. `## [<version>] - <date>`. (A release candidate may skip this; see
below.)
2. Put the version in `VERSION`. 2. Put the version in `VERSION`.
3. `make test && JARL_BIND_ROOT=... make e2e` (CI runs only `make test`; 3. `make test && JARL_BIND_ROOT=... make e2e` (CI runs only `make test`;
it has no BIND). 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 Gitea release (the CHANGELOG section as notes; the tarballs, the CLI for
Linux, macOS and Windows, the `.deb`s and `SHA256SUMS` attached) and Linux, macOS and Windows, the `.deb`s and `SHA256SUMS` attached) and
uploads the `.deb`s to the owner's Debian registry, distribution `stable`, uploads the `.deb`s to the owner's Debian registry, distribution `stable`,
component `main` ([install.md](install.md#from-the-package-registry)). A component `main` ([install.md](install.md#from-the-package-registry)).
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`. ### 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 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 the script replaces the attachments and packages, so a failed release can
+11
View File
@@ -60,6 +60,17 @@ sudo apt update
sudo apt install jarl 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 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: with a public account, apt needs no login even if the
repository is private. For a private or limited account, give apt a token repository is private. For a private or limited account, give apt a token
+59 -12
View File
@@ -11,22 +11,75 @@
# Run by .gitea/workflows/release.yml when a v* tag is pushed. Running it # Run by .gitea/workflows/release.yml when a v* tag is pushed. Running it
# again for the same version replaces the attachments and packages. # 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: # Variables:
# GITEA_URL e.g. https://gitea.example.com (in Actions: # GITEA_URL e.g. https://gitea.example.com (in Actions:
# GITHUB_SERVER_URL) # GITHUB_SERVER_URL)
# GITEA_REPO owner/name (in Actions: GITHUB_REPOSITORY) # GITEA_REPO owner/name (in Actions: GITHUB_REPOSITORY)
# GITEA_TOKEN a token with write:repository and write:package # GITEA_TOKEN a token with write:repository and write:package
# VERSION default: the VERSION file # 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) # DEB_COMPONENT the apt component (default: main)
# #
# Needs curl and jq. # Needs curl and jq.
set -eu 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:=${GITHUB_SERVER_URL:-}}" "${GITEA_REPO:=${GITHUB_REPOSITORY:-}}"
: "${GITEA_URL:?set GITEA_URL}" "${GITEA_REPO:?set GITEA_REPO}" "${GITEA_TOKEN:?set GITEA_TOKEN}" : "${GITEA_URL:?set GITEA_URL}" "${GITEA_REPO:?set GITEA_REPO}" "${GITEA_TOKEN:?set GITEA_TOKEN}"
VERSION=${VERSION:-$(cat VERSION)} if [ "$prerelease" = true ]; then
DEB_DISTRIBUTION=${DEB_DISTRIBUTION:-stable} DEB_DISTRIBUTION=${DEB_DISTRIBUTION:-testing}
else
DEB_DISTRIBUTION=${DEB_DISTRIBUTION:-stable}
fi
DEB_COMPONENT=${DEB_COMPONENT:-main} DEB_COMPONENT=${DEB_COMPONENT:-main}
GITEA_URL=${GITEA_URL%/} GITEA_URL=${GITEA_URL%/}
owner=${GITEA_REPO%%/*} owner=${GITEA_REPO%%/*}
@@ -51,21 +104,15 @@ call() {
files=$(find dist -maxdepth 1 -type f ! -name SHA256SUMS | sort) files=$(find dist -maxdepth 1 -type f ! -name SHA256SUMS | sort)
[ -n "$files" ] || { echo "gitea-release: nothing in dist/" >&2; exit 1; } [ -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 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 done
# shellcheck disable=SC2046 # file names have no spaces # shellcheck disable=SC2046 # file names have no spaces
(cd dist && sha256sum $(printf '%s\n' "$files" | sed 's|^dist/||')) > dist/SHA256SUMS (cd dist && sha256sum $(printf '%s\n' "$files" | sed 's|^dist/||')) > dist/SHA256SUMS
files="$files 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). # The release (created, or found when publishing again).
release=$(call 404 "$api/releases/tags/$tag") release=$(call 404 "$api/releases/tags/$tag")
id=$(printf '%s\n' "$release" | jq -r '.id // empty') id=$(printf '%s\n' "$release" | jq -r '.id // empty')