Releases: VERSION + automatic patch tags + promote; updater installs newer versions (node-26#49)
Tag release / tag (push) Successful in 8s
CI / lint (push) Successful in 8s
CI / test-op25 (push) Successful in 16s
CI / test (push) Successful in 49s
Build edge-node / build (push) Successful in 1m2s

VERSION holds MAJOR.MINOR (1.1), edited by hand. Every push to main is
tagged vMAJOR.MINOR.PATCH by the "Tag release" workflow, with PATCH counted
as an integer by scripts/next-version.sh. "Promote release" moves the major
tag (v1) to a tested release and refuses to move it backwards.

drb-update.sh: a newer release on the node's channel is now a reason to
update by itself. REF=latest follows the highest vX.Y.Z tag (test nodes);
REF=v1 follows the promoted tag (customer nodes). Versions compare per
field as integers (1.10.0 > 1.9.3, never string or float order). It never
downgrades, and an untagged target still needs a request. After an update
it writes DRB_VERSION to .env; the edge node reports it in checkin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Logan Cusano
2026-11-09 00:34:50 -05:00
co-authored by Claude Opus 5.5
parent 6330609a75
commit 5549736706
8 changed files with 234 additions and 9 deletions
+62 -8
View File
@@ -1,7 +1,10 @@
#!/usr/bin/env bash
#
# DRB node host updater (node-26#15). Runs as root from systemd:
# drb-update.timer -> every ~15 min: HTTPS check-in with C2; update if asked
# drb-update.timer -> every ~15 min: HTTPS check-in with C2; update if asked,
# or if the configured channel has a newer release
# (node-26#49: REF=latest follows the highest vX.Y.Z
# tag, REF=v1 follows the promoted major tag)
# drb-update.path -> fires when the edge-node container drops
# <install>/configs/update-requested (MQTT node_update)
# Both start drb-update.service, which runs this script. install-updater.sh
@@ -26,6 +29,42 @@
# very file mid-run cannot change what is executing.
set -euo pipefail
# ── Release versions (node-26#49) ─────────────────────────────────────────
# Versions are MAJOR.MINOR.PATCH and every comparison is per field, as
# integers: 1.10.0 > 1.9.3. Never string or float order ("1.10" < "1.9" as a
# float, "1.10.0" < "1.9.3" as a string).
semver_sort() { sort -t. -k1,1n -k2,2n -k3,3n; }
semver_gt() { # semver_gt A B -> true when A is newer than B
local a b i
IFS=. read -r -a a <<< "$1"; IFS=. read -r -a b <<< "$2"
for i in 0 1 2; do
(( 10#${a[i]:-0} > 10#${b[i]:-0} )) && return 0
(( 10#${a[i]:-0} < 10#${b[i]:-0} )) && return 1
done
return 1
}
version_at() { # highest X.Y.Z release tagged on a commit, or nothing
git tag --points-at "$1" 2>/dev/null | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \
| sed 's/^v//' | semver_sort | tail -1 || true
}
resolve_target() { # REF -> a ref name git can resolve, or fail
local ref="$1" top
if [ "$ref" = latest ]; then
top="$(git tag -l 'v*' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sed 's/^v//' | semver_sort | tail -1 || true)"
[ -n "$top" ] || return 1
echo "refs/tags/v$top"
elif git show-ref --verify -q "refs/remotes/origin/$ref"; then
echo "refs/remotes/origin/$ref"
elif git show-ref --verify -q "refs/tags/$ref"; then
echo "refs/tags/$ref"
else
return 1
fi
}
main() {
local CONF="${DRB_UPDATER_CONF:-/etc/drb/updater.conf}"
local STATE_DIR="${DRB_UPDATER_STATE:-/var/lib/drb-update}"
@@ -118,6 +157,22 @@ main() {
new_request="$requested"
reason="${reason:-C2 request $requested}"
fi
# A newer release on this node's channel is a reason on its own (node-26#49).
# Only ever forward, and only between tagged releases: an untagged target
# (a branch REF, or v1 before its first promotion) still needs a request.
if [ -z "$reason" ]; then
local origin_url target tv iv
origin_url="$(git remote get-url origin 2>/dev/null || true)"
if [ "$origin_url" = "$REPO_URL" ] \
&& git -c protocol.allow=never -c protocol.https.allow=always fetch -q --force --tags --prune origin \
&& target="$(resolve_target "$REF")"; then
tv="$(version_at "$(git rev-parse "$target^{commit}")")"
iv="$(version_at HEAD)"
if [ -n "$tv" ] && { [ -z "$iv" ] || semver_gt "$tv" "$iv"; }; then
reason="release $tv on $REF (installed ${iv:-untagged})"
fi
fi
fi
[ -n "$reason" ] || { log "no update requested"; return 0; }
# ── Update ─────────────────────────────────────────────────────────────
@@ -145,13 +200,7 @@ do_update() {
git -c protocol.allow=never -c protocol.https.allow=always fetch --force --tags --prune origin || return 1
local target
if git show-ref --verify -q "refs/remotes/origin/$REF"; then
target="refs/remotes/origin/$REF"
elif git show-ref --verify -q "refs/tags/$REF"; then
target="refs/tags/$REF"
else
err "ref '$REF' not found on origin"; return 1
fi
target="$(resolve_target "$REF")" || { err "ref '$REF' not found on origin"; return 1; }
local old new
old="$(git rev-parse HEAD)"; new="$(git rev-parse "$target^{commit}")"
@@ -206,6 +255,11 @@ do_update() {
drb_pin_image_tags "$INSTALL_DIR/.env" "$INSTALL_DIR" "$new" || { err "could not write image pins to .env"; return 1; }
fi
# The release this node now runs, for its checkin (node-26#49).
local ver; ver="$(version_at "$new")"
sed -i '/^DRB_VERSION=/d' "$INSTALL_DIR/.env"
printf 'DRB_VERSION=%s\n' "${ver:-untagged}" >> "$INSTALL_DIR/.env"
if [ "$BUILD" = 1 ]; then
docker compose build || return 1 # tags the local builds with the same pins
fi
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# node-26#49 — the release tag this commit should get, or nothing if it has one.
#
# VERSION holds MAJOR.MINOR and is edited by hand. PATCH is automatic: one
# more than the highest existing vMAJOR.MINOR.PATCH tag, compared as an
# integer (so .10 follows .9), starting at 0 for a new MAJOR.MINOR.
#
# Usage: scripts/next-version.sh (from the repo root, tags fetched)
set -euo pipefail
base="$(tr -d ' \r\n' < VERSION)"
[[ "$base" =~ ^[0-9]+\.[0-9]+$ ]] || { echo "VERSION must be MAJOR.MINOR, got '$base'" >&2; exit 1; }
pattern="^v${base//./\\.}\\.[0-9]+$"
if git tag --points-at HEAD | grep -qE "$pattern"; then
exit 0 # this commit is already released
fi
last="$(git tag -l "v${base}.*" | grep -E "$pattern" | sed "s/^v${base}\.//" | sort -n | tail -1 || true)"
echo "v${base}.$(( ${last:--1} + 1 ))"