Files
steward/scripts/gitea-release.sh
T
abrown38andClaude Opus 5.5 3c0808591f Steward 0.1.0 (in development): a DHCPv4 server with a web UI, API and CLI
Subnets served per interface, pools, reservations, options at three
levels, leases in SQLite saved before the ACK, ping and decline conflict
checks, validated changes with history and undo, users and tokens, an
audit log, a Kea importer, a sandboxed systemd unit and Debian packages.
Tested with real udhcpc and dhcpcd clients in Docker (scripts/e2e.sh).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:14:07 +01:00

151 lines
5.7 KiB
Bash
Executable File

#!/bin/sh
# Publishes a built release (make dist, make cli-dist, make deb for each
# architecture) to Gitea:
#
# * a release for tag v<VERSION> with the CHANGELOG section for VERSION as
# its notes, and every file in dist/ (tarballs, CLI binaries, .debs)
# plus a SHA256SUMS of them attached;
# * the .debs in the owner's Debian package registry, so servers can
# 'apt install steward' and get updates with apt (docs/install.md).
#
# 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, 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}"
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%%/*}
tag=v$VERSION
api=$GITEA_URL/api/v1/repos/$GITEA_REPO
# curl with the token; fails on HTTP errors except the codes in $ok.
call() {
ok=$1
shift
out=$(mktemp)
code=$(curl -sS -o "$out" -w '%{http_code}' -H "Authorization: token $GITEA_TOKEN" "$@")
case " 200 201 204 $ok " in
*" $code "*) cat "$out"; rm -f "$out"; return 0 ;;
esac
echo "gitea-release: HTTP $code from $*" >&2
cat "$out" >&2
echo >&2
rm -f "$out"
return 1
}
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 -: steward_0.2.0~rc.1_amd64.deb.)
debversion=$(printf '%s' "$VERSION" | tr - '~')
for f in $files; do
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 (created, or found when publishing again).
release=$(call 404 "$api/releases/tags/$tag")
id=$(printf '%s\n' "$release" | jq -r '.id // empty')
if [ -z "$id" ]; then
body=$(jq -n --arg tag "$tag" --arg notes "$notes" --argjson pre "$prerelease" \
'{tag_name: $tag, name: ("Steward " + $tag), body: $notes, prerelease: $pre}')
release=$(call "" -X POST -H 'Content-Type: application/json' -d "$body" "$api/releases")
id=$(printf '%s\n' "$release" | jq -r .id)
echo "gitea-release: created release $tag"
fi
for f in $files; do
name=${f#dist/}
old=$(printf '%s\n' "$release" | jq -r --arg n "$name" '.assets[]? | select(.name == $n) | .id')
[ -z "$old" ] || call "" -X DELETE "$api/releases/$id/assets/$old" >/dev/null
call "" -X POST -F "attachment=@$f" "$api/releases/$id/assets?name=$name" >/dev/null
echo "gitea-release: attached $name"
done
# The Debian packages. Gitea refuses a package version it already has
# (409), so publishing again deletes it first.
pool=$GITEA_URL/api/packages/$owner/debian/pool/$DEB_DISTRIBUTION/$DEB_COMPONENT
for f in dist/*.deb; do
[ -e "$f" ] || continue
# shellcheck disable=SC2016,SC2046 # dpkg-deb's fields; split into "$@"
set -- $(dpkg-deb --showformat '${Package} ${Version} ${Architecture}' --show "$f")
if ! call 409 --upload-file "$f" "$pool/upload" >/dev/null; then
echo "gitea-release: upload of $f failed" >&2
exit 1
fi
if [ "$code" = 409 ]; then
call "" -X DELETE "$pool/$1/$2/$3" >/dev/null
call "" --upload-file "$f" "$pool/upload" >/dev/null
fi
echo "gitea-release: uploaded $1 $2 $3 to $DEB_DISTRIBUTION/$DEB_COMPONENT"
done