diff --git a/browse/build.sh b/browse/build.sh index d976367..1a0927f 100755 --- a/browse/build.sh +++ b/browse/build.sh @@ -116,4 +116,7 @@ echo "Wrote $output_html" # Promote AFTER the dist file exists so promote_release can copy from # $output_html. (The order matters — _promote_stable does cp $output_html ...) -promote_release "$tool" +# Only fires on a release cut; plain dev builds leave release-output alone. +if [ "$is_release" = "1" ]; then + promote_release "$tool" +fi diff --git a/build b/build index 4aef5a4..83a980c 100755 --- a/build +++ b/build @@ -6,68 +6,68 @@ set -eu # ./build dev build: assemble tool dist/, cross-compile # zddc-server binaries. Nothing else is touched # — no release artifacts produced, no deploy, -# and zddc/internal/apps/embedded/ is left alone +# zddc/internal/apps/embedded/ is left alone # (binary will embed whatever the last beta or # stable cut committed there). -# ./build alpha cut alpha: produce a complete release bundle -# in dist/release-output/ (cascades nothing). -# Like dev, embedded/ is NOT updated — the -# invariant is that alpha labels are never baked -# into the binary. -# ./build beta cut beta (cascades alpha → beta). Updates -# embedded/ with beta-labeled tool HTMLs and -# commits them — the dev image (which builds -# from main) ships those bytes. -# ./build release cut coordinated stable (cascades alpha + beta -# → new stable; updates embedded/ with stable -# labels, makes a release commit, tags all -# seven tools at that commit). Prod images -# (which build from the latest stable tag) -# ship those bytes. +# ./build beta internal SHA snapshot for the BMC dev chart. +# Updates embedded/ with current tool HTMLs + +# makes a `chore(embedded): cut v-beta` +# commit; the chart's appVersion pins to that +# SHA via Dockerfile parsing. NO public +# artifact in dist/release-output/. +# ./build release cut coordinated stable. Updates embedded/ +# with stable-labeled bytes, makes a release +# commit, tags all 8 artifacts at that commit, +# writes _v.html + .html +# symlink for every tool and the zddc-server +# per-platform binaries into +# dist/release-output/. # ./build release X.Y.Z same, explicit version. # ./build help this message. # -# Lockstep: every channel/release cut bumps all seven tools (6 HTML + -# zddc-server) together. Coordinated next-stable = max(latest tag) + 1. +# Lockstep: every release cut bumps all 8 tools (7 HTML + zddc-server) +# together. Coordinated next-stable = max(latest tag) + 1. # -# Channel/release cuts write a complete intended-live snapshot to +# Stable release cuts write a complete intended-live snapshot to # ${ZDDC_DEPLOY_RELEASES_DIR:-$SCRIPT_DIR/dist/release-output}. The build -# does NOT touch the live site — run `./deploy` (or `./deploy --releases`) -# to rsync the snapshot into /srv/zddc/. The snapshot is built by seeding -# from the current live state (so cascades and the verifier see a -# complete world), then mutating the channel(s) being cut on top. +# does NOT touch the live site — run `./deploy --releases` to rsync the +# snapshot into /srv/zddc/. The snapshot is seeded from the current +# live state's per-version files (so older immutable artifacts are +# preserved), then this cut's .html canonical symlinks + new +# per-version file are written on top. # # Bake-in invariant (what zddc-server's binary embeds via //go:embed): -# - prod image (Dockerfile.prod, ZDDC_REF=stable): always stable bytes -# - dev image (Dockerfile, ZDDC_REF=main): stable OR beta bytes -# (whatever last beta/ -# stable cut wrote) -# - alpha is NEVER baked in. Active dev iteration happens via the tool's -# local dist/.html, not via the binary's embedded copy. +# - prod image (Dockerfile.prod): always stable bytes — chart's +# Dockerfile.prod fetches the source at the latest +# zddc-server-vX.Y.Z tag. +# - dev image (Dockerfile): stable OR beta-snapshot bytes — the +# chart's appVersion is set to either "X.Y.Z" (stable) +# or "X.Y.Z-beta-" (snapshot), and Dockerfile +# fetches that ref. Dev builds (`./build` no-arg) do +# NOT touch embedded/, so the binary's baked copy stays +# at whatever the last beta or stable cut wrote. 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. +# (promote_zddc_server, write_zddc_server_stubs_all, +# _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 empty means dev mode (build only, no website worktree -# writes); set means a channel/release cut that promotes to the website -# worktree under $ZDDC_DEPLOY_RELEASES_DIR. +# RELEASE_CHANNEL empty means dev mode (build only, no release output); +# "beta" means an internal SHA snapshot (regenerate embedded/ + commit, +# no public artifact); "stable" means a coordinated release cut that +# writes to dist/release-output/. RELEASE_CHANNEL="" RELEASE_VERSION="" case "${1:-dev}" in dev|build) # Dev build: tool dist/ + zddc-server binaries only. Touches - # nothing in the website worktree. - ;; - alpha) - RELEASE_CHANNEL="alpha" + # nothing in release-output. ;; beta) RELEASE_CHANNEL="beta" @@ -84,7 +84,7 @@ case "${1:-dev}" in fi ;; help | -h | --help) - sed -n '4,22p' "$0" | sed 's/^# \{0,1\}//' + sed -n '4,30p' "$0" | sed 's/^# \{0,1\}//' exit 0 ;; *) @@ -112,32 +112,38 @@ export ZDDC_DEPLOY_RELEASES_DIR="${ZDDC_DEPLOY_RELEASES_DIR:-$SCRIPT_DIR/dist/re RELEASES_DIR="$ZDDC_DEPLOY_RELEASES_DIR" mkdir -p "$RELEASES_DIR" -# When cutting a channel/release, seed RELEASES_DIR from the current live -# site so the resulting bundle is a complete intended-live snapshot, not -# a sparse one-channel diff. Two reasons: -# 1. Per-tool promote_release does cascade writes (beta cut → also -# rewrites alpha to track beta; stable cut → resets alpha + beta). -# The cascade itself is deterministic, but downstream artifacts that -# were NOT touched by this cut (e.g. older versioned files, the -# other channel mirrors, partial-version symlinks) still need to be -# present in the bundle so `./deploy --releases` (rsync -# --delete-after) doesn't wipe them off the live site. -# 2. verify_channel_links cross-checks the full release tree; it -# flags absent channels as missing. With seeding, a fresh -# `dist/release-output/` matches live state, the cut mutates on -# top, and the verifier sees a complete world. -# Bootstrap case (no live site yet, or live releases dir empty) is +# On a stable cut, seed RELEASES_DIR from the current live site so the +# resulting bundle is a complete intended-live snapshot, not a sparse +# diff. The seed copies the immutable per-version files +# (_v.html, zddc-server_v_) plus their .sig +# files. The cut then writes this version's new per-version files + +# refreshes the canonical .html / zddc-server_ symlinks on +# top. `./deploy --releases` (rsync --delete-after) wipes any stale +# files in /srv/zddc/releases/ that aren't in the bundle. +# +# We skip the seed for beta cuts (no public artifacts to produce). +# Bootstrap case (no live site yet, or empty live releases dir) is # silently skipped — the very first stable cut populates everything. -if [ -n "$RELEASE_CHANNEL" ]; then +if [ "$RELEASE_CHANNEL" = "stable" ]; then LIVE_RELEASES="${ZDDC_LIVE_DIR:-/srv/zddc}/releases" if [ -d "$LIVE_RELEASES" ] && [ -n "$(ls -A "$LIVE_RELEASES" 2>/dev/null)" ]; then - echo "=== Seeding $RELEASES_DIR from $LIVE_RELEASES ===" + echo "=== Seeding $RELEASES_DIR from $LIVE_RELEASES (per-version artifacts only) ===" rm -rf "$RELEASES_DIR" mkdir -p "$RELEASES_DIR" - # cp -a preserves the symlink graph (channel mirrors + - # _v / _v partial-version pins) so cascade decisions - # downstream see the same world the live site has. - cp -a "$LIVE_RELEASES/." "$RELEASES_DIR/" + # Copy per-version immutable files + their .sig sidecars. The + # canonical .html / zddc-server_ symlinks will be + # rewritten by this cut and old channel/partial-pin files from + # the previous layout (if any still live there) will be cleaned + # up by deploy's --delete-after rsync. + find "$LIVE_RELEASES" -maxdepth 1 -type f \( \ + -name '*_v*.html' -o \ + -name '*_v*.html.sig' -o \ + -name 'zddc-server_v*' \ + \) -exec cp -a '{}' "$RELEASES_DIR/" \; + # Also seed the public key (it lives at the releases root). + if [ -f "$LIVE_RELEASES/pubkey.pem" ]; then + cp -a "$LIVE_RELEASES/pubkey.pem" "$RELEASES_DIR/" + fi fi fi @@ -183,11 +189,10 @@ echo "Wrote zddc/dist/web/{index,archive,transmittal,classifier,form,tables,brow # Mirror the cascade-served HTMLs into the apps embed source dir so the # next `go build` of zddc-server picks them up via //go:embed. ONLY happens -# on a beta or stable cut — that's the project invariant: alpha labels are -# never baked into the binary, beta labels go to the dev image (which builds -# from main), and stable labels go to prod (which builds from the latest -# stable tag). Plain `./build` and `./build alpha` leave the embedded files -# untouched; whatever the last beta/stable cut committed remains in place. +# on a beta or stable cut — beta cuts feed the dev image (chart pins by +# SHA to the embedded-commit), stable cuts feed the prod image (chart +# pins to the tag). Plain `./build` leaves embedded files untouched — +# whatever the last beta or stable cut committed stays in place. EMBED_DIR="$SCRIPT_DIR/zddc/internal/apps/embedded" if [ "$RELEASE_CHANNEL" = "beta" ] || [ "$RELEASE_CHANNEL" = "stable" ]; then mkdir -p "$EMBED_DIR" @@ -304,10 +309,12 @@ echo " binary version: $ZDDC_BINARY_VERSION" ' # --- Sign release artifacts ----------------------------------------------- -# After a channel/release cut has populated $RELEASES_DIR with the actual -# bytes for this build, walk the dir and produce a detached Ed25519 .sig -# alongside every real artifact. Symlinks (channel mirrors, partial-version -# pins) skip — the .sig at the symlink's target is what counts. +# After a stable cut has populated $RELEASES_DIR with the actual bytes +# for this build, walk the dir and produce a detached Ed25519 .sig +# alongside every immutable per-version artifact. Canonical symlinks +# (.html, zddc-server_) skip — the .sig at the symlink's +# target is what counts, and a companion .sig symlink (also written +# by promote_release) chains the canonical .sig URL to that target. # # Operators verify with stdlib openssl: # @@ -341,14 +348,15 @@ sign_release_artifacts() { fi # Collect the artifact list first so the signing loop runs in this # shell (no subshell counter scope issue). find: real files only - # (-P, the default), matching _v*.html, _.html, - # and zddc-server_v*_(.exe). Excludes the index, stub pages, - # and any pre-existing .sig files. + # (-P, the default), matching _v*.html and + # zddc-server_v*_(.exe). The canonical symlinks (.html / + # zddc-server_) don't get separate .sig files — verification + # follows the symlink to the immutable per-version file whose .sig + # is signed below. Excludes the index, stub pages, and pre-existing + # .sig files. _list=$(find "$_dir" -maxdepth 1 -type f \( \ -name '*_v*.html' -o \ - -name '*_stable.html' -o -name '*_beta.html' -o -name '*_alpha.html' -o \ - -name 'zddc-server_v*' -o \ - -name 'zddc-server_stable_*' -o -name 'zddc-server_beta_*' -o -name 'zddc-server_alpha_*' \ + -name 'zddc-server_v*' \ \) ! -name '*.sig' ! -name 'index.html' ! -name 'zddc-server_*.html' 2>/dev/null) _signed=0 @@ -377,22 +385,22 @@ sign_release_artifacts() { } # --- Promote zddc-server release artifacts --------------------------------- -# On a channel/release cut, 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. On a plain dev build, skip — we don't touch the worktree. -if [ -n "$RELEASE_CHANNEL" ]; then +# On a stable cut, copy the freshly cross-compiled binaries to the +# release-output bundle under their canonical names + symlinks. +# promote_zddc_server also re-runs write_zddc_server_stubs_all +# internally, so the per-version + canonical stub pages get regenerated +# in the same call. Beta cuts produce no public binary artifact. +if [ "$RELEASE_CHANNEL" = "stable" ]; then echo "" - echo "=== Promoting zddc-server $RELEASE_CHANNEL release ===" - promote_zddc_server "$RELEASE_CHANNEL" "$RELEASE_VERSION" "$RELEASES_DIR" "$SCRIPT_DIR/zddc/dist" + echo "=== Promoting zddc-server stable release ===" + promote_zddc_server "stable" "$RELEASE_VERSION" "$RELEASES_DIR" "$SCRIPT_DIR/zddc/dist" fi -# Latest stable version, by following archive_stable.html → versioned target. +# Latest stable version, by following archive.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" + _link="$RELEASES_DIR/archive.html" [ -L "$_link" ] || return 0 _target=$(readlink "$_link") # archive_v0.0.8.html → 0.0.8 @@ -403,18 +411,6 @@ _latest_stable_version() { 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 @@ -450,9 +446,6 @@ build_releases_index() { | sort -Vr ) - _alpha_active="0"; _channel_is_active alpha && _alpha_active="1" - _beta_active="0"; _channel_is_active beta && _beta_active="1" - { cat < @@ -503,39 +496,21 @@ build_releases_index() { @@ -552,24 +527,24 @@ PICKER_END # 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 + if [ -e "$RELEASES_DIR/zddc-server_linux-amd64" ]; then _zs_published="1" fi if [ "$_zs_published" = "1" ]; then - # Default href is the channel-mirror URL (zddc-server_stable_) - # 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. + # Default href is the canonical per-platform URL + # (zddc-server_), a symlink that always points at the + # latest stable. Picking a pinned version from the dropdown + # rewrites these to the immutable per-version URL via JS. printf ' \n' printf ' \n' printf ' Download for Linux (x86_64)\n' printf ' \n' - printf ' zddc-server_stable_linux-amd64\n' + printf ' zddc-server_linux-amd64\n' printf '
\n' printf ' Other platforms:\n' @@ -581,7 +556,7 @@ PICKER_END _label="${_entry#*|}" _suffix="" case "$_plat" in *windows*) _suffix=".exe" ;; esac - printf ' %s\n' \ + printf ' %s\n' \ "$_plat" "$_plat" "$_suffix" "$_label" done printf '
\n' @@ -622,9 +597,9 @@ PATH_B_OPEN _rest="${_entry#*|}" _name="${_rest%%|*}" _desc="${_rest#*|}" - # Default href is the stable-channel mirror; the dropdown - # rewires these per selection. - printf ' \n' "$_t" "$_t" + # Default href is the canonical symlink .html; the + # dropdown rewires these per selection. + printf ' \n' "$_t" "$_t" printf ' %s\n' "$_name" printf ' %s\n' "$_desc" printf ' Download →\n' @@ -717,12 +692,12 @@ PIN_MID

Verify a download

-

Each artifact has a matching .sig file alongside it (archive_stable.htmlarchive_stable.html.sig, etc.). Fetch both, then:

-
curl -O https://zddc.varasys.io/releases/archive_stable.html
-curl -O https://zddc.varasys.io/releases/archive_stable.html.sig
+          

Each artifact has a matching .sig file alongside it (archive.htmlarchive.html.sig, etc.). Fetch both, then:

+
curl -O https://zddc.varasys.io/releases/archive.html
+curl -O https://zddc.varasys.io/releases/archive.html.sig
 openssl pkeyutl -verify -pubin -inkey pubkey.pem \
-    -rawin -in archive_stable.html \
-    -sigfile archive_stable.html.sig
+ -rawin -in archive.html \ + -sigfile archive.html.sig

Output is Signature Verified Successfully on a clean download. Any other output (or no output and a non-zero exit) means the bytes do not match the published signature — do not trust them.

@@ -745,7 +720,8 @@ ZDDC_ROOT=/srv/zddc ./zddc-server
# <ZDDC_ROOT>/.zddc
 admins: [you@yourcompany.com]
 acl:
-  allow: ["*@yourcompany.com"]
+  permissions:
+    '*@yourcompany.com': r
 apps_pubkey: |
   -----BEGIN PUBLIC KEY-----
   MCowBQYDK2VwAyEAXXaxIUIyOFnhD1eZs02nEt3xZ8izOi7bURFcpJ9iWZY=
@@ -756,25 +732,6 @@ apps_pubkey: |
       

When configured, the resolver fetches the .sig automatically on every URL-pinned apps: entry and rejects any unsigned or invalid-signature artifact, falling back to the embedded copy. Operators enforcing signature verification on locally-saved artifacts (Path A, "drop a copy into your archive") run the openssl verify command above as part of their save workflow.

- -
-

Channels

-

Three channels, applied in lockstep across all tools. Pre-release channels exist to soak changes; stable is what production runs.

-
-
-

alpha

-

Active dev iteration. Rebuilds without notice. Look here for the very latest.

-
-
-

beta

-

Ready for general testing. Has soaked through alpha. Still mutable — pin to a versioned URL for reproducibility.

-
-
-

stable

-

Ready to ship. Every per-version file is immutable; _stable follows the latest cut. Channel cuts cascade: stable cut resets beta and alpha to track stable.

-
-
-