Renames build.sh → build and replaces the --release flag form with
subcommands:
./build cut alpha (default; active dev iteration)
./build beta cut beta (cascades alpha → beta)
./build release cut stable (coordinated next version)
./build release X.Y.Z cut stable at explicit version
./build help
The contract shift: there's no longer a "plain dev build that doesn't
touch channels" at the top level. Every full-stack build is a publish
action — running ./build IS active dev iteration, which is what alpha
already meant. To iterate on one tool without writing to the website
worktree, use the per-tool sh tool/build.sh (unchanged).
Output continues to land in ${ZDDC_DEPLOY_RELEASES_DIR:-$HOME/src/zddc-website/releases}
and nothing is pushed automatically. Commit + push the website branch
yourself when you want to publish. Stable cuts still tag locally on
main; tags push separately too.
Behind the scenes: the export of ZDDC_DEPLOY_RELEASES_DIR is moved
above the per-tool build.sh invocations so children inherit it. The
prior "if RELEASE_CHANNEL else write_zddc_server_stubs_all" branch is
collapsed since RELEASE_CHANNEL is always set under the new CLI.
Docs (CLAUDE.md, AGENTS.md, ARCHITECTURE.md, zddc/README.md) updated
to reference ./build everywhere; the per-tool sh tool/build.sh refs
stay (they're a separate, narrower entry point).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
651 lines
30 KiB
Bash
Executable file
651 lines
30 KiB
Bash
Executable file
#!/bin/sh
|
||
set -eu
|
||
|
||
# build — lockstep release driver for ZDDC.
|
||
#
|
||
# Three subcommands. The first arg names a channel and every invocation
|
||
# is a publish action — alpha is the default since "alpha = active dev
|
||
# iteration" and that's what running the build IS.
|
||
#
|
||
# ./build cut alpha (default; active dev iteration)
|
||
# ./build beta cut beta (cascades alpha → beta)
|
||
# ./build release cut stable (coordinated next version;
|
||
# cascades alpha + beta → stable)
|
||
# ./build release X.Y.Z cut stable at explicit version
|
||
# ./build help this message
|
||
#
|
||
# Lockstep: every cut bumps all six tools (5 HTML + zddc-server)
|
||
# together. The coordinated next-stable version is
|
||
# max(latest tag across all tools) + 1.
|
||
#
|
||
# Output goes to ${ZDDC_DEPLOY_RELEASES_DIR:-$HOME/src/zddc-website/releases}
|
||
# — the website branch's worktree, which is what Caddy serves. Nothing
|
||
# is pushed automatically; commit + push the website branch yourself
|
||
# when you want the changes mirrored to Codeberg.
|
||
|
||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||
|
||
# Source build-lib.sh once at the top level so the helpers it provides
|
||
# (promote_zddc_server, write_zddc_server_stubs_all, verify_channel_links,
|
||
# _coordinated_next_stable) are in scope. Each tool's build.sh sources it
|
||
# again — that's a no-op on already-defined functions.
|
||
root_dir="$SCRIPT_DIR"
|
||
. "$SCRIPT_DIR/shared/build-lib.sh"
|
||
|
||
# --- Parse subcommand ------------------------------------------------------
|
||
RELEASE_CHANNEL=""
|
||
RELEASE_VERSION=""
|
||
|
||
case "${1:-alpha}" in
|
||
alpha)
|
||
RELEASE_CHANNEL="alpha"
|
||
;;
|
||
beta)
|
||
RELEASE_CHANNEL="beta"
|
||
;;
|
||
release)
|
||
RELEASE_CHANNEL="stable"
|
||
if [ -n "${2:-}" ]; then
|
||
_validate_semver "$2"
|
||
RELEASE_VERSION="$2"
|
||
echo "=== Lockstep stable release — explicit version: v$RELEASE_VERSION ==="
|
||
else
|
||
RELEASE_VERSION=$(_coordinated_next_stable)
|
||
echo "=== Lockstep stable release — coordinated version: v$RELEASE_VERSION ==="
|
||
fi
|
||
;;
|
||
help | -h | --help)
|
||
sed -n '4,22p' "$0" | sed 's/^# \{0,1\}//'
|
||
exit 0
|
||
;;
|
||
*)
|
||
echo "build: unknown subcommand '$1'. Try './build help'." >&2
|
||
exit 1
|
||
;;
|
||
esac
|
||
|
||
# Per-tool argument list. The single-tool build.sh scripts still take
|
||
# the legacy `--release [channel|version]` form, so translate.
|
||
if [ "$RELEASE_CHANNEL" = "stable" ]; then
|
||
TOOL_RELEASE_ARGS="--release $RELEASE_VERSION"
|
||
else
|
||
TOOL_RELEASE_ARGS="--release $RELEASE_CHANNEL"
|
||
fi
|
||
|
||
# Deploy directory for release artifacts. The website lives in the
|
||
# orphan `website` branch served by Caddy from a fixed path; this dir
|
||
# is the worktree of that branch (default ~/src/zddc-website/releases).
|
||
# Override with $ZDDC_DEPLOY_RELEASES_DIR for testing or alternate
|
||
# deploy targets. Exported so child per-tool build.sh invocations see
|
||
# the same path.
|
||
export ZDDC_DEPLOY_RELEASES_DIR="${ZDDC_DEPLOY_RELEASES_DIR:-$HOME/src/zddc-website/releases}"
|
||
RELEASES_DIR="$ZDDC_DEPLOY_RELEASES_DIR"
|
||
mkdir -p "$RELEASES_DIR"
|
||
|
||
echo "=== Building ZDDC tools ==="
|
||
|
||
# Each tool's compute_build_label writes a sidecar `<tool>.label` here so
|
||
# we can assemble zddc/internal/apps/embedded/versions.txt below.
|
||
BUILD_LABELS_DIR="$SCRIPT_DIR/zddc/internal/apps/embedded/.labels"
|
||
rm -rf "$BUILD_LABELS_DIR"
|
||
mkdir -p "$BUILD_LABELS_DIR"
|
||
export BUILD_LABELS_DIR
|
||
|
||
# shellcheck disable=SC2086 # intentional word-splitting on TOOL_RELEASE_ARGS
|
||
sh "$SCRIPT_DIR/transmittal/build.sh" $TOOL_RELEASE_ARGS
|
||
sh "$SCRIPT_DIR/archive/build.sh" $TOOL_RELEASE_ARGS
|
||
sh "$SCRIPT_DIR/classifier/build.sh" $TOOL_RELEASE_ARGS
|
||
sh "$SCRIPT_DIR/mdedit/build.sh" $TOOL_RELEASE_ARGS
|
||
sh "$SCRIPT_DIR/landing/build.sh" $TOOL_RELEASE_ARGS
|
||
|
||
echo ""
|
||
echo "=== Assembling zddc/dist/web/ ==="
|
||
# All five tool HTMLs ship inside the server bundle. landing and archive call
|
||
# server APIs (GET / for the project list, directory listings for archive) and
|
||
# are useless without zddc-server. transmittal, classifier, and mdedit are
|
||
# pure client-side tools but are still bundled — the server uses these copies
|
||
# as the embedded fallback (//go:embed in internal/apps/embedded/) when both
|
||
# the cache is empty AND the upstream is unreachable.
|
||
mkdir -p "$SCRIPT_DIR/zddc/dist/web"
|
||
cp "$SCRIPT_DIR/landing/dist/index.html" "$SCRIPT_DIR/zddc/dist/web/index.html"
|
||
cp "$SCRIPT_DIR/archive/dist/archive.html" "$SCRIPT_DIR/zddc/dist/web/archive.html"
|
||
cp "$SCRIPT_DIR/transmittal/dist/transmittal.html" "$SCRIPT_DIR/zddc/dist/web/transmittal.html"
|
||
cp "$SCRIPT_DIR/classifier/dist/classifier.html" "$SCRIPT_DIR/zddc/dist/web/classifier.html"
|
||
cp "$SCRIPT_DIR/mdedit/dist/mdedit.html" "$SCRIPT_DIR/zddc/dist/web/mdedit.html"
|
||
echo "Wrote zddc/dist/web/{index,archive,transmittal,classifier,mdedit}.html"
|
||
|
||
# Mirror the same five HTMLs into the Go embed source dir so the next
|
||
# `go build` of zddc-server picks them up via //go:embed. Files are checked
|
||
# into git as empty placeholders; the build always overwrites them with the
|
||
# fresh dist/ output.
|
||
EMBED_DIR="$SCRIPT_DIR/zddc/internal/apps/embedded"
|
||
mkdir -p "$EMBED_DIR"
|
||
cp "$SCRIPT_DIR/landing/dist/index.html" "$EMBED_DIR/index.html"
|
||
cp "$SCRIPT_DIR/archive/dist/archive.html" "$EMBED_DIR/archive.html"
|
||
cp "$SCRIPT_DIR/transmittal/dist/transmittal.html" "$EMBED_DIR/transmittal.html"
|
||
cp "$SCRIPT_DIR/classifier/dist/classifier.html" "$EMBED_DIR/classifier.html"
|
||
cp "$SCRIPT_DIR/mdedit/dist/mdedit.html" "$EMBED_DIR/mdedit.html"
|
||
echo "Populated $EMBED_DIR/ for //go:embed"
|
||
|
||
# Assemble the embedded versions manifest from the per-tool .label sidecars
|
||
# written by shared/build-lib.sh's compute_build_label. The Go side reads
|
||
# this via //go:embed in internal/apps/versions.go and surfaces it in
|
||
# `zddc-server --version` output and the startup log line.
|
||
VERSIONS_FILE="$EMBED_DIR/versions.txt"
|
||
{
|
||
echo "# Generated by build.sh — do not edit. One <app>=<build label> per line."
|
||
for _tool in archive transmittal classifier mdedit landing; do
|
||
_label_file="$BUILD_LABELS_DIR/${_tool}.label"
|
||
if [ -f "$_label_file" ]; then
|
||
_label=$(cat "$_label_file")
|
||
else
|
||
_label=""
|
||
fi
|
||
printf '%s=%s\n' "$_tool" "$_label"
|
||
done
|
||
} > "$VERSIONS_FILE"
|
||
echo "Wrote $VERSIONS_FILE"
|
||
rm -rf "$BUILD_LABELS_DIR"
|
||
|
||
# Cross-compiled zddc-server binaries for Linux/macOS/Windows. Always built
|
||
# inside docker.io/golang:1.24-alpine via podman (or docker), matching the
|
||
# helm/zddc-server-prod chart's `buildImage` so dev binaries are byte-for-byte
|
||
# what production gets. The build container is downloaded on first run.
|
||
echo ""
|
||
echo "=== Building zddc-server binaries (containerized) ==="
|
||
mkdir -p "$SCRIPT_DIR/zddc/dist"
|
||
|
||
# Pick a container runtime. Both work; podman is preferred (rootless default).
|
||
GO_RUNNER=""
|
||
if command -v podman >/dev/null 2>&1; then
|
||
GO_RUNNER=podman
|
||
elif command -v docker >/dev/null 2>&1; then
|
||
GO_RUNNER=docker
|
||
else
|
||
echo "error: neither podman nor docker is available — cannot build zddc-server binaries." >&2
|
||
echo " Install podman (preferred) or docker. zddc-server build is containerized as policy." >&2
|
||
exit 1
|
||
fi
|
||
|
||
GO_BUILD_IMAGE="${ZDDC_GO_BUILD_IMAGE:-docker.io/golang:1.24-alpine}"
|
||
|
||
# Cache the Go module + build cache across runs via named volumes that
|
||
# persist between container invocations. Second build is fast.
|
||
GO_MOD_VOL="${ZDDC_GO_MOD_VOL:-zddc-go-mod}"
|
||
GO_BUILD_VOL="${ZDDC_GO_BUILD_VOL:-zddc-go-cache}"
|
||
|
||
# Compute the binary's own version. On a stable cut, hard-code the
|
||
# coordinated version so the binary embeds the same string the rest of the
|
||
# release cycle has agreed on. Otherwise fall back to git describe (clean
|
||
# tag, or tag-N-gSHA[-dirty] for in-flight commits).
|
||
if [ -n "$RELEASE_VERSION" ]; then
|
||
ZDDC_BINARY_VERSION="$RELEASE_VERSION"
|
||
else
|
||
ZDDC_BINARY_VERSION=$(git -C "$SCRIPT_DIR" describe --tags --dirty --match 'zddc-server-v*' 2>/dev/null || true)
|
||
if [ -z "$ZDDC_BINARY_VERSION" ]; then
|
||
_sha=$(git -C "$SCRIPT_DIR" rev-parse --short=7 HEAD 2>/dev/null || echo unknown)
|
||
if ! git -C "$SCRIPT_DIR" diff --quiet HEAD 2>/dev/null; then
|
||
_sha="${_sha}-dirty"
|
||
fi
|
||
ZDDC_BINARY_VERSION="dev-${_sha}"
|
||
fi
|
||
fi
|
||
echo " binary version: $ZDDC_BINARY_VERSION"
|
||
|
||
# Single container invocation, multiple cross-compile targets inside a
|
||
# `for` loop — avoids paying image-startup overhead 4×.
|
||
"$GO_RUNNER" run --rm \
|
||
-v "$SCRIPT_DIR:/src:Z" \
|
||
-v "${GO_MOD_VOL}:/go/pkg/mod" \
|
||
-v "${GO_BUILD_VOL}:/root/.cache/go-build" \
|
||
-w /src/zddc \
|
||
-e GOFLAGS=-mod=mod \
|
||
-e CGO_ENABLED=0 \
|
||
-e ZDDC_BINARY_VERSION="$ZDDC_BINARY_VERSION" \
|
||
"$GO_BUILD_IMAGE" \
|
||
sh -c '
|
||
set -e
|
||
for target in linux/amd64 darwin/amd64 darwin/arm64 windows/amd64; do
|
||
os="${target%/*}"; arch="${target#*/}"
|
||
out="zddc-server-${os}-${arch}"
|
||
case "$os" in windows) out="${out}.exe" ;; esac
|
||
echo " building $out"
|
||
GOOS="$os" GOARCH="$arch" \
|
||
go build -trimpath \
|
||
-ldflags="-s -w -X main.version=${ZDDC_BINARY_VERSION}" \
|
||
-o "dist/$out" ./cmd/zddc-server
|
||
done
|
||
'
|
||
|
||
# --- Promote zddc-server release artifacts ---------------------------------
|
||
# Copy the freshly cross-compiled binaries to the website worktree's
|
||
# releases/ under their canonical names + symlinks. promote_zddc_server
|
||
# also re-runs write_zddc_server_stubs_all internally, so the matrix-cell
|
||
# stub pages get regenerated in the same call.
|
||
echo ""
|
||
echo "=== Promoting zddc-server $RELEASE_CHANNEL release ==="
|
||
promote_zddc_server "$RELEASE_CHANNEL" "$RELEASE_VERSION" "$RELEASES_DIR" "$SCRIPT_DIR/zddc/dist"
|
||
|
||
# Latest stable version, by following archive_stable.html → versioned target.
|
||
# Returns "" if no stable cut exists yet (bootstrap state). All HTML tools
|
||
# move in lockstep so any one of them is a valid probe; archive is canonical.
|
||
_latest_stable_version() {
|
||
_link="$RELEASES_DIR/archive_stable.html"
|
||
[ -L "$_link" ] || return 0
|
||
_target=$(readlink "$_link")
|
||
# archive_v0.0.8.html → 0.0.8
|
||
_v="${_target#archive_v}"
|
||
_v="${_v%.html}"
|
||
case "$_v" in
|
||
[0-9]*.[0-9]*.[0-9]*) echo "$_v" ;;
|
||
esac
|
||
}
|
||
|
||
# Channel "active" iff the channel mirror is real bytes rather than a
|
||
# symlink → stable. Used to surface alpha/beta in the dropdown only when
|
||
# they meaningfully differ from stable. Probes archive (HTML lockstep
|
||
# representative); zddc-server's probe is its per-platform binary.
|
||
_channel_is_active() {
|
||
_ch="$1" # alpha | beta
|
||
_f="$RELEASES_DIR/archive_${_ch}.html"
|
||
[ -L "$_f" ] && return 1 # symlink → tracks stable, not "active"
|
||
[ -f "$_f" ] && return 0
|
||
return 1
|
||
}
|
||
|
||
# Regenerate website/releases/index.html as the action-first install
|
||
# guide (not a matrix). The page guides users to either self-host the
|
||
# server or download individual tools, with one version dropdown that
|
||
# rewires every download link via JS. The default static state always
|
||
# uses latest-stable URLs so the page works fully without JS.
|
||
build_releases_index() {
|
||
_out="$RELEASES_DIR/index.html"
|
||
mkdir -p "$RELEASES_DIR"
|
||
|
||
_latest=$(_latest_stable_version)
|
||
if [ -z "$_latest" ]; then
|
||
_latest="0.0.0"
|
||
fi
|
||
|
||
# All distinct stable versions across every tool, descending. Same
|
||
# awk that the prior matrix used — proven across the tool naming.
|
||
_all_versions=$(
|
||
find "$RELEASES_DIR" -maxdepth 1 -type f \( \
|
||
-name 'archive_v*.html' -o -name 'transmittal_v*.html' \
|
||
-o -name 'classifier_v*.html' -o -name 'mdedit_v*.html' \
|
||
-o -name 'landing_v*.html' \
|
||
-o -name 'zddc-server_v*_linux-amd64' \
|
||
\) 2>/dev/null \
|
||
| awk -F/ '{
|
||
n = split($NF, parts, "_v");
|
||
if (n < 2) next;
|
||
v = parts[2];
|
||
sub(/\.html$/, "", v);
|
||
sub(/_linux-amd64$/, "", v);
|
||
if (v ~ /^[0-9]+\.[0-9]+\.[0-9]+$/) print v;
|
||
}' \
|
||
| sort -Vu \
|
||
| sort -Vr
|
||
)
|
||
|
||
_alpha_active="0"; _channel_is_active alpha && _alpha_active="1"
|
||
_beta_active="0"; _channel_is_active beta && _beta_active="1"
|
||
|
||
{
|
||
cat <<HEAD
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Download ZDDC</title>
|
||
<meta name="description" content="Self-host the ZDDC server, or download individual tools. Pin a version your project trusts; your archive's tools are yours.">
|
||
<meta name="theme-color" content="#2a5a8a">
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="../css/style.css">
|
||
</head>
|
||
<body>
|
||
<header class="site-header">
|
||
<div class="container header-content">
|
||
<a href="/" class="brand">
|
||
<svg class="brand-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" aria-hidden="true">
|
||
<rect width="64" height="64" rx="12" fill="#1e3a5f"/>
|
||
<g fill="#fff">
|
||
<rect x="14" y="18" width="36" height="7"/>
|
||
<polygon points="43,25 50,25 21,43 14,43"/>
|
||
<rect x="14" y="43" width="36" height="7"/>
|
||
</g>
|
||
</svg>
|
||
<span class="brand-name">ZDDC</span>
|
||
</a>
|
||
<nav class="header-nav">
|
||
<a href="/" class="nav-link">Home</a>
|
||
<a href="../reference.html" class="nav-link">Docs</a>
|
||
<a href="index.html" class="nav-link active">Download</a>
|
||
</nav>
|
||
</div>
|
||
</header>
|
||
|
||
<section class="hero">
|
||
<div class="container">
|
||
<h1>Download ZDDC</h1>
|
||
<p class="hero-subtitle">Pick how you want to use it. Pick the version you want. Every link below points at a real, immutable file you can save into your archive — your tools, your version, forever.</p>
|
||
</div>
|
||
</section>
|
||
|
||
<main class="container" style="margin-bottom: var(--spacing-2xl);">
|
||
<div class="version-picker-bar">
|
||
<label for="version-picker">Showing</label>
|
||
<select id="version-picker">
|
||
HEAD
|
||
|
||
# Channels — selectable directly so users can copy the channel-
|
||
# mirror URLs (e.g. archive_stable.html) for bookmarks. stable is
|
||
# the default. The label tells the truth about the channel's
|
||
# current state: when stable is set, show which version it points
|
||
# at; when alpha/beta is just a symlink to stable, mark as
|
||
# "tracks stable" so picking it isn't surprising.
|
||
printf ' <optgroup label="Channels (mutable URLs)">\n'
|
||
if [ -n "$_latest" ] && [ "$_latest" != "0.0.0" ]; then
|
||
printf ' <option value="stable" selected>stable — currently v%s</option>\n' "$_latest"
|
||
else
|
||
printf ' <option value="stable" selected>stable</option>\n'
|
||
fi
|
||
if [ "$_beta_active" = "1" ]; then
|
||
printf ' <option value="beta">beta — general testing</option>\n'
|
||
else
|
||
printf ' <option value="beta">beta — tracks stable</option>\n'
|
||
fi
|
||
if [ "$_alpha_active" = "1" ]; then
|
||
printf ' <option value="alpha">alpha — active dev</option>\n'
|
||
else
|
||
printf ' <option value="alpha">alpha — tracks stable</option>\n'
|
||
fi
|
||
printf ' </optgroup>\n'
|
||
|
||
# Pinned per-version, latest first. These are the immutable URLs
|
||
# for reproducibility. No "(current stable)" suffix because the
|
||
# stable channel above already covers that.
|
||
printf ' <optgroup label="Pinned versions (immutable URLs)">\n'
|
||
printf '%s\n' "$_all_versions" | while read -r _v; do
|
||
[ -n "$_v" ] || continue
|
||
printf ' <option value="v%s">v%s</option>\n' "$_v" "$_v"
|
||
done
|
||
printf ' </optgroup>\n'
|
||
|
||
cat <<'PICKER_END'
|
||
</select>
|
||
<span class="picker-hint">Changes every download link below.</span>
|
||
</div>
|
||
|
||
<!-- ───────────── Path A — Self-host the server ───────────── -->
|
||
<section class="card" style="background: var(--color-bg-subtle); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--spacing-lg) var(--spacing-xl); margin-top: var(--spacing-lg);">
|
||
<h2 style="margin-top:0;">Path A — Self-host the server</h2>
|
||
<p>One small Go binary. <strong>All five tools are baked in</strong> via <code>//go:embed</code>; the server picks the right one for each folder of your archive. Adds ACL via <code>.zddc</code> files, the virtual <code>.archive</code> document index, and SSO header passthrough. Stop the server and the directory is still a perfectly valid ZDDC archive — the server is convenience, not lock-in.</p>
|
||
PICKER_END
|
||
|
||
# Render the download UI only when zddc-server has been published
|
||
# at least once. Until then, show an honest "not yet released"
|
||
# placeholder rather than dangling download buttons.
|
||
_zs_published="0"
|
||
if [ -e "$RELEASES_DIR/zddc-server_stable_linux-amd64" ]; then
|
||
_zs_published="1"
|
||
fi
|
||
|
||
if [ "$_zs_published" = "1" ]; then
|
||
# Default href is the channel-mirror URL (zddc-server_stable_<plat>)
|
||
# because "stable" is the dropdown's selected option. Picking a
|
||
# pinned version from the dropdown rewrites these to the
|
||
# immutable per-version URL via the IIFE.
|
||
printf ' <a class="dl-primary"\n'
|
||
printf ' data-tool="zddc-server"\n'
|
||
printf ' data-platform="linux-amd64"\n'
|
||
printf ' href="zddc-server_stable_linux-amd64"\n'
|
||
printf ' id="dl-primary-binary">\n'
|
||
printf ' <span class="dl-icon">⬇</span>\n'
|
||
printf ' <span>Download <span id="dl-primary-platlabel">for Linux (x86_64)</span></span>\n'
|
||
printf ' </a>\n'
|
||
printf ' <span class="dl-primary-meta" id="dl-primary-meta">zddc-server_stable_linux-amd64</span>\n'
|
||
|
||
printf ' <div class="dl-secondary-row" id="dl-others">\n'
|
||
printf ' <span>Other platforms:</span>\n'
|
||
for _entry in "linux-amd64|Linux (x86_64)" \
|
||
"darwin-amd64|macOS (Intel)" \
|
||
"darwin-arm64|macOS (Apple Silicon)" \
|
||
"windows-amd64|Windows (x86_64)"; do
|
||
_plat="${_entry%%|*}"
|
||
_label="${_entry#*|}"
|
||
_suffix=""
|
||
case "$_plat" in *windows*) _suffix=".exe" ;; esac
|
||
printf ' <a data-tool="zddc-server" data-platform="%s" href="zddc-server_stable_%s%s">%s</a>\n' \
|
||
"$_plat" "$_plat" "$_suffix" "$_label"
|
||
done
|
||
printf ' </div>\n'
|
||
|
||
cat <<'PATH_A_END'
|
||
<p style="margin-top: var(--spacing-md); font-size: 0.92rem; color: var(--color-text-muted);">
|
||
After download: <code>chmod +x</code> the file, set <code>ZDDC_ROOT=/path/to/archive</code>, run.
|
||
Need a different platform? <a href="https://codeberg.org/VARASYS/ZDDC">Build from source</a> at the matching tag.
|
||
</p>
|
||
</section>
|
||
PATH_A_END
|
||
else
|
||
# Bootstrap state: no zddc-server stable cut yet.
|
||
cat <<'PATH_A_BOOTSTRAP'
|
||
<p style="margin-top: var(--spacing-md); padding: var(--spacing-md); background: var(--color-bg); border-left: 3px solid var(--color-accent); border-radius: var(--radius-sm); color: var(--color-text);">
|
||
<strong>Not yet published.</strong> The first lockstep release publishes binaries here. Until then, build from source: <code>git clone</code> and <code>(cd zddc && go build ./cmd/zddc-server)</code>. Once <code>sh build.sh --release</code> runs, this card auto-populates with download buttons for every platform.
|
||
</p>
|
||
</section>
|
||
PATH_A_BOOTSTRAP
|
||
fi
|
||
|
||
cat <<'PATH_B_OPEN'
|
||
|
||
<!-- ───────────── Path B — Standalone tool HTMLs ───────────── -->
|
||
<section class="card" style="border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--spacing-lg) var(--spacing-xl); margin-top: var(--spacing-xl);">
|
||
<h2 style="margin-top:0;">Path B — Standalone tools</h2>
|
||
<p>Every tool is a single self-contained HTML file. <strong>Open it locally and point it at a folder on your disk</strong> — no install, no server, no account. Same on-disk layout the server uses. Use one tool, use all five, mix and match — there is no orchestration to set up.</p>
|
||
<div class="grid-4" style="margin-top: var(--spacing-md);">
|
||
PATH_B_OPEN
|
||
|
||
# Tool cards — reuse home page's .tool-card vocabulary
|
||
for _entry in "archive|Archive Browser|Browse and download from a ZDDC archive." \
|
||
"transmittal|Transmittal Creator|Build, sign, and verify transmittal packages." \
|
||
"classifier|Classifier|Rename loose files to ZDDC convention." \
|
||
"mdedit|Markdown Editor|Edit project markdown files in place." \
|
||
"landing|Landing|Project picker for multi-project servers."; do
|
||
_t="${_entry%%|*}"
|
||
_rest="${_entry#*|}"
|
||
_name="${_rest%%|*}"
|
||
_desc="${_rest#*|}"
|
||
# Default href is the stable-channel mirror; the dropdown
|
||
# rewires these per selection.
|
||
printf ' <a class="tool-card" data-tool="%s" href="%s_stable.html">\n' "$_t" "$_t"
|
||
printf ' <span class="tool-card__title">%s</span>\n' "$_name"
|
||
printf ' <span class="tool-card__desc">%s</span>\n' "$_desc"
|
||
printf ' <span class="tool-card__link">Download →</span>\n'
|
||
printf ' </a>\n'
|
||
done
|
||
|
||
cat <<'PATH_B_END'
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ───────────── Pinning empowerment narrative ───────────── -->
|
||
<section class="card" style="border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--spacing-lg) var(--spacing-xl); margin-top: var(--spacing-xl);">
|
||
<h2 style="margin-top:0;">Your version, forever</h2>
|
||
<p>Your server may run v0.0.8 next month and v0.1.0 the month after. <strong>Your project doesn't have to follow.</strong> If you depend on a specific behavior in <code>archive</code> v0.0.5, save that version into your archive — the next server upgrade can't take it away from you. Two ways to do it:</p>
|
||
<div class="grid-2" style="margin-top: var(--spacing-md);">
|
||
<div class="pin-card">
|
||
<h3>Drop a copy into your archive</h3>
|
||
<p>Save the tool's HTML at the path the server would serve it from. The server's resolution order picks up real files <em>first</em> — before any cascade or embedded fallback.</p>
|
||
PATH_B_END
|
||
|
||
printf ' <pre>curl -o MyProject/archive.html \\\n https://zddc.varasys.io/releases/archive_v%s.html</pre>\n' "$_latest"
|
||
|
||
cat <<'PIN_MID'
|
||
<p>Now <code>MyProject/archive.html</code> is yours. The server serves your bytes; nothing about a future <code>--release</code> can change them.</p>
|
||
</div>
|
||
<div class="pin-card">
|
||
<h3>Pin via <code>.zddc</code></h3>
|
||
<p>Less invasive — no copies in your archive, just a small config entry telling the server which version to fetch and cache. Closer-to-leaf wins, so subprojects can pin further.</p>
|
||
PIN_MID
|
||
|
||
printf ' <pre># MyProject/.zddc\napps:\n archive: v%s</pre>\n' "$_latest"
|
||
|
||
cat <<'PIN_END'
|
||
<p>Server fetches once on first hit, caches under <code>_app/</code>, falls through to the embedded copy if the fetch fails.</p>
|
||
</div>
|
||
</div>
|
||
<p class="pin-note">Your archive's tools are <strong>yours</strong>. The server is convenience; deletion of the server doesn't break your archive — every per-version download above is a real, immutable static file. Save what you trust.</p>
|
||
</section>
|
||
|
||
<!-- ───────────── Channels explainer ───────────── -->
|
||
<section class="card" style="border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--spacing-lg) var(--spacing-xl); margin-top: var(--spacing-xl); margin-bottom: var(--spacing-xl);">
|
||
<h2 style="margin-top:0;">Channels</h2>
|
||
<p>Three channels, applied in lockstep across all tools. Pre-release channels exist to soak changes; <strong>stable</strong> is what production runs.</p>
|
||
<div class="channel-explainer">
|
||
<div>
|
||
<h4 class="alpha">alpha</h4>
|
||
<p>Active dev iteration. Rebuilds without notice. Look here for the very latest.</p>
|
||
</div>
|
||
<div>
|
||
<h4 class="beta">beta</h4>
|
||
<p>Ready for general testing. Has soaked through alpha. Still mutable — pin to a versioned URL for reproducibility.</p>
|
||
</div>
|
||
<div>
|
||
<h4 class="stable">stable</h4>
|
||
<p>Ready to ship. Every per-version file is immutable; <code>_stable</code> follows the latest cut. Channel cuts cascade: stable cut resets beta and alpha to track stable.</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<footer class="site-footer">
|
||
<div class="container footer-content">
|
||
<span>ZDDC is open source — <a href="https://codeberg.org/VARASYS/ZDDC">codeberg.org/VARASYS/ZDDC</a></span>
|
||
</div>
|
||
</footer>
|
||
|
||
<script>
|
||
(function() {
|
||
// Platform auto-detect: choose the most likely binary for this user's
|
||
// OS on first paint. Promotes that platform to the primary CTA; the
|
||
// other three render in the secondary row. UA-sniffing is good
|
||
// enough — wrong guesses fall through to the always-visible
|
||
// "Other platforms" row below.
|
||
var ua = navigator.userAgent || '';
|
||
var detected = 'linux-amd64'; // sensible default
|
||
var platLabel = 'Linux (x86_64)';
|
||
if (/Macintosh|Mac OS X/.test(ua)) {
|
||
// Apple Silicon vs Intel — UA hints aren't reliable, prefer arm64
|
||
// since modern Macs are predominantly arm64. Users on Intel can
|
||
// pick from "Other platforms".
|
||
detected = 'darwin-arm64';
|
||
platLabel = 'macOS (Apple Silicon)';
|
||
} else if (/Windows/.test(ua)) {
|
||
detected = 'windows-amd64';
|
||
platLabel = 'Windows (x86_64)';
|
||
}
|
||
|
||
var primary = document.getElementById('dl-primary-binary');
|
||
var primaryLabel = document.getElementById('dl-primary-platlabel');
|
||
var primaryMeta = document.getElementById('dl-primary-meta');
|
||
var others = document.getElementById('dl-others');
|
||
|
||
function isChannel(v) {
|
||
return v === 'stable' || v === 'beta' || v === 'alpha';
|
||
}
|
||
function platBinaryName(slug, plat) {
|
||
// slug is a channel name ("stable") or a pinned version ("v0.0.8").
|
||
// The on-disk name uses the slug as-is in both cases since the
|
||
// channel-mirror filenames are zddc-server_<channel>_<plat> and
|
||
// per-version are zddc-server_v<X.Y.Z>_<plat>.
|
||
var suf = (plat.indexOf('windows') === 0) ? '.exe' : '';
|
||
return 'zddc-server_' + slug + '_' + plat + suf;
|
||
}
|
||
function htmlAssetName(tool, slug) {
|
||
return tool + '_' + slug + '.html';
|
||
}
|
||
|
||
// Promote the detected platform to the primary CTA. The secondary
|
||
// row keeps all four; the matching one is hidden to avoid showing
|
||
// the same download twice.
|
||
if (primary) {
|
||
primary.dataset.platform = detected;
|
||
if (primaryLabel) primaryLabel.textContent = 'for ' + platLabel;
|
||
}
|
||
if (others) {
|
||
others.querySelectorAll('a[data-platform="' + detected + '"]').forEach(function(a) {
|
||
a.style.display = 'none';
|
||
});
|
||
}
|
||
|
||
// Single source of truth: the dropdown's current value drives every
|
||
// download link's href. Static markup ships with the stable-channel
|
||
// mirror (`<tool>_stable.html`, `zddc-server_stable_<plat>`) so the
|
||
// page works fully without JS — the JS just keeps things in sync
|
||
// when the user picks a different channel or pins a version.
|
||
var picker = document.getElementById('version-picker');
|
||
if (!picker) return;
|
||
|
||
function rewire(slug) {
|
||
// slug ∈ {"stable", "beta", "alpha"} | "v<X.Y.Z>". Every link with
|
||
// a data-tool attribute is a download URL the dropdown owns.
|
||
document.querySelectorAll('[data-tool]').forEach(function(a) {
|
||
var tool = a.dataset.tool;
|
||
var plat = a.dataset.platform || '';
|
||
if (tool === 'zddc-server') {
|
||
a.href = plat ? platBinaryName(slug, plat) : ('zddc-server_' + slug + '.html');
|
||
} else {
|
||
a.href = htmlAssetName(tool, slug);
|
||
}
|
||
});
|
||
if (primary && primaryMeta) {
|
||
primaryMeta.textContent = primary.getAttribute('href');
|
||
}
|
||
}
|
||
|
||
picker.addEventListener('change', function() { rewire(picker.value); });
|
||
|
||
// Run rewire once on load to apply the platform-detection result
|
||
// (the static href for the primary button is for linux-amd64; on a
|
||
// non-linux client, that needs to flip to the detected platform).
|
||
rewire(picker.value);
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|
||
PIN_END
|
||
} > "$_out"
|
||
echo "Wrote $_out"
|
||
}
|
||
|
||
echo ""
|
||
echo "=== Building releases/index.html ==="
|
||
build_releases_index
|
||
|
||
echo ""
|
||
echo "=== Verifying channel links ==="
|
||
verify_channel_links "$RELEASES_DIR"
|
||
|
||
echo ""
|
||
echo "=== All tools built successfully ==="
|
||
echo ""
|
||
echo "Cut: $RELEASE_CHANNEL"
|
||
if [ -n "$RELEASE_VERSION" ]; then
|
||
echo "Version: v$RELEASE_VERSION"
|
||
echo ""
|
||
echo "Tags created locally on main (push when ready):"
|
||
for _t in archive transmittal classifier mdedit landing zddc-server; do
|
||
echo " ${_t}-v${RELEASE_VERSION}"
|
||
done
|
||
echo " git push origin main && git push origin --tags"
|
||
fi
|
||
echo ""
|
||
echo "Artifacts written to $RELEASES_DIR/"
|
||
echo " cd $(dirname "$RELEASES_DIR") && git status # to review the deploy"
|
||
echo " cd $(dirname "$RELEASES_DIR") && git add -A && git commit && git push origin website"
|
||
echo " ↑ commits + pushes the website branch when you're ready to publish"
|