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>
151 lines
5.7 KiB
Bash
Executable File
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
|