Releases publish only two things per tool now: a current-stable
canonical symlink and an immutable per-version file. No more channel
mirrors (_stable/_beta/_alpha) and no more partial-version pins
(_v<X.Y>, _v<X>) — those were debt from a release model that never
matched the project's actual usage.
The `./build beta` verb stays, but narrowed: it's an internal SHA
snapshot for the BMC dev chart pipeline (chart's appVersion pins to
"<X.Y.Z>-beta-<sha>" and the chart Dockerfile fetches that SHA from
git). No public artifact on /srv/zddc/releases/. The embedded/* +
chore commit produced by `./build beta` is the actual snapshot.
`./build alpha` is removed entirely.
build/build-lib.sh:
- Drop alpha verb; narrow beta verb to embedded regen + chore commit
- promote_release: stable cut writes <tool>_v<X.Y.Z>.html + <tool>.html
symlink + <tool>.html.sig companion symlink; beta is a no-op
- promote_zddc_server: same shape — per-version binary +
per-platform canonical symlink (zddc-server_<plat>) + .sig symlink
- write_zddc_server_stub: singular; emits per-version stubs +
one canonical zddc-server.html for current stable
- Delete _promote_channel, verify_channel_links, _channel_is_active
- Seed-from-live now copies only per-version files + .sig + pubkey.pem
(the canonical symlinks get rewritten by this cut; old layout files
get cleaned by deploy's --delete-after)
- build_releases_index: dropdown simplified to "latest stable +
pinned versions"; channels-explainer section removed; tool cards +
CTA URLs point at canonical <tool>.html / zddc-server_<plat>;
composer emits "stable" sentinel for `apps:` entries
- Fix the acl:{allow:[...]} footgun in the apps_pubkey example
apps.go:
- isValidChannelOrVersion: accept only "stable" + exact X.Y.Z
(drop alpha/beta and partial pins v0.0/v0)
- normalizeChannel: same
- Resolve URL composition: stable → canonical <prefix>/<app>.html
(no _stable_ suffix), exact-version → <prefix>/<app>_v<X.Y.Z>.html
- Tests rewritten to match (beta/alpha replaced with v0.0.4 / stable;
a new TestParseSpec_RejectsLegacyChannelsAndPartialPins locks in
that the removed forms now error)
browse/build.sh: gate promote_release on $is_release like every other
tool's build.sh (longstanding inconsistency that errored under the new
promote_release case-statement).
freshen-channel: deleted (no channels to freshen).
Net: -254 lines, all green on full `go test ./...`. Dev build verified
via `./build` (no-arg) — new label format "v<next>-dev · <ts> · <sha>".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
614 lines
25 KiB
Bash
Executable file
614 lines
25 KiB
Bash
Executable file
#!/bin/sh
|
|
# =============================================================================
|
|
# ZDDC shared build helpers — sourced by each tool's build.sh
|
|
#
|
|
# Usage in a tool build.sh:
|
|
# root_dir=$(cd "$(dirname "$0")" && pwd)
|
|
# . "$root_dir/../shared/build-lib.sh"
|
|
#
|
|
# Provides:
|
|
# ensure_exists <path> — abort with error if file missing
|
|
# concat_files <file ...> — cat each relative path under $root_dir
|
|
# build_timestamp — ISO UTC timestamp string, set at source time;
|
|
# used as build_label for dev builds
|
|
# escape_js_close_tags <in> <out>
|
|
# — copy <in> to <out> with all '</' rewritten as
|
|
# '<\/' so the HTML parser cannot misread the
|
|
# inlined JS as containing a closing </script>.
|
|
# The JS engine treats \/ as a regular slash,
|
|
# so runtime behaviour is unchanged.
|
|
# compute_build_label <tool> [--release [<beta-or-version>]]
|
|
# — sets globals: build_label, build_version,
|
|
# is_release, is_red, channel.
|
|
# See "Release args" below.
|
|
# promote_release <tool> — for stable cuts, copy the dist HTML into
|
|
# the release-output bundle (default
|
|
# $root_dir/../dist/release-output;
|
|
# override $ZDDC_DEPLOY_RELEASES_DIR).
|
|
# Writes the immutable per-version file
|
|
# <tool>_v<X.Y.Z>.html plus the canonical
|
|
# symlink <tool>.html pointing at it.
|
|
# Tagging is centralized in the top-level
|
|
# ./build (after the embedded commit).
|
|
# Beta cuts produce NO public artifact —
|
|
# they are an internal SHA snapshot for
|
|
# the BMC dev chart pipeline; the
|
|
# embedded/* regeneration + chore commit
|
|
# in the top-level ./build is the actual
|
|
# artifact (chart appVersion pins to that
|
|
# SHA, Dockerfile fetches it from git).
|
|
#
|
|
# Release args:
|
|
# <none> dev build, tool/dist/ only, label
|
|
# "v<next-stable>-dev · <ts> · <sha>[-dirty]" (red).
|
|
# No release-output side-effect.
|
|
# --release stable cut, auto-bump patch from latest tag (or 0.0.1).
|
|
# Writes <tool>_v<X.Y.Z>.html + <tool>.html symlink;
|
|
# tagged later by ./build.
|
|
# --release X.Y.Z stable cut, explicit version.
|
|
# --release beta internal SHA snapshot for the BMC dev chart. Build
|
|
# label is "v<next-stable>-beta · <date> · <sha>";
|
|
# no public artifact, no tag. The top-level ./build
|
|
# regenerates zddc/internal/apps/embedded/ + commits.
|
|
# --release <other> error.
|
|
# =============================================================================
|
|
|
|
# Abort if root_dir is not set by the caller
|
|
if [ -z "${root_dir:-}" ]; then
|
|
echo "build-lib.sh: root_dir must be set before sourcing this file" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# NOTE: there's no Codeberg release-asset publication path anymore. All
|
|
# release artifacts (HTML tools + zddc-server binaries) materialize in
|
|
# dist/release-output/ via the lockstep ./build, then ./deploy rsyncs
|
|
# them to /srv/zddc/ on the deploy host. The deprecated zddc/release.sh
|
|
# is now a no-op guard that prints a redirection message.
|
|
|
|
# Fail hard on any missing source file
|
|
ensure_exists() {
|
|
_path="$1"
|
|
if [ ! -f "$_path" ]; then
|
|
echo "error: missing file: $_path" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Concatenate files listed as positional args, each relative to root_dir
|
|
concat_files() {
|
|
for _rel do
|
|
ensure_exists "$root_dir/$_rel"
|
|
cat "$root_dir/$_rel"
|
|
printf '\n'
|
|
done
|
|
}
|
|
|
|
# ISO UTC build timestamp — set once when this file is sourced
|
|
build_timestamp=$(date -u +"%Y-%m-%d %H:%M:%S")
|
|
|
|
# Read shared/favicon.svg, base64-encode it, and assemble a data: URI suitable
|
|
# for inlining into a <link rel="icon" type="image/svg+xml" href="..."> tag.
|
|
# Set once at source time so every tool's build.sh can pick it up via the
|
|
# $favicon_data_uri global. Editing shared/favicon.svg auto-propagates to all
|
|
# tools on the next build.
|
|
_favicon_path="$root_dir/../shared/favicon.svg"
|
|
if [ -f "$_favicon_path" ]; then
|
|
favicon_data_uri="data:image/svg+xml;base64,$(base64 -w 0 "$_favicon_path")"
|
|
else
|
|
favicon_data_uri=""
|
|
fi
|
|
|
|
# Rewrite '</script' (case-insensitive) in JS as '<\/script' so the HTML parser
|
|
# cannot mistake string contents for a closing </script> tag. Per the HTML5 spec
|
|
# only </script terminates a <script> block — other tags like </div> are safe
|
|
# inside a script's text content. Narrowly targeting </script avoids corrupting
|
|
# regex literals like /</g whose trailing letter is a flag, not a tag name.
|
|
# The JS engine treats '\/' the same as '/' inside a string, so behaviour is
|
|
# unchanged. See ARCHITECTURE.md "HTML Embedding Safety".
|
|
escape_js_close_tags() {
|
|
sed 's#</\([sS][cC][rR][iI][pP][tT]\)#<\\/\1#g' "$1" > "$2"
|
|
}
|
|
|
|
# Validate that $1 is a strict X.Y.Z numeric version, where each component
|
|
# is a non-empty numeric string. Exits with an error if not.
|
|
_validate_semver() {
|
|
_v="$1"
|
|
_bad() {
|
|
echo "error: invalid release argument: '$_v' (expected: beta, or X.Y.Z stable version)" >&2
|
|
exit 1
|
|
}
|
|
_v1="${_v%%.*}"
|
|
_rest="${_v#*.}"
|
|
[ "$_rest" = "$_v" ] && _bad
|
|
_v2="${_rest%%.*}"
|
|
_v3="${_rest#*.}"
|
|
{ [ "$_v3" = "$_rest" ] || [ "$_v3" != "${_v3%.*}" ]; } && _bad
|
|
case "$_v1" in '' | *[!0-9]*) _bad ;; esac
|
|
case "$_v2" in '' | *[!0-9]*) _bad ;; esac
|
|
case "$_v3" in '' | *[!0-9]*) _bad ;; esac
|
|
}
|
|
|
|
# Walk backwards from HEAD until a non-auto-commit is found, return the
|
|
# resolved git ref (e.g. "HEAD~2"). Auto-commits are recognised by their
|
|
# canonical commit-message prefixes:
|
|
#
|
|
# - "chore(embedded): cut v<X.Y.Z>-beta" (beta auto-commit, build:993)
|
|
# - "release: v<X.Y.Z> lockstep" (stable auto-commit, build:986)
|
|
#
|
|
# Used by the build-label helpers to derive a stable identifier (short
|
|
# SHA, three-word slug) from the underlying source state. The source
|
|
# ref is invariant across the auto-commit step (HEAD shifts when
|
|
# embedded bytes change), so a re-run on the same source state produces
|
|
# the same identifiers and no spurious commit.
|
|
#
|
|
# Defensive cap: stops walking after 32 commits and returns whatever
|
|
# ref was reached.
|
|
_source_commit_ref() {
|
|
_i=0
|
|
_ref="HEAD"
|
|
while [ "$_i" -lt 32 ]; do
|
|
_msg=$(git -C "$root_dir" log -1 --format=%s "$_ref" 2>/dev/null || echo "")
|
|
case "$_msg" in
|
|
"chore(embedded): cut v"* | "release: v"*" lockstep")
|
|
_ref="${_ref}~1"
|
|
_i=$((_i + 1))
|
|
continue
|
|
;;
|
|
esac
|
|
break
|
|
done
|
|
echo "$_ref"
|
|
}
|
|
|
|
# Short SHA of the underlying source commit (skipping past embedded
|
|
# auto-commits — see _source_commit_ref). Same source state → same SHA
|
|
# even after a `chore(embedded): cut …` commit has landed on top.
|
|
_source_commit_short_sha() {
|
|
_ref=$(_source_commit_ref)
|
|
git -C "$root_dir" rev-parse --short=7 "$_ref" 2>/dev/null || echo "unknown"
|
|
}
|
|
|
|
# Compute build label and channel. Reads positional args:
|
|
# compute_build_label <tool_name> [--release [<beta-or-version>]]
|
|
# Sets global variables:
|
|
# build_label — text rendered into the page's {{BUILD_LABEL}} slot
|
|
# build_version — bare semver string (stable releases only)
|
|
# is_release — "1" for any --release invocation, else "0"
|
|
# is_red — "1" if the label should render red+bold (dev/beta), else "0"
|
|
# channel — "stable" / "beta" / "dev"
|
|
#
|
|
# Versioning: pre-release semver. The next-stable target is computed from
|
|
# the latest clean tool-vX.Y.Z tag (patch-bump). Plain dev builds and
|
|
# `--release beta` carry the next-stable target as a pre-release suffix
|
|
# in the on-page label so users can see which stable the snapshot is
|
|
# working toward. Stable releases write a clean vX.Y.Z label and tag.
|
|
#
|
|
# HTML tools do NOT tag beta cuts — beta produces no public artifact
|
|
# (the chart pins by SHA via appVersion). Plain dev builds and beta
|
|
# cuts share the same on-page label format (full UTC timestamp + short
|
|
# source SHA). A plain dev build may carry a "-dirty" SHA suffix when
|
|
# the working tree has uncommitted changes; release cuts don't.
|
|
compute_build_label() {
|
|
_tool="$1"
|
|
_flag="${2:-}"
|
|
_arg="${3:-}"
|
|
|
|
is_release=0
|
|
is_red=1
|
|
channel=""
|
|
build_version=""
|
|
|
|
# Compute the next-stable target once for label inclusion.
|
|
_next_stable=$(_next_stable_for_tool "$_tool")
|
|
|
|
if [ "$_flag" != "--release" ]; then
|
|
# Plain builds are dev iteration — tool/dist/ only, no release
|
|
# output. The label includes the next-stable target so a developer
|
|
# opening the local dist file can see which version-in-progress
|
|
# they're looking at. Full timestamp + dirty marker distinguish
|
|
# iterative dev builds from formal cuts.
|
|
_sha=$(git -C "$root_dir" rev-parse --short=7 HEAD 2>/dev/null || echo "unknown")
|
|
if ! git -C "$root_dir" diff --quiet HEAD 2>/dev/null; then
|
|
_sha="${_sha}-dirty"
|
|
fi
|
|
channel="dev"
|
|
build_label="v${_next_stable}-dev · ${build_timestamp} · ${_sha}"
|
|
_emit_build_label_sidecar "$_tool"
|
|
return 0
|
|
fi
|
|
|
|
is_release=1
|
|
|
|
case "$_arg" in
|
|
beta)
|
|
channel="beta"
|
|
# Internal SHA snapshot for the BMC dev chart. The chart's
|
|
# appVersion gets set to "<next>-beta-<sha>" and the
|
|
# Dockerfile parses the suffix to fetch this SHA from git.
|
|
# _source_commit_short_sha walks past any `chore(embedded):
|
|
# cut …` auto-commit at HEAD so a re-cut on unchanged
|
|
# source produces the same SHA.
|
|
_sha=$(_source_commit_short_sha)
|
|
build_label="v${_next_stable}-beta · ${build_timestamp} · ${_sha}"
|
|
_emit_build_label_sidecar "$_tool"
|
|
return 0
|
|
;;
|
|
'')
|
|
# Stable cut, auto-bump patch.
|
|
build_version="$_next_stable"
|
|
;;
|
|
*)
|
|
_validate_semver "$_arg"
|
|
build_version="$_arg"
|
|
;;
|
|
esac
|
|
|
|
channel="stable"
|
|
is_red=0
|
|
build_label="v${build_version}"
|
|
_emit_build_label_sidecar "$_tool"
|
|
}
|
|
|
|
# Write the resolved build label to a sidecar file the top-level build.sh
|
|
# reads to assemble zddc/internal/apps/embedded/versions.txt. No-op when
|
|
# BUILD_LABELS_DIR is not set in the env (tools built standalone).
|
|
_emit_build_label_sidecar() {
|
|
if [ -z "${BUILD_LABELS_DIR:-}" ]; then
|
|
return 0
|
|
fi
|
|
mkdir -p "$BUILD_LABELS_DIR"
|
|
printf '%s\n' "$build_label" > "$BUILD_LABELS_DIR/$1.label"
|
|
}
|
|
|
|
# Tools that participate in the lockstep release. Source of truth — used
|
|
# by helpers that enumerate "all release artifacts" (matrix render,
|
|
# coordinated next-stable).
|
|
ZDDC_RELEASE_TOOLS="archive transmittal classifier landing form tables browse zddc-server"
|
|
|
|
# Compute the next-stable target for a single tool — patch-bump of its own
|
|
# latest <tool>-vX.Y.Z tag. Used by compute_build_label so a tool's
|
|
# on-page label reads against its own history (e.g. a beta cut for a
|
|
# tool that's been quiet still labels itself targeting that tool's next
|
|
# stable, even when the lockstep convention is in force).
|
|
_next_stable_for_tool() {
|
|
_t="$1"
|
|
_latest=$(git -C "$root_dir" tag --list "${_t}-v*" 2>/dev/null \
|
|
| grep -E "^${_t}-v[0-9]+\.[0-9]+\.[0-9]+\$" \
|
|
| sed "s|^${_t}-v||" \
|
|
| sort -V \
|
|
| tail -1)
|
|
[ -n "$_latest" ] || _latest="0.0.0"
|
|
_major="${_latest%%.*}"
|
|
_rest="${_latest#*.}"
|
|
_minor="${_rest%%.*}"
|
|
_patch="${_rest#*.}"
|
|
echo "${_major}.${_minor}.$((_patch + 1))"
|
|
}
|
|
|
|
# Compute the coordinated next-stable target across every release artifact
|
|
# (5 HTML tools + zddc-server). Used by the top-level build.sh on
|
|
# `--release` (no explicit version) to enforce lockstep — every tool cuts
|
|
# at the same version even if it hasn't changed. Picks max(latest tag
|
|
# across all tools) + patch bump, so a tool at v0.0.2 jumps straight to
|
|
# wherever the leader is + 1 the first time the lockstep rule fires.
|
|
_coordinated_next_stable() {
|
|
_max="0.0.0"
|
|
for _t in $ZDDC_RELEASE_TOOLS; do
|
|
_latest=$(git -C "$root_dir" tag --list "${_t}-v*" 2>/dev/null \
|
|
| grep -E "^${_t}-v[0-9]+\.[0-9]+\.[0-9]+\$" \
|
|
| sed "s|^${_t}-v||" \
|
|
| sort -V \
|
|
| tail -1)
|
|
[ -n "$_latest" ] || continue
|
|
# sort -V picks the larger of two semvers
|
|
_max=$(printf '%s\n%s\n' "$_max" "$_latest" | sort -V | tail -1)
|
|
done
|
|
_major="${_max%%.*}"
|
|
_rest="${_max#*.}"
|
|
_minor="${_rest%%.*}"
|
|
_patch="${_rest#*.}"
|
|
echo "${_major}.${_minor}.$((_patch + 1))"
|
|
}
|
|
|
|
# Promote a built dist file to the release-output bundle. Reads from caller
|
|
# scope: $channel ("stable" / "beta"), $build_version (stable only),
|
|
# $output_html, $root_dir. Bundle path resolves from $ZDDC_DEPLOY_RELEASES_DIR
|
|
# (default $root_dir/../dist/release-output).
|
|
#
|
|
# Stable cuts:
|
|
# 1. Copy dist HTML → <bundle>/<tool>_v<X.Y.Z>.html (immutable).
|
|
# 2. Refresh canonical symlink: <bundle>/<tool>.html → the new versioned file.
|
|
# 3. Tag the commit <tool>-v<X.Y.Z> (centralized in the top-level ./build).
|
|
#
|
|
# Beta cuts:
|
|
# No public artifact. The chart's Dockerfile fetches the source at the
|
|
# SHA pinned in chart appVersion and compiles its own binary; the
|
|
# embedded/* regeneration + chore commit in the top-level ./build is
|
|
# the actual snapshot.
|
|
#
|
|
# Plain dev builds (no --release): never call promote_release.
|
|
promote_release() {
|
|
_tool="$1"
|
|
# The top-level `./build` exports $ZDDC_DEPLOY_RELEASES_DIR pointing
|
|
# at $SCRIPT_DIR/dist/release-output. Single-tool standalone
|
|
# invocations fall back to the same default — no inheritance from a
|
|
# parent build run.
|
|
_releases_dir="${ZDDC_DEPLOY_RELEASES_DIR:-$root_dir/../dist/release-output}"
|
|
mkdir -p "$_releases_dir"
|
|
|
|
if [ ! -d "$_releases_dir" ]; then
|
|
echo "promote_release: $_releases_dir not found" >&2
|
|
return 1
|
|
fi
|
|
|
|
case "$channel" in
|
|
stable)
|
|
if [ -z "$build_version" ]; then
|
|
echo "promote_release: stable channel but no build_version" >&2
|
|
return 1
|
|
fi
|
|
# Lockstep: every release cut writes per-version files for
|
|
# every tool, even when a tool's source hasn't changed since
|
|
# its last tag. The bytes are identical (build is deterministic
|
|
# at the same source), so the overwrite is a no-op on disk;
|
|
# but the canonical symlink <tool>.html advances to the new
|
|
# version, which is the actual goal.
|
|
_promote_stable "$_tool" "$build_version" "$_releases_dir"
|
|
;;
|
|
beta)
|
|
# Internal SHA snapshot for the BMC dev chart. No public
|
|
# artifact: the chart fetches the source at the SHA via git,
|
|
# the embedded/* regeneration + chore commit (in the top-
|
|
# level ./build) IS the artifact.
|
|
echo " ${_tool}: beta is internal (no public artifact)"
|
|
;;
|
|
*)
|
|
echo "promote_release: unknown channel '$channel'" >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Stable cut: per-version immutable file + canonical symlink. Tagging is
|
|
# centralized in the top-level ./build (it commits embedded artifacts
|
|
# FIRST, then tags at the new commit — see "Release commit + tag" block
|
|
# at the bottom of the script). _promote_stable historically created
|
|
# tags itself, but that placed them on the source-side commit before
|
|
# embedded files were folded in, leaving prod binaries with stale bytes
|
|
# baked in.
|
|
_promote_stable() {
|
|
_t="$1"
|
|
_ver="$2"
|
|
_rdir="$3"
|
|
|
|
_versioned="${_t}_v${_ver}.html"
|
|
_canonical="${_t}.html"
|
|
|
|
cp "$output_html" "$_rdir/$_versioned"
|
|
echo "Wrote $_rdir/$_versioned"
|
|
|
|
ln -sfn "$_versioned" "$_rdir/$_canonical"
|
|
echo " $_canonical → $_versioned"
|
|
|
|
# Companion .sig symlink so `curl <canonical>.sig` resolves. The
|
|
# actual .sig file is written by sign_release_artifacts; this
|
|
# symlink points there.
|
|
ln -sfn "${_versioned}.sig" "$_rdir/${_canonical}.sig"
|
|
|
|
# Pre-flight check only: if the tag already exists pointing at a
|
|
# commit that is NOT an ancestor of HEAD, the operator needs to
|
|
# resolve manually before this cut can complete cleanly.
|
|
_tag="${_t}-v${_ver}"
|
|
if git -C "$root_dir" rev-parse -q --verify "refs/tags/$_tag" >/dev/null; then
|
|
_existing=$(git -C "$root_dir" rev-list -n 1 "$_tag")
|
|
_head=$(git -C "$root_dir" rev-parse HEAD)
|
|
if [ "$_existing" != "$_head" ] \
|
|
&& ! git -C "$root_dir" merge-base --is-ancestor "$_existing" "$_head"; then
|
|
echo "promote_release: tag $_tag exists at $_existing which is not in HEAD's history" >&2
|
|
echo " manual intervention required before re-running" >&2
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
echo "Released ${_t} v${_ver} (stable; tagging deferred to top-level build)"
|
|
}
|
|
|
|
# Platforms zddc-server is cross-compiled for. The first three are
|
|
# extension-less (Linux/macOS); Windows gets .exe. The build always emits
|
|
# all four; the matrix cell's stub page links each by its <platform> tag.
|
|
ZDDC_SERVER_PLATFORMS="linux-amd64 darwin-amd64 darwin-arm64 windows-amd64"
|
|
|
|
# Display label for the stub-page download list. Keeps the binary-asset
|
|
# names canonical even if we later add e.g. linux-arm64.
|
|
_zddc_server_platform_label() {
|
|
case "$1" in
|
|
linux-amd64) echo "Linux (x86_64)" ;;
|
|
darwin-amd64) echo "macOS (Intel)" ;;
|
|
darwin-arm64) echo "macOS (Apple Silicon)" ;;
|
|
windows-amd64) echo "Windows (x86_64)" ;;
|
|
*) echo "$1" ;;
|
|
esac
|
|
}
|
|
|
|
# Resolve a zddc-server binary's filename for one (slug, platform).
|
|
# Returns the bare name (no path); ".exe" suffix on windows. Empty slug
|
|
# means the canonical "current stable" symlink (zddc-server_<plat>);
|
|
# non-empty slug is a per-version asset (zddc-server_v<X.Y.Z>_<plat>).
|
|
_zddc_server_binary_name() {
|
|
_slug="$1"
|
|
_plat="$2"
|
|
_suffix=""
|
|
case "$_plat" in *windows*) _suffix=".exe" ;; esac
|
|
if [ -z "$_slug" ]; then
|
|
printf 'zddc-server_%s%s' "$_plat" "$_suffix"
|
|
else
|
|
printf 'zddc-server_%s_%s%s' "$_slug" "$_plat" "$_suffix"
|
|
fi
|
|
}
|
|
|
|
# Write the small HTML index page that becomes the entry point for a
|
|
# zddc-server release. Lists each platform binary with a download link.
|
|
# $1 — release directory (absolute)
|
|
# $2 — slug ("" for canonical "current stable", or "v0.0.8" per-version)
|
|
# $3 — display label (e.g. "current stable", "v0.0.8")
|
|
write_zddc_server_stub() {
|
|
_rdir="$1"
|
|
_slug="$2"
|
|
_label="$3"
|
|
if [ -z "$_slug" ]; then
|
|
_out="$_rdir/zddc-server.html"
|
|
else
|
|
_out="$_rdir/zddc-server_${_slug}.html"
|
|
fi
|
|
|
|
{
|
|
cat <<HEAD
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>zddc-server ${_label} — ZDDC</title>
|
|
<link rel="stylesheet" href="../css/style.css">
|
|
<style>
|
|
.dl-table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
|
|
.dl-table th, .dl-table td { text-align: left; padding: 0.5rem 0.75rem; border-bottom: 1px solid var(--color-border); }
|
|
.dl-table a { color: var(--color-primary); text-decoration: none; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.9rem; }
|
|
.dl-table a:hover { text-decoration: underline; }
|
|
.breadcrumb { color: var(--color-text-muted); margin-bottom: 1rem; font-size: 0.9rem; }
|
|
.breadcrumb a { color: var(--color-text-muted); }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main class="container" style="max-width: 720px; margin: 2rem auto;">
|
|
<p class="breadcrumb"><a href="/">home</a> / <a href="index.html">releases</a> / zddc-server ${_label}</p>
|
|
<h1>zddc-server — ${_label}</h1>
|
|
<p>Cross-compiled binaries. Download for your platform, mark executable, and run with <code>ZDDC_ROOT=/path/to/archive ./zddc-server</code>.</p>
|
|
<table class="dl-table">
|
|
<thead><tr><th>Platform</th><th>Download</th></tr></thead>
|
|
<tbody>
|
|
HEAD
|
|
for _plat in $ZDDC_SERVER_PLATFORMS; do
|
|
_bin=$(_zddc_server_binary_name "$_slug" "$_plat")
|
|
_plabel=$(_zddc_server_platform_label "$_plat")
|
|
printf ' <tr><td>%s</td><td><a href="%s">%s</a></td></tr>\n' "$_plabel" "$_bin" "$_bin"
|
|
done
|
|
cat <<'TAIL'
|
|
</tbody>
|
|
</table>
|
|
<p style="font-size: 0.9rem; color: var(--color-text-muted);">Need a different platform? Build from source: <code>(cd zddc && go build -o zddc-server ./cmd/zddc-server)</code> from the repo at the matching tag.</p>
|
|
</main>
|
|
</body>
|
|
</html>
|
|
TAIL
|
|
} > "$_out"
|
|
}
|
|
|
|
# Refresh every zddc-server stub page based on what's currently in the
|
|
# release-output bundle: one per-version stub per zddc-server_v*_*
|
|
# binary set, plus a canonical zddc-server.html if the latest-stable
|
|
# symlinks are in place. Indexed off linux-amd64 since all four
|
|
# platforms ship in lockstep.
|
|
#
|
|
# $1 — releases dir (absolute)
|
|
write_zddc_server_stubs_all() {
|
|
_rdir="$1"
|
|
|
|
# Per-version stubs (immutable).
|
|
for _bin in "$_rdir"/zddc-server_v*_linux-amd64; do
|
|
[ -e "$_bin" ] || continue
|
|
_name=$(basename "$_bin")
|
|
_slug=$(echo "$_name" | sed -E 's/^zddc-server_(v[^_]+)_linux-amd64$/\1/')
|
|
case "$_slug" in
|
|
v*.*.*) write_zddc_server_stub "$_rdir" "$_slug" "$_slug" ;;
|
|
esac
|
|
done
|
|
|
|
# Canonical stub (follows the latest-stable symlink). Probes the
|
|
# linux-amd64 canonical name; if it exists, the platform symlinks
|
|
# are in place and we can write the entry page.
|
|
if [ -e "$_rdir/zddc-server_linux-amd64" ]; then
|
|
write_zddc_server_stub "$_rdir" "" "current stable"
|
|
fi
|
|
}
|
|
|
|
# Promote a freshly-cross-compiled set of zddc-server binaries to the
|
|
# release-output bundle. Called by the top-level ./build on a stable
|
|
# release cut. Beta cuts produce no public artifact (the chart's
|
|
# Dockerfile compiles from source at the SHA pinned in appVersion).
|
|
#
|
|
# $1 — channel ("stable" | "beta")
|
|
# $2 — version (X.Y.Z; required for stable; ignored for beta)
|
|
# $3 — releases dir (absolute)
|
|
# $4 — dist dir holding cross-compiled binaries (absolute)
|
|
promote_zddc_server() {
|
|
_ch="$1"
|
|
_ver="$2"
|
|
_rdir="$3"
|
|
_dist="$4"
|
|
|
|
# Verify all four binaries exist before doing anything destructive.
|
|
for _plat in $ZDDC_SERVER_PLATFORMS; do
|
|
_suffix=""
|
|
case "$_plat" in *windows*) _suffix=".exe" ;; esac
|
|
_src="$_dist/zddc-server-${_plat}${_suffix}"
|
|
if [ ! -f "$_src" ]; then
|
|
echo "promote_zddc_server: missing source binary $_src" >&2
|
|
return 1
|
|
fi
|
|
done
|
|
|
|
case "$_ch" in
|
|
stable)
|
|
if [ -z "$_ver" ]; then
|
|
echo "promote_zddc_server: stable cut requires version" >&2
|
|
return 1
|
|
fi
|
|
|
|
# Per-version immutable + canonical per-platform symlink.
|
|
for _plat in $ZDDC_SERVER_PLATFORMS; do
|
|
_suffix=""
|
|
case "$_plat" in *windows*) _suffix=".exe" ;; esac
|
|
_src="$_dist/zddc-server-${_plat}${_suffix}"
|
|
_versioned="zddc-server_v${_ver}_${_plat}${_suffix}"
|
|
_canonical="zddc-server_${_plat}${_suffix}"
|
|
cp "$_src" "$_rdir/$_versioned"
|
|
echo "Wrote $_rdir/$_versioned"
|
|
ln -sfn "$_versioned" "$_rdir/$_canonical"
|
|
echo " $_canonical → $_versioned"
|
|
# Companion .sig symlink — see _promote_stable for the
|
|
# same pattern.
|
|
ln -sfn "${_versioned}.sig" "$_rdir/${_canonical}.sig"
|
|
done
|
|
|
|
# Pre-flight tag check only — actual tagging happens in the
|
|
# top-level ./build after embedded artifacts are committed.
|
|
# See _promote_stable for the same rationale.
|
|
_tag="zddc-server-v${_ver}"
|
|
if git -C "$root_dir" rev-parse -q --verify "refs/tags/$_tag" >/dev/null; then
|
|
_existing=$(git -C "$root_dir" rev-list -n 1 "$_tag")
|
|
_head=$(git -C "$root_dir" rev-parse HEAD)
|
|
if [ "$_existing" != "$_head" ] \
|
|
&& ! git -C "$root_dir" merge-base --is-ancestor "$_existing" "$_head"; then
|
|
echo "promote_zddc_server: tag $_tag exists at $_existing which is not in HEAD's history" >&2
|
|
echo " manual intervention required before re-running" >&2
|
|
return 1
|
|
fi
|
|
fi
|
|
echo "Released zddc-server v${_ver} (stable; tagging deferred to top-level build)"
|
|
;;
|
|
beta)
|
|
# Internal SHA snapshot — the chart's Dockerfile fetches the
|
|
# source at that SHA and compiles its own binary. No public
|
|
# binary is published.
|
|
echo " zddc-server: beta is internal (no public artifact)"
|
|
;;
|
|
*)
|
|
echo "promote_zddc_server: unknown channel '$_ch'" >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
# Refresh stub pages (per-version + canonical).
|
|
write_zddc_server_stubs_all "$_rdir"
|
|
}
|