ZDDC/build.sh
ZDDC 2dc9ad240c refactor: distribute via Codeberg release assets, drop the upstream image
Removes the codeberg.org/varasys/zddc-server registry image, which had
no remaining consumer outside this shop. The two chart Dockerfiles
(tnd-zddc-chart) now compile zddc-server from source at build time,
fetching the right tag from a Codeberg release. release-image.sh,
zddc/Containerfile, and zddc/podman-compose.yaml are gone.

Build artifacts (HTML tools + zddc-server binaries) move from
website/releases/ in this repo to Codeberg release assets attached to
git tags. The website at zddc.varasys.io serves them by reverse-
proxying /releases/<tag>/<asset> to the corresponding Codeberg URL,
so consumers (zddc-use, level-2 bootstrap stubs, the chart
Dockerfiles) only ever talk to zddc.varasys.io.

Releases page becomes server-rendered static HTML regenerated on each
build via a single Codeberg API call. A small website/releases/manifest.json
maps <tool>-<channel> → tag for runtime channel resolution by zddc-use
and the level-2 stubs.

Files added:
- shared/publish-codeberg-release.sh — POSIX-sh helper that creates a
  Codeberg release for a tag (sets prerelease flag from tag suffix)
  and uploads/replaces release assets idempotently. Sourced by
  build-lib.sh and zddc/release.sh.
- zddc/release.sh — replaces release-image.sh. Tags + cross-compiles
  binaries via native Go (no podman needed; install Go) + uploads to
  Codeberg release assets. No image build, no registry push.

Files modified:
- shared/build-lib.sh — promote_release tags + uploads via the helper
  for stable AND alpha/beta now (alpha/beta were untagged before).
  update_alpha removed; per-tool build.sh files no longer mirror to
  website/releases/<tool>_alpha.html on plain dev builds.
- build.sh — prefers native go build over the old podman-based
  cross-compile (which is gone with Containerfile). build_releases_index
  queries the Codeberg API once and writes static HTML + manifest.json,
  with graceful fallback when the API is unreachable.
- bootstrap/level2.html.tmpl — fetches manifest.json to resolve
  channel → tag, then fetches the asset from /releases/<tag>/<asset>
  (Caddy proxy). Replaces the old /releases/<tool>_<channel>.html flat
  URL pattern. Operators with curl'd level-2 stubs need to re-issue
  them — this is a breaking change.
- AGENTS.md, CLAUDE.md — rewritten to describe the new flow.
- .gitignore — releases/ artifacts now expected to be on Codeberg, not
  committed locally.

NOT in this commit (deferred until $CODEBERG_TOKEN is provisioned):
- Backfilling existing tags as Codeberg releases.
- Cleanup commit: git rm-ing the existing artifacts in website/releases/.
  Until backfill happens, those files are how operators with old
  bootstrap stubs still get content. Once Codeberg has the assets,
  drop them.
- The Caddy reverse-proxy config on zddc.varasys.io.

Operator-side changes (not in this repo):
- tnd-zddc-chart Dockerfile.prod and Dockerfile (dev) need updating
  to compile from source rather than `FROM codeberg.org/...:stable`.
  Done in a separate commit on that repo.
- Caddyfile rule for the /releases/<tag>/<asset> reverse-proxy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:18:26 -05:00

390 lines
18 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/sh
set -eu
# Top-level build script — builds all ZDDC HTML tools, the zddc-server
# binaries, and the bootstrap stubs published under website/bootstrap/.
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
echo "=== Building ZDDC tools ==="
sh "$SCRIPT_DIR/transmittal/build.sh" "${1:-}" "${2:-}"
sh "$SCRIPT_DIR/archive/build.sh" "${1:-}" "${2:-}"
sh "$SCRIPT_DIR/classifier/build.sh" "${1:-}" "${2:-}"
sh "$SCRIPT_DIR/mdedit/build.sh" "${1:-}" "${2:-}"
sh "$SCRIPT_DIR/landing/build.sh" "${1:-}" "${2:-}"
echo ""
echo "=== Assembling zddc/dist/web/ ==="
# Only landing and archive ship inside the server bundle: they call the
# server's JSON API (GET / for the project list, directory listings for the
# archive) and are useless without it. transmittal, classifier, and mdedit
# are pure client-side tools that work from file:// or any static host;
# they are released to website/ for download but not bundled with the server.
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"
echo "Wrote zddc/dist/web/index.html"
echo "Wrote zddc/dist/web/archive.html"
# Cross-compiled zddc-server binaries — only relevant if you're shipping
# standalone Linux/macOS/Windows binaries to users. Skipped silently when
# Go isn't on PATH. (zddc/release.sh handles the publish flow that
# uploads these to Codeberg release assets.)
echo ""
echo "=== Building zddc-server binaries ==="
if command -v go >/dev/null 2>&1; then
cd "$SCRIPT_DIR/zddc"
mkdir -p dist
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"
CGO_ENABLED=0 GOOS="$os" GOARCH="$arch" \
go build -trimpath -ldflags="-s -w" -o "dist/$out" ./cmd/zddc-server
done
cd "$SCRIPT_DIR"
else
echo "go not found — skipping cross-compiled binary build."
echo " (Install Go 1.24+ to build standalone binaries.)"
fi
# ─── Bootstrap stubs ─────────────────────────────────────────────────────────
# Generated from bootstrap/level{1,2}.html.tmpl on every build and published
# as standalone files under website/bootstrap/. The website's "Install on
# your server" section prints copy-pasteable shell snippets that curl these
# files into the operator's deployment directory.
#
# bootstrap/level1/<tool>.html — same-origin stubs for
# <project>/<tool>.html (4 tools;
# landing only lives at root)
# bootstrap/track-<channel>/<tool>.html — level-2 stubs that fetch the
# named channel from upstream
# (5 tools × 3 channels = 15)
WEBSITE_DIR="$SCRIPT_DIR/website"
RELEASES_DIR="$WEBSITE_DIR/releases"
BOOTSTRAP_DIR="$SCRIPT_DIR/bootstrap"
mkdir -p "$WEBSITE_DIR"
# tool|filename|title
TOOL_TABLE='archive|archive.html|Archive
transmittal|transmittal.html|Transmittal
classifier|classifier.html|Classifier
mdedit|mdedit.html|Markdown Editor
landing|index.html|ZDDC'
# Substitute {{TOOL}}, {{TOOL_TITLE}}, {{CHANNEL}}, {{FAVICON}} in a template.
# The favicon is a base64-encoded data URI built once from shared/favicon.svg.
_favicon_data_uri=""
if [ -f "$SCRIPT_DIR/shared/favicon.svg" ]; then
_favicon_data_uri="data:image/svg+xml;base64,$(base64 -w 0 "$SCRIPT_DIR/shared/favicon.svg")"
fi
render_stub() {
sed \
-e "s|{{TOOL_TITLE}}|$3|g" \
-e "s|{{TOOL}}|$2|g" \
-e "s|{{CHANNEL}}|${4:-}|g" \
-e "s|{{FAVICON}}|$_favicon_data_uri|g" \
"$1" > "$5"
}
build_bootstrap_stubs() {
_stubs="$WEBSITE_DIR/bootstrap"
rm -rf "$_stubs"
mkdir -p "$_stubs/level1"
# Level-1 stubs (same-origin, channel-agnostic). Drop into a project
# subdirectory so <project>/<tool>.html fetches ../<tool>.html.
# Landing has no level-1 stub — landing only lives at deployment root.
while IFS='|' read -r _tool _file _title; do
render_stub "$BOOTSTRAP_DIR/level1.html.tmpl" "$_tool" "$_title" "" \
"$_stubs/level1/$_file"
done <<EOF
archive|archive.html|Archive
transmittal|transmittal.html|Transmittal
classifier|classifier.html|Classifier
mdedit|mdedit.html|Markdown Editor
EOF
echo "Wrote $_stubs/level1/{archive,transmittal,classifier,mdedit}.html"
# Level-2 stubs, one set per channel. Each fetches its named channel
# from upstream on every page load.
for _channel in alpha beta stable; do
mkdir -p "$_stubs/track-$_channel"
while IFS='|' read -r _tool _file _title; do
render_stub "$BOOTSTRAP_DIR/level2.html.tmpl" "$_tool" "$_title" "$_channel" \
"$_stubs/track-$_channel/$_file"
done <<EOF
$TOOL_TABLE
EOF
echo "Wrote $_stubs/track-$_channel/ (5 stubs)"
done
}
# Regenerate website/releases/index.html and manifest.json from the
# Codeberg release list. Single API call at build time, no runtime
# dependency on Codeberg from the page (it's static HTML when served).
#
# Page links use /releases/<tag>/<asset> URLs which the website's Caddy
# reverse-proxies to codeberg.org/.../releases/download/<tag>/<asset>.
# Operators see one origin (zddc.varasys.io); Codeberg is the storage
# backend.
#
# Skips silently if curl/jq aren't on PATH (offline dev shouldn't blow
# up). Skips with a warning if the API call fails (network down, rate-
# limited, etc.) — the existing index.html stays as-is.
build_releases_index() {
_out="$RELEASES_DIR/index.html"
_manifest="$RELEASES_DIR/manifest.json"
mkdir -p "$RELEASES_DIR"
if ! command -v curl >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then
echo " (curl or jq missing — skipping releases index regeneration)"
return 0
fi
_api_resp=$(curl -fsSL --max-time 30 \
'https://codeberg.org/api/v1/repos/VARASYS/ZDDC/releases?limit=100' \
2>/dev/null) || _api_resp=""
if [ -z "$_api_resp" ]; then
echo " (Codeberg API unreachable — leaving releases/index.html as-is)"
return 0
fi
# Build manifest.json: for each tool/channel, find the latest matching
# release and emit "<tool>-<channel>": "<tag>". Channel resolution:
# alpha = latest tag matching <tool>-vX.Y.Z-alpha.N
# beta = latest tag matching <tool>-vX.Y.Z-beta.N
# stable = latest tag matching <tool>-vX.Y.Z (no suffix)
# "Latest" via sort -V on the version part.
_tools="archive transmittal classifier mdedit landing zddc-server"
{
printf '{\n'
_first=1
for _tool in $_tools; do
for _ch in stable beta alpha; do
if [ "$_ch" = "stable" ]; then
_re="^${_tool}-v[0-9]+\\.[0-9]+\\.[0-9]+\$"
else
_re="^${_tool}-v[0-9]+\\.[0-9]+\\.[0-9]+-${_ch}\\.[0-9]+\$"
fi
_tag=$(printf '%s' "$_api_resp" \
| jq -r --arg re "$_re" '
[.[] | select(.tag_name | test($re)) | .tag_name]
| sort
| last // empty
')
if [ -n "$_tag" ]; then
[ "$_first" = "1" ] || printf ',\n'
printf ' "%s-%s": "%s"' "$_tool" "$_ch" "$_tag"
_first=0
fi
done
done
printf '\n}\n'
} > "$_manifest"
echo "Wrote $_manifest"
{
cat <<'HEAD'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Releases — ZDDC</title>
<meta name="description" content="All released versions and channel builds of every ZDDC tool.">
<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">
<style>
.rel-tool { margin-top: var(--spacing-xl); padding: var(--spacing-md); border: 1px solid var(--color-border); border-radius: 8px; }
.rel-tool h2 { margin-top: 0; }
.rel-channels { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.75rem 0 1.25rem 0; }
.rel-channels a { padding: 0.25rem 0.625rem; border-radius: 999px; text-decoration: none; border: 1px solid var(--color-border); color: var(--color-text); font-size: 0.9rem; }
.rel-channels a.stable { border-color: var(--color-primary); color: var(--color-primary); font-weight: 600; }
.rel-channels a.beta, .rel-channels a.alpha { color: var(--color-text-muted); }
.rel-channels a:hover { background: var(--color-bg-subtle); }
.rel-versions { font-size: 0.875rem; color: var(--color-text-muted); }
.rel-versions a { margin-right: 0.5rem; color: var(--color-text); text-decoration: none; padding: 0.1rem 0.4rem; border-radius: 4px; }
.rel-versions a:hover { background: var(--color-bg-subtle); text-decoration: underline; }
.rel-meta { font-size: 0.85rem; color: var(--color-text-muted); margin-top: 0.5rem; }
.rel-bin-table { width: 100%; border-collapse: collapse; margin: 0.5rem 0 1rem; font-size: 0.9rem; }
.rel-bin-table th, .rel-bin-table td { text-align: left; padding: 0.4rem 0.6rem; border-bottom: 1px solid var(--color-border); }
.rel-bin-table th { font-weight: 600; color: var(--color-text-muted); }
.rel-bin-table td.ch-stable { color: var(--color-primary); font-weight: 600; }
.rel-bin-table td.ch-beta, .rel-bin-table td.ch-alpha { color: var(--color-text-muted); }
.rel-bin-table a { color: var(--color-text); text-decoration: none; padding: 0.1rem 0.35rem; border-radius: 4px; }
.rel-bin-table a:hover { background: var(--color-bg-subtle); text-decoration: underline; }
.rel-bin-table td.empty { color: var(--color-text-muted); font-style: italic; }
.rel-pull { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.85rem; background: var(--color-bg-subtle); padding: 0.25rem 0.5rem; border-radius: 4px; display: inline-block; margin: 0.2rem 0; }
</style>
</head>
<body>
<header class="site-header">
<div class="container header-content">
<a href="/" class="brand">
<div class="brand-logo">
<svg viewBox="0 0 24 24"><path d="M3 5h18v2H3V5zm0 6h12v2H3v-2zm0 6h6v2H3v-2z" /></svg>
</div>
<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">Releases</a>
</nav>
</div>
</header>
<section class="hero">
<div class="container">
<h1>Releases</h1>
<p class="hero-subtitle">All published versions and channel builds of every ZDDC tool. Stable releases are immutable; alpha and beta channels are rebuilt without notice.</p>
</div>
</section>
<main class="container" style="margin-bottom: var(--spacing-2xl);">
HEAD
# Render one section per tool. The HTML tool releases publish a
# single asset per tag (the inlined HTML); zddc-server publishes
# per-platform binaries. The renderer handles both.
for _tool_entry in 'archive|Archive' \
'transmittal|Transmittal' \
'classifier|Classifier' \
'mdedit|Markdown Editor' \
'landing|Landing (project picker)' \
'zddc-server|zddc-server (Go file server)'; do
_tool="${_tool_entry%%|*}"
_title="${_tool_entry#*|}"
_re_stable="^${_tool}-v[0-9]+\\.[0-9]+\\.[0-9]+\$"
_re_alpha="^${_tool}-v[0-9]+\\.[0-9]+\\.[0-9]+-alpha\\.[0-9]+\$"
_re_beta="^${_tool}-v[0-9]+\\.[0-9]+\\.[0-9]+-beta\\.[0-9]+\$"
_latest_stable=$(printf '%s' "$_api_resp" | jq -r --arg re "$_re_stable" \
'[.[] | select(.tag_name | test($re)) | .tag_name] | sort | last // empty')
_latest_beta=$(printf '%s' "$_api_resp" | jq -r --arg re "$_re_beta" \
'[.[] | select(.tag_name | test($re)) | .tag_name] | sort | last // empty')
_latest_alpha=$(printf '%s' "$_api_resp" | jq -r --arg re "$_re_alpha" \
'[.[] | select(.tag_name | test($re)) | .tag_name] | sort | last // empty')
_all_stables=$(printf '%s' "$_api_resp" | jq -r --arg re "$_re_stable" \
'[.[] | select(.tag_name | test($re)) | .tag_name] | sort | reverse | .[]')
# Skip the section entirely if no releases exist for this tool.
if [ -z "$_latest_stable$_latest_beta$_latest_alpha" ]; then
continue
fi
printf ' <section class="rel-tool">\n'
printf ' <h2>%s</h2>\n' "$_title"
# Channel chips. Each link uses /releases/<tag>/<asset>
# which Caddy proxies to the Codeberg release-asset URL.
# Asset name is <tool>_v<version>.html for HTML tools; for
# zddc-server we link to the Codeberg release page since
# the asset is per-platform (operator picks one).
printf ' <div class="rel-channels">\n'
for _row in "stable|$_latest_stable" "beta|$_latest_beta" "alpha|$_latest_alpha"; do
_ch="${_row%%|*}"
_tag="${_row#*|}"
[ -n "$_tag" ] || continue
if [ "$_tool" = "zddc-server" ]; then
printf ' <a class="%s" href="https://codeberg.org/VARASYS/ZDDC/releases/tag/%s">%s</a>\n' \
"$_ch" "$_tag" "$_ch"
else
_ver="${_tag#${_tool}-v}"
_asset="${_tool}_v${_ver}.html"
printf ' <a class="%s" href="/releases/%s/%s">%s</a>\n' \
"$_ch" "$_tag" "$_asset" "$_ch"
fi
done
printf ' </div>\n'
# zddc-server: per-platform binary table, one row per channel.
if [ "$_tool" = "zddc-server" ]; then
_platforms="linux-amd64 darwin-amd64 darwin-arm64 windows-amd64"
printf ' <h3 style="font-size:1rem;margin:0.75rem 0 0.4rem;">Standalone binaries</h3>\n'
printf ' <table class="rel-bin-table"><thead><tr><th>Channel</th>'
for _p in $_platforms; do printf '<th>%s</th>' "$_p"; done
printf '</tr></thead><tbody>\n'
for _row in "stable|$_latest_stable" "beta|$_latest_beta" "alpha|$_latest_alpha"; do
_ch="${_row%%|*}"
_tag="${_row#*|}"
printf ' <tr><td class="ch-%s">%s</td>' "$_ch" "$_ch"
if [ -z "$_tag" ]; then
for _p in $_platforms; do printf '<td class="empty">—</td>'; done
else
for _p in $_platforms; do
_ext=""; case "$_p" in windows-*) _ext=".exe" ;; esac
_asset="zddc-server-${_p}${_ext}"
printf '<td><a href="/releases/%s/%s">download</a></td>' "$_tag" "$_asset"
done
fi
printf '</tr>\n'
done
printf ' </tbody></table>\n'
fi
# Pin-to-version row. All stables (semver-descending). HTML
# tools link to the asset directly; zddc-server links to the
# Codeberg release page (per-platform asset choice).
if [ -n "$_all_stables" ]; then
printf ' <div class="rel-versions"><strong>Pin to version:</strong>\n'
printf '%s\n' "$_all_stables" | while read -r _t; do
[ -n "$_t" ] || continue
_v="${_t#${_tool}-v}"
if [ "$_tool" = "zddc-server" ]; then
printf ' <a href="https://codeberg.org/VARASYS/ZDDC/releases/tag/%s">v%s</a>\n' "$_t" "$_v"
else
printf ' <a href="/releases/%s/%s_v%s.html">v%s</a>\n' "$_t" "$_tool" "$_v" "$_v"
fi
done
printf ' </div>\n'
fi
printf ' </section>\n'
done
cat <<'TAIL'
<section style="margin-top: var(--spacing-2xl); color: var(--color-text-muted); font-size: 0.9rem;">
<p>Append <code>?v=alpha</code>, <code>?v=beta</code>, <code>?v=stable</code>, or <code>?v=0.0.1</code> to any deployment URL to switch versions for a single request — see <a href="../">the home page</a>.</p>
</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>
</body>
</html>
TAIL
} > "$_out"
echo "Wrote $_out"
}
echo ""
echo "=== Building bootstrap stubs and releases/index.html ==="
build_bootstrap_stubs
build_releases_index
echo ""
echo "=== All tools built successfully ==="
echo ""
echo "Server deployment package: zddc/dist/"
echo " Binaries: zddc-server-{linux,darwin,windows}-*"
echo " Web files: web/ (copy contents to ZDDC_ROOT)"
echo ""
echo "Bootstrap stubs: website/bootstrap/"
echo " level1/<tool>.html — same-origin stubs for project subdirs"
echo " track-{alpha,beta,stable}/ — level-2 stubs for each channel"
echo ""
echo "The home page's 'Install on your server' section prints copy-pasteable"
echo "shell snippets that curl these files into the operator's deployment dir."