ZDDC/release-image.sh
ZDDC 94591397cf build: pre-release semver for alpha/beta channels
Replace the build-counter version scheme (every alpha push monotonically
bumps the patch number, producing immutable :0.0.X tags that look
indistinguishable from stable releases) with proper semver pre-release
suffixes. Stable owns clean vX.Y.Z; alpha and beta carry
vX.Y.Z-{alpha,beta}[.N] indicating the next-stable target.

The next-stable target is the patch-bump of the latest clean
<prefix>-vX.Y.Z tag. Counter N is per-channel (alpha and beta count
separately) and resets when a new stable advances next-patch. Used
only for zddc-server image tags, where every release is git-tagged;
HTML tools omit the counter since alpha/beta cuts there don't tag.

release-image.sh:
- New CLI: sh release-image.sh [alpha|beta|stable] [<version>].
- Default channel alpha. Version arg only valid (and only optional)
  for stable.
- Auto-derives the version via next_prerelease for alpha/beta, and
  patch-bump for unspecified stable.
- Now creates the git tag itself (the auto-derived version is no
  longer something the operator can predict in advance), but does
  not push — operator finishes with `git push --tags`.

shared/build-lib.sh:
- Add next_prerelease(channel, tag_prefix) helper.
- compute_build_label embeds v<next-stable>-{alpha,beta} in the
  on-page label for plain and --release alpha|beta builds.
- Plain builds: v<next-stable>-alpha · <ts> · <sha>[-dirty]
  --release alpha: v<next-stable>-alpha · <date> · <sha>
  --release beta:  v<next-stable>-beta · <date> · <sha>
  --release [<version>]: v<X.Y.Z> (clean stable, unchanged shape).

Pre-release semver ordering (vX.Y.Z-alpha.1 < vX.Y.Z-alpha.2 <
vX.Y.Z-beta.1 < vX.Y.Z) is honored by registry tag sorting,
git tag --sort=-v:refname, sort -V, npm, cargo — so consumers can
pin or compare versions without surprises.

Existing zddc-server-v0.0.{3..7} git tags and registry tags are
audit history; not rewritten. Going forward, alpha/beta cuts produce
v0.0.8-{alpha,beta}.N format, and clean v0.0.8 is reserved for a
deliberate stable promotion.

freshen-channel needs no code change. It runs --release <channel>
inside a worktree at the latest stable tag, where the build-lib.sh
at that tag is still the old version producing old-format labels;
the first stable cut after this commit will propagate the new format
to subsequent freshens (per the existing "build pipeline at the tag"
reproducibility policy).

AGENTS.md and CLAUDE.md updated.
2026-04-29 17:32:35 -05:00

181 lines
6.4 KiB
Bash
Executable file

#!/bin/sh
# release-image.sh — build the zddc-server runtime image locally and push it
# to codeberg.org/varasys/zddc-server with cascading channel tags.
#
# Usage:
# sh release-image.sh # alpha cut, version auto-derived
# sh release-image.sh alpha # same
# sh release-image.sh beta # beta cut, version auto-derived
# sh release-image.sh stable # stable cut, patch++ from latest stable
# sh release-image.sh stable 0.1.0 # stable cut, explicit version
#
# Channel cascade rules (unchanged from earlier):
# alpha → :<version> :alpha
# beta → :<version> :beta :alpha
# stable → :<version> :stable :beta :alpha
#
# Versioning: pre-release semver. Stable releases own clean vX.Y.Z; alpha
# and beta carry vX.Y.Z-alpha.N / vX.Y.Z-beta.N suffixes, where X.Y.Z is
# the next patch of the latest clean stable tag and N is a per-channel
# counter that resets when stable advances.
#
# The script does ONE thing the old version did not: it `git tag`s the
# release before pushing the image, since auto-derivation means the
# operator can no longer predict the version up-front. The tag is pushed
# to origin only if the operator runs `git push --tags` afterwards
# (intentional — the script never pushes git history on its own).
#
# Prerequisites:
# - podman (or docker — commands are identical)
# - logged in to codeberg.org: `podman login codeberg.org`
# (Codeberg username + a token with `package:write` scope, generated at
# https://codeberg.org/user/settings/applications)
#
# What it does:
# 1. Compute the version (auto for alpha/beta, auto-or-explicit for stable).
# 2. Refresh dist/web by running sh build.sh — the Containerfile's
# server stage COPYs those files in.
# 3. Tag the current commit zddc-server-v<version>.
# 4. Build zddc/Containerfile's `server` stage as a single local image.
# 5. Tag and push each cascade tag.
set -eu
usage() {
cat >&2 <<'EOF'
usage: release-image.sh [alpha|beta|stable] [<version>]
alpha (default) cut alpha, version auto-derived from the latest
clean stable tag (vX.Y.Z + patch++ + -alpha.N).
beta cut beta, version auto-derived (vX.Y.Z + -beta.N).
stable cut stable. Without <version>, patch-bump from the
latest clean stable tag. With <version>, use it
verbatim (must be a clean X.Y.Z).
EOF
exit 1
}
CHANNEL="${1:-alpha}"
case "$CHANNEL" in
alpha | beta | stable) ;;
-h | --help) usage ;;
*) echo "error: unknown channel '$CHANNEL'" >&2; usage ;;
esac
EXPLICIT_VERSION="${2:-}"
if [ -n "$EXPLICIT_VERSION" ] && [ "$CHANNEL" != "stable" ]; then
echo "error: an explicit <version> is only valid with the 'stable' channel" >&2
echo " alpha and beta versions are auto-derived." >&2
exit 1
fi
REPO="codeberg.org/varasys/zddc-server"
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
TAG_PREFIX="zddc-server-v"
# Source build-lib.sh so we can call next_prerelease for alpha/beta and
# share the validation helpers used by the HTML tools.
root_dir="$SCRIPT_DIR"
. "$SCRIPT_DIR/shared/build-lib.sh"
# --- Determine the version --------------------------------------------------
case "$CHANNEL" in
alpha | beta)
VERSION=$(next_prerelease "$CHANNEL" "$TAG_PREFIX")
;;
stable)
if [ -n "$EXPLICIT_VERSION" ]; then
_validate_semver "$EXPLICIT_VERSION"
VERSION="$EXPLICIT_VERSION"
else
# Auto-bump patch from the latest clean stable tag.
_latest=$(git -C "$SCRIPT_DIR" tag --list "${TAG_PREFIX}*" 2>/dev/null \
| grep -E "^${TAG_PREFIX}[0-9]+\.[0-9]+\.[0-9]+\$" \
| sed "s|^${TAG_PREFIX}||" \
| sort -V \
| tail -1)
[ -n "$_latest" ] || _latest="0.0.0"
_major="${_latest%%.*}"
_rest="${_latest#*.}"
_minor="${_rest%%.*}"
_patch="${_rest#*.}"
VERSION="${_major}.${_minor}.$((_patch + 1))"
fi
;;
esac
case "$CHANNEL" in
alpha) TAGS="$VERSION alpha" ;;
beta) TAGS="$VERSION beta alpha" ;;
stable) TAGS="$VERSION stable beta alpha" ;;
esac
# Pick podman or docker, whichever is on PATH.
if command -v podman >/dev/null 2>&1; then
OCI=podman
elif command -v docker >/dev/null 2>&1; then
OCI=docker
else
echo "error: neither podman nor docker found on PATH" >&2
exit 1
fi
GIT_TAG="${TAG_PREFIX}${VERSION}"
echo "=== Building $REPO ==="
echo "Channel: $CHANNEL"
echo "Version: $VERSION"
echo "Git tag: $GIT_TAG"
echo "Image tags: $TAGS"
echo "OCI CLI: $OCI"
echo
# --- Refresh HTML dist (Containerfile COPYs from dist/web) ------------------
sh "$SCRIPT_DIR/build.sh"
# --- Tag the commit (idempotent: skip if the tag already points here) -------
if git -C "$SCRIPT_DIR" rev-parse -q --verify "refs/tags/$GIT_TAG" >/dev/null; then
_existing=$(git -C "$SCRIPT_DIR" rev-list -n 1 "$GIT_TAG")
_head=$(git -C "$SCRIPT_DIR" rev-parse HEAD)
if [ "$_existing" != "$_head" ]; then
echo "error: tag $GIT_TAG already exists at $_existing, but HEAD is $_head" >&2
echo " refusing to overwrite. Resolve manually." >&2
exit 1
fi
echo "(tag $GIT_TAG already at HEAD)"
else
git -C "$SCRIPT_DIR" tag "$GIT_TAG"
echo "tagged $GIT_TAG (run 'git push --tags' to publish)"
fi
# --- Build + push -----------------------------------------------------------
"$OCI" build --target server -t zddc-server:build "$SCRIPT_DIR/zddc/"
echo
echo "=== Pushing tags ==="
for tag in $TAGS; do
"$OCI" tag zddc-server:build "$REPO:$tag"
"$OCI" push "$REPO:$tag"
echo "pushed $REPO:$tag"
done
echo
echo "=== Done ==="
echo "Image: $REPO:$VERSION"
echo "Cascading tags: $TAGS"
echo "Git tag: $GIT_TAG (publish with: git push origin $GIT_TAG)"
echo
case "$CHANNEL" in
stable)
echo "Reminder (channel discipline rule 4): freshen alpha + beta now"
echo "so users tracking those channels are not on stale code:"
echo " ./freshen-channel <tool> alpha"
echo " ./freshen-channel <tool> beta"
echo
echo "Bump tnd-zddc-chart's ZDDC_SERVER_TAG (push to develop) so the"
echo "chart's CI rebuilds the dev-shell image and helm-rolls."
;;
beta)
echo "Beta cut. Soak before promoting to stable."
;;
esac