docs: clean up drift left over from the Codeberg release-assets refactor

The 2dc9ad2 commit ("refactor: distribute via Codeberg release assets,
drop the upstream image") rewrote AGENTS.md and CLAUDE.md but left
several pre-existing references to the old write-to-website/releases
flow and the now-removed Containerfile / podman-compose / release-image.sh.
This sweeps the rest:

- CLAUDE.md
  - drop "podman/podman-compose" from the zddc/ blurb (no Containerfile)
  - drop the broken `podman build -t zddc-server zddc/` command
  - rewrite the "Most-used commands" table so --release semantics match
    actual behavior (tag + Codeberg upload, not file write)
  - rewrite "Things that bite": replace "never write to website/releases/"
    and the obsolete "alpha exception" bullet with the new rules
    ($CODEBERG_TOKEN required, dist files no longer force-tracked, etc.)
  - rewrite the website/ description in "Repo shape" to reflect that
    only index.html + manifest.json live there now

- ARCHITECTURE.md
  - rewrite the website/ directory tree (no more <tool>_v*.html, _stable
    symlinks, or _alpha/_beta files)
  - rewrite "Channels" section: every cut now tags + uploads to Codeberg,
    alpha/beta have .N counters and matching tags, no more in-place
    overwrites
  - rewrite the build-label table: dev builds carry the next-stable
    target as a -alpha pre-release suffix with full timestamp + dirty
    marker (was: "Built: <ts> BETA")
  - update level-2 bootstrap description: resolves channel via
    manifest.json, fetches /releases/<tag>/<asset>, not a flat URL
  - update landing-tool description: ships only as Codeberg release
    asset, not a committed website/releases/landing_v<X>.html

- AGENTS.md
  - update website/ tree to the post-refactor layout
  - replace the two-step podman build / podman-compose run blocks under
    zddc-server with a Go build + go run quickstart (no container in
    this repo)
  - drop the "Containerfile uses a multi-stage build" note from the
    "Notes" list (Containerfile is gone)
  - drop the stale "landing/build.sh writes website/index.html" note —
    website/index.html is now hand-edited, not produced by landing's
    build

- README.md (top-level)
  - tools table no longer links to /releases/<tool>_stable.html
    (those URLs return 404 post-refactor); link to the releases page
    once instead

- bootstrap/README.md
  - update the "permanent pin" URL examples and CORS verification
    snippet to use /releases/<tag>/<asset> URLs (Caddy → Codeberg)
    instead of the old flat /releases/<tool>_<channel>.html pattern
  - explain that channel resolution is via manifest.json now

- zddc/README.md
  - rewrite Quick Start: download a release binary or build from source,
    no `podman build`
  - rewrite TLS examples to invoke ./zddc-server directly instead of
    `podman run ... zddc-server` (image name no longer exists)
  - mention ZDDC_INSECURE_DIRECT in the env-var table and the plain-HTTP
    example — startup is refused without it on non-loopback binds
  - replace the "Container image" section with "Distribution" (binaries
    on Codeberg, no image) and the "Building" section with go build
    instructions
  - replace "Release Tagging" with documentation of zddc/release.sh
    (the canonical replacement for release-image.sh, which is gone)

- shared/build-lib.sh
  - fix the comment claiming "plain builds mirror to website/releases/"
    — they don't anymore

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZDDC 2026-04-30 08:01:20 -05:00
parent b28c4aef81
commit bdac8dc4fb
7 changed files with 188 additions and 181 deletions

View file

@ -3,17 +3,17 @@
## Commands ## Commands
```bash ```bash
# Build all tools (writes to dist/ only) # Build all tools (writes to dist/ only; also regenerates website/releases/{index.html,manifest.json})
sh build.sh sh build.sh
# Build single tool # Build single tool
sh tool/build.sh # archive | transmittal | classifier | mdedit | landing sh tool/build.sh # archive | transmittal | classifier | mdedit | landing
# Cut a stable release (auto-increments patch version, tags, writes to website/releases/) # Cut a stable release (auto-increments patch version, tags <tool>-vX.Y.Z, uploads <tool>_vX.Y.Z.html to Codeberg)
sh tool/build.sh --release sh tool/build.sh --release
sh tool/build.sh --release 1.2.0 # explicit version sh tool/build.sh --release 1.2.0 # explicit version
# Cut an alpha/beta channel build (mutable, no git tag) # Cut an alpha/beta channel build (tags <tool>-vX.Y.Z-{alpha,beta}.N, uploads to Codeberg as a prerelease)
sh tool/build.sh --release alpha sh tool/build.sh --release alpha
sh tool/build.sh --release beta sh tool/build.sh --release beta
@ -55,12 +55,10 @@ shared/
sourced by every tool's build.sh via: . "$root_dir/../shared/build-lib.sh" sourced by every tool's build.sh via: . "$root_dir/../shared/build-lib.sh"
website/ website/
index.html current stable landing (root URL) index.html hand-edited intro page (root URL)
releases/ releases/
<tool>_v<X>.<Y>.<Z>.html immutable stable release archives index.html versions index, regenerated by build.sh from the Codeberg release list
<tool>_stable.html -> ... symlink to current stable (highest semver) manifest.json <tool>-<channel> → tag map (regenerated by build.sh; consumed by the level-2 stub at runtime)
<tool>_alpha.html mutable; overwritten by --release alpha
<tool>_beta.html mutable; overwritten by --release beta
bootstrap/ bootstrap/
level1/<tool>.html same-origin level-1 stubs (4 tools, no landing) level1/<tool>.html same-origin level-1 stubs (4 tools, no landing)
track-stable/<tool>.html level-2 stubs that track the current-stable channel track-stable/<tool>.html level-2 stubs that track the current-stable channel
@ -73,7 +71,9 @@ bootstrap/
README.md install / channel / pin docs README.md install / channel / pin docs
``` ```
**Critical:** `dist/` files are gitignored but force-committed (`git add -f`). Never edit them directly. **Critical:** `dist/` files are gitignored. They're the canonical built artifact for testing and the source for `--release` uploads to Codeberg, but they aren't checked in. Never edit them directly.
The per-version `<tool>_v<X.Y.Z>.html` artifacts and zddc-server binaries also aren't checked in — they live on Codeberg as release assets attached to git tags. `website/releases/` only contains `index.html` (versions index) and `manifest.json` (channel → tag map), both regenerated by `build.sh` from a Codeberg API call.
## Shared CSS (`shared/base.css`) ## Shared CSS (`shared/base.css`)
@ -177,7 +177,7 @@ After cutting a release, run `git push --tags` to publish the tag.
`$CODEBERG_TOKEN` must be exported before any `--release` invocation. The `promote_release` helper calls `publish_codeberg_release` which uses the token to create the release and upload the asset. `$CODEBERG_TOKEN` must be exported before any `--release` invocation. The `promote_release` helper calls `publish_codeberg_release` which uses the token to create the release and upload the asset.
`landing/build.sh --release <version>` additionally writes `website/index.html` (the root URL of zddc.varasys.io) as a regular committed file — that page is hand-edited intro copy, not a release asset. `website/index.html` (the root URL of zddc.varasys.io) is **hand-edited static content**, not built by `landing/build.sh`. The landing tool ships only as a Codeberg release asset; the self-contained install snippet curls `landing_v<ver>.html` to `<deployment-root>/index.html` at customer-deployment time.
### Channel discipline (MUST rules) ### Channel discipline (MUST rules)
@ -251,18 +251,30 @@ Go HTTP server sub-project living at `zddc/`. Replaces `caddy file-server --brow
### Build ### Build
```sh zddc-server ships as a cross-compiled binary, not a container image. There's no Containerfile or compose file in this repo (the chart Dockerfiles in `tnd-zddc-chart` compile from source at deploy time, fetching the right tag from Codeberg).
# Build the container image (from the zddc/ directory)
podman build -t zddc-server zddc/
# Or inside the zddc/ directory: ```sh
podman build -t zddc-server . # Compile a local binary for the host platform (requires Go 1.24+)
(cd zddc && go build -o zddc-server ./cmd/zddc-server)
# Or run directly without producing a binary
(cd zddc && go run ./cmd/zddc-server)
``` ```
The repo's top-level `sh build.sh` cross-compiles the four release binaries (linux/amd64, darwin/amd64, darwin/arm64, windows/amd64) into `zddc/dist/` when Go is on PATH. It's silently skipped otherwise — the HTML tools build regardless.
### Run (development) ### Run (development)
```sh ```sh
ZDDC_DATA_DIR=/path/to/your/archive podman-compose -f zddc/podman-compose.yaml up --build ZDDC_ROOT=/path/to/your/archive ZDDC_TLS_CERT=none ZDDC_ADDR=:8080 \
go run ./cmd/zddc-server
```
For a release binary (downloaded from Codeberg or built via `sh build.sh`):
```sh
ZDDC_ROOT=/path/to/your/archive ZDDC_TLS_CERT=none ZDDC_ADDR=:8080 \
./zddc/dist/zddc-server-linux-amd64
``` ```
### Key environment variables ### Key environment variables
@ -342,7 +354,6 @@ local path that fails loudly and visibly on the developer's terminal.
### Notes ### Notes
- No external test framework yet — Go unit tests run with `go test ./...` inside `zddc/` (requires Go 1.24+) - No external test framework yet — Go unit tests run with `go test ./...` inside `zddc/` (requires Go 1.24+)
- The container image does NOT require Go on the host — the Containerfile uses a multi-stage build
- Portfolio files (`*.portfolio`) in the served tree appear as virtual group directories - Portfolio files (`*.portfolio`) in the served tree appear as virtual group directories
- Every folder exposes a `.archive` virtual directory backed by the same global index — the depth in the URL only matters so HTML produced for offline use can reach `.archive/` via `../.archive/` relative links and have the browser resolve them before the request hits the server. The flat listing emits two redirect entries per tracking number: `<tracking>.html` (highest base rev) and `<tracking>_<rev>.html` (each specific base rev). Both redirect to the first chronologically received copy of the named revision. Modifier files (`<tracking>_<rev>+C1.html` etc.) remain reachable via the resolver but are not surfaced in the listing — they're return traffic, not primary documents. ACL is the only filter: the listing endpoint is gated by the contextPath's `.zddc` chain, and each entry is then filtered against the ACL of its resolved file's directory; per-target denials return 404 rather than 403 to avoid leaking that the tracking number exists in another subtree - Every folder exposes a `.archive` virtual directory backed by the same global index — the depth in the URL only matters so HTML produced for offline use can reach `.archive/` via `../.archive/` relative links and have the browser resolve them before the request hits the server. The flat listing emits two redirect entries per tracking number: `<tracking>.html` (highest base rev) and `<tracking>_<rev>.html` (each specific base rev). Both redirect to the first chronologically received copy of the named revision. Modifier files (`<tracking>_<rev>+C1.html` etc.) remain reachable via the resolver but are not surfaced in the listing — they're return traffic, not primary documents. ACL is the only filter: the listing endpoint is gated by the contextPath's `.zddc` chain, and each entry is then filtered against the ACL of its resolved file's directory; per-target denials return 404 rather than 403 to avoid leaking that the tracking number exists in another subtree
- ACL is enforced via cascading `.zddc` YAML files; authentication is delegated to the upstream proxy via the `X-Auth-Request-Email` header (configurable with `ZDDC_EMAIL_HEADER`) - ACL is enforced via cascading `.zddc` YAML files; authentication is delegated to the upstream proxy via the `X-Auth-Request-Email` header (configurable with `ZDDC_EMAIL_HEADER`)

View file

@ -33,21 +33,21 @@ tool/
tool.html # Generated output — never edit this manually tool.html # Generated output — never edit this manually
``` ```
Website files (what `zddc.varasys.io` serves) are organized by channel: Website files (what `zddc.varasys.io` serves) — committed in this repo as static assets, but the actual built tool HTML and zddc-server binaries live on Codeberg as release assets:
``` ```
website/ website/
index.html # current stable landing tool (root URL) index.html # hand-edited intro page (root URL)
releases/ releases/
<tool>_v<X>.<Y>.<Z>.html # immutable stable release archives index.html # versions index, regenerated by build.sh from the Codeberg API
<tool>_stable.html -> ... # symlink to the highest-versioned stable manifest.json # <tool>-<channel> → tag map, regenerated by build.sh; the level-2 stub fetches this
<tool>_alpha.html # mutable: overwritten on every --release alpha
<tool>_beta.html # mutable: overwritten on every --release beta
bootstrap/ bootstrap/
level1/<tool>.html # same-origin stubs for project subdirectories level1/<tool>.html # same-origin stubs for project subdirectories
track-{alpha,beta,stable}/ # per-channel level-2 stubs (5 tools each) track-{alpha,beta,stable}/ # per-channel level-2 stubs (5 tools each)
``` ```
The per-version `<tool>_v<X.Y.Z>.html` files and zddc-server binaries are **not** committed — they live on Codeberg as release assets attached to git tags. Caddy at `zddc.varasys.io` reverse-proxies `/releases/<tag>/<asset>` to the corresponding Codeberg URL, so consumers (operators' bootstrap stubs, `zddc-use`, the chart Dockerfiles) only ever talk to `zddc.varasys.io`.
There is no `website/dev/`. To preview a build locally, open `dist/tool.html` directly via the dev server. To publish on `zddc.varasys.io`, cut a release. There is no `website/dev/`. To preview a build locally, open `dist/tool.html` directly via the dev server. To publish on `zddc.varasys.io`, cut a release.
Vendor dependencies (bundled third-party libraries) live in `tool/vendor/` if present. The build script is responsible for inlining them into the output. Vendor dependencies (bundled third-party libraries) live in `tool/vendor/` if present. The build script is responsible for inlining them into the output.
@ -69,7 +69,7 @@ Each topic has exactly one authoritative home; everything else links to it.
| Architecture & internal patterns | `ARCHITECTURE.md` (this file) | `AGENTS.md` | | Architecture & internal patterns | `ARCHITECTURE.md` (this file) | `AGENTS.md` |
| Per-tool internal design quirks | `<tool>/README.md` | (linked from website intro tool cards) | | Per-tool internal design quirks | `<tool>/README.md` | (linked from website intro tool cards) |
`website/index.html` is **hand-edited static content** (analogous to `reference.html`), not the landing-tool output. The landing tool ships only via `website/releases/landing_v<X>.html` — the self-contained install snippet copies `landing_stable.html` to `<deployment-root>/index.html` for customer sites where the project picker UI is actually useful (it queries `zddc-server` for the project list). The public website at `zddc.varasys.io/` has nothing to pick, so its root URL is the introduction page. `website/index.html` is **hand-edited static content** (analogous to `reference.html`), not the landing-tool output. The landing tool ships only as a Codeberg release asset (`landing-v<X.Y.Z>` tag → `landing_v<X.Y.Z>.html` asset) — the self-contained install snippet curls the current-stable asset through Caddy at `zddc.varasys.io/releases/<tag>/landing_v<ver>.html` and saves it as `<deployment-root>/index.html` for customer sites where the project picker UI is actually useful (it queries `zddc-server` for the project list). The public website at `zddc.varasys.io/` has nothing to pick, so its root URL is the introduction page.
When updating documentation, prefer linking over duplicating. If you find yourself rewriting the file-naming convention in a tool's README, link to `reference.html` instead. When updating documentation, prefer linking over duplicating. If you find yourself rewriting the file-naming convention in a tool's README, link to `reference.html` instead.
@ -85,35 +85,39 @@ Each tool's `build.sh`:
2. Reads JS files in declaration order, concatenates them 2. Reads JS files in declaration order, concatenates them
3. Processes `template.html` with `awk`, replacing `{{PLACEHOLDER}}` markers with the concatenated content and stripping CDN `<script>`/`<link>` tags 3. Processes `template.html` with `awk`, replacing `{{PLACEHOLDER}}` markers with the concatenated content and stripping CDN `<script>`/`<link>` tags
4. Writes the result to `dist/tool.html` 4. Writes the result to `dist/tool.html`
5. If `--release <channel-or-version>` was passed, calls `promote_release` to write the appropriate file under `website/releases/` 5. If `--release <channel-or-version>` was passed, calls `promote_release` to tag the commit and upload the dist HTML as a Codeberg release asset (via `shared/publish-codeberg-release.sh`).
The top-level `build.sh` at the repository root calls all five tool build scripts in sequence and then regenerates `website/bootstrap/` (level-1 stubs and per-channel level-2 stubs) so they always match what's in `releases/`. The top-level `build.sh` at the repository root calls all five tool build scripts in sequence, regenerates `website/bootstrap/` (level-1 stubs and per-channel level-2 stubs), and then queries the Codeberg API once to rewrite `website/releases/index.html` and `manifest.json` so the website's versions index reflects current Codeberg state.
### Channels ### Channels
Three release channels: Three release channels. Each `--release` invocation tags the commit and uploads the resulting HTML to Codeberg as a release asset; nothing is written under `website/releases/` other than the regenerated `index.html` / `manifest.json`.
- **Stable** — versioned, immutable. `--release [version]` writes `website/releases/<tool>_v<version>.html`, refreshes the `<tool>_stable.html` symlink, and tags `<tool>-v<version>` in git. Skips automatically when there is no source change since the last tag. - **Stable** — versioned, immutable. `--release [version]` tags `<tool>-v<version>` in git and uploads `<tool>_v<version>.html` to the new Codeberg release. Skips automatically when there is no source change since the last stable tag.
- **Beta**mutable. `--release beta` overwrites `website/releases/<tool>_beta.html` in place. No git tag; the on-page label is `beta · <date> · <sha>` so the source is recoverable from git history via the SHA. - **Beta**`--release beta` tags `<tool>-v<next-patch>-beta.N` (auto-incrementing counter) and uploads `<tool>_v<next-patch>-beta.N.html`. The Codeberg release is marked `prerelease: true`. On-page label: `vX.Y.Z-beta · <date> · <sha>`.
- **Alpha**mutable, analogous to beta. - **Alpha**`--release alpha` tags `<tool>-v<next-patch>-alpha.N` and uploads, analogous to beta.
Stable releases do not automatically clobber `<tool>_alpha.html` / `<tool>_beta.html` — those keep whatever was last built into them. Use `./freshen-channel <tool> <channel>` (worktree-based, no manual `git checkout`) to drag a channel forward to current stable. A plain `sh tool/build.sh` (no `--release`) is a dev build: it produces `dist/<tool>.html` only, with the on-page label `vX.Y.Z-alpha · <full-ts> · <sha>[-dirty]`. No tag, no Codeberg upload.
Stable releases do not automatically advance the alpha/beta channels. Use `./freshen-channel <tool> <channel>` (worktree-based, no manual `git checkout`) to cut a fresh `-alpha.N` / `-beta.N` from the current stable tag — channel discipline rule 4 says do this after every stable release.
The on-page `{{BUILD_LABEL}}` is rendered red+bold for dev/alpha/beta builds (`is_red=1`) and black for stable releases. The label format is: The on-page `{{BUILD_LABEL}}` is rendered red+bold for dev/alpha/beta builds (`is_red=1`) and black for stable releases. The label format is:
| Build | Label | | Build | Label |
|---------------|---------------------------------------------| |--------------------|--------------------------------------------------------|
| dev | `Built: 2026-04-27 14:00:00 BETA` | | dev (no `--release`) | `v0.0.6-alpha · 2026-04-27 14:00:00 · abc1234[-dirty]` |
| alpha | `alpha · 2026-04-27 · abc1234` | | `--release alpha` | `v0.0.6-alpha · 2026-04-27 · abc1234` |
| beta | `beta · 2026-04-27 · abc1234` | | `--release beta` | `v0.0.6-beta · 2026-04-27 · abc1234` |
| stable | `v0.0.5` | | `--release [ver]` | `v0.0.5` |
`X.Y.Z` for non-stable labels is the **next-stable target** — patch+1 from the latest clean `<tool>-vX.Y.Z` tag. Dev builds use the full timestamp + `-dirty` marker so iterative work is distinguishable from a formal `--release alpha` cut (which stamps date-only and is committed-clean by definition).
### Two-level bootstrap ### Two-level bootstrap
Customer deployments under `zddc-server` use a two-level bootstrap pattern that keeps tool installation decoupled from publishing. See `bootstrap/README.md` for the full story; in short: Customer deployments under `zddc-server` use a two-level bootstrap pattern that keeps tool installation decoupled from publishing. See `bootstrap/README.md` for the full story; in short:
- **Level 1**: per-project stub at `<project>/<tool>.html` that fetches `../<tool>.html` (always same-origin). One file per project per tool, never edited after install. - **Level 1**: per-project stub at `<project>/<tool>.html` that fetches `../<tool>.html` (always same-origin). One file per project per tool, never edited after install.
- **Level 2** (optional): site admin replaces `<deployment-root>/<tool>.html` with a stub fetching `https://zddc.varasys.io/releases/<tool>_<channel>.html` — switches the whole site to a channel. Without it, `<deployment-root>/<tool>.html` is just the actual built tool HTML (self-contained install). - **Level 2** (optional): site admin replaces `<deployment-root>/<tool>.html` with a stub that fetches `https://zddc.varasys.io/releases/manifest.json` to resolve `<tool>-<channel>` → tag, then fetches `https://zddc.varasys.io/releases/<tag>/<tool>_v<version>.html` (Caddy reverse-proxies to the Codeberg release-asset URL). Switches the whole site to a channel. Without it, `<deployment-root>/<tool>.html` is just the actual built tool HTML (self-contained install).
`document.write()` chains across both levels; origin stays at the deployment domain throughout. CORS only matters at level 2 (cross-origin to `zddc.varasys.io`); level 1 is same-origin. `document.write()` chains across both levels; origin stays at the deployment domain throughout. CORS only matters at level 2 (cross-origin to `zddc.varasys.io`); level 1 is same-origin.

View file

@ -16,18 +16,18 @@ If something in this CLAUDE.md conflicts with those, those win — and please up
This is a **monorepo of independent tools**, not one application: This is a **monorepo of independent tools**, not one application:
- `archive/`, `transmittal/`, `classifier/`, `mdedit/`, `landing/` — five self-contained HTML tools, each compiled to a single inlined HTML file in its own `dist/`. Naming: the first four output `dist/tool.html`; **`landing/` outputs `dist/index.html`** (it's the project picker served at the root of `zddc-server`). - `archive/`, `transmittal/`, `classifier/`, `mdedit/`, `landing/` — five self-contained HTML tools, each compiled to a single inlined HTML file in its own `dist/`. Naming: the first four output `dist/tool.html`; **`landing/` outputs `dist/index.html`** (it's the project picker served at the root of `zddc-server`).
- `zddc/` — Go HTTP server (separate sub-project, podman/podman-compose; Go 1.24+). Serves `ZDDC_ROOT/index.html` at `GET /` as the landing page; `Accept: application/json` on `/` returns the ACL-filtered project list. - `zddc/` — Go HTTP server (separate sub-project; Go 1.24+). Serves `ZDDC_ROOT/index.html` at `GET /` as the landing page; `Accept: application/json` on `/` returns the ACL-filtered project list. Released as cross-compiled binaries on Codeberg (no container image — the chart Dockerfiles compile from source at deploy time).
- `shared/``base.css` plus shared JS modules (`zddc.js`, `hash.js`, `zddc-filter.js`, `theme.js`, `help.js`) included by every tool's build, and `build-lib.sh` (POSIX sh helpers sourced by every tool's `build.sh`) - `shared/``base.css` plus shared JS modules (`zddc.js`, `hash.js`, `zddc-filter.js`, `theme.js`, `help.js`) included by every tool's build, `build-lib.sh` (POSIX sh helpers sourced by every tool's `build.sh`), and `publish-codeberg-release.sh` (the create-release-and-upload-asset helper used by both `build-lib.sh` and `zddc/release.sh`).
- `website/` — published artifacts: `index.html` (root URL), `releases/<tool>_v<X.Y.Z>.html` (immutable stable archives), `releases/<tool>_stable.html` (symlink to current stable), `releases/<tool>_{alpha,beta}.html` (mutable channel files), and `bootstrap/{level1,track-stable,track-beta,track-alpha}/<tool>.html` (per-channel level-2 stubs + same-origin level-1 stubs that the home-page install snippets curl into the operator's deployment dir). `--release` is the only path to publishing `releases/`; `bootstrap/` is regenerated by every plain `sh build.sh`. There is no `website/dev/`. - `website/` — published artifacts: `index.html` (root URL), `releases/index.html` + `releases/manifest.json` (regenerated by `build.sh` from the Codeberg API; the manifest maps `<tool>-<channel>` → tag for runtime channel resolution), and `bootstrap/{level1,track-stable,track-beta,track-alpha}/<tool>.html` (per-channel level-2 stubs + same-origin level-1 stubs that the home-page install snippets curl into the operator's deployment dir). The actual built tool HTML and zddc-server binaries live on Codeberg as release assets — Caddy at `zddc.varasys.io` reverse-proxies `/releases/<tag>/<asset>` to the corresponding Codeberg URL. `bootstrap/` is regenerated by every plain `sh build.sh`. There is no `website/dev/`.
- `tests/` — Playwright specs (Chromium only, requires File System Access API). `tests/schema.spec.js` validates `transmittal.schema.json` against canonical fixtures via `ajv` (only dev dep besides Playwright) - `tests/` — Playwright specs (Chromium only, requires File System Access API). `tests/schema.spec.js` validates `transmittal.schema.json` against canonical fixtures via `ajv` (only dev dep besides Playwright)
## Most-used commands ## Most-used commands
```bash ```bash
sh build.sh # build all five HTML tools (dist/ only) sh build.sh # build all five HTML tools (dist/ only) + regen releases index/manifest
sh tool/build.sh # build one (archive|transmittal|classifier|mdedit|landing) sh tool/build.sh # build one (archive|transmittal|classifier|mdedit|landing)
sh tool/build.sh --release [version] # cut stable; tag, write website/releases/<tool>_v<ver>.html, refresh _stable symlink sh tool/build.sh --release [version] # cut stable; tag <tool>-vX.Y.Z and upload <tool>_vX.Y.Z.html to Codeberg
sh tool/build.sh --release alpha|beta # cut channel build; overwrites website/releases/<tool>_<channel>.html (mutable, no tag) sh tool/build.sh --release alpha|beta # cut channel build; tag <tool>-vX.Y.Z-{alpha,beta}.N and upload to Codeberg
./freshen-channel <tool> <channel> # rebuild alpha/beta from current stable tag (run after every stable release) ./freshen-channel <tool> <channel> # rebuild alpha/beta from current stable tag (run after every stable release)
npm test # all Playwright specs (build first!) npm test # all Playwright specs (build first!)
npx playwright test <tool> # one spec npx playwright test <tool> # one spec
@ -35,7 +35,6 @@ npx playwright test <tool> # one spec
# zddc/ Go server (separate sub-project, not part of sh build.sh) # zddc/ Go server (separate sub-project, not part of sh build.sh)
(cd zddc && go test ./...) # unit tests (Go 1.24+) (cd zddc && go test ./...) # unit tests (Go 1.24+)
podman build -t zddc-server zddc/ # build container image
sh zddc/release.sh [alpha|beta|stable] [<version>] # cut + publish zddc-server binaries to Codeberg release assets (default: alpha; auto-derives version) sh zddc/release.sh [alpha|beta|stable] [<version>] # cut + publish zddc-server binaries to Codeberg release assets (default: alpha; auto-derives version)
``` ```
@ -43,10 +42,11 @@ No lint/typecheck/format commands exist for the HTML tools — vanilla JS + POSI
## Things that bite if you forget ## Things that bite if you forget
- **`dist/` is gitignored but force-committed** (`git add -f tool/dist/tool.html`). Never hand-edit a `dist/` file. - **`dist/` is gitignored.** `tool/dist/<tool>.html` is the canonical built artifact for testing and as the source for `--release` uploads. Never hand-edit a `dist/` file.
- **Never write to `website/index.html`, `website/releases/<tool>_v*.html`, `website/releases/<tool>_stable.html`, or `website/releases/<tool>_beta.html` directly** — promote via `sh tool/build.sh --release [version|alpha|beta]`. Stable releases write `website/releases/<tool>_v<ver>.html` (immutable) and refresh `<tool>_stable.html`; alpha/beta overwrite `<tool>_<channel>.html` in place. - **Codeberg is the canonical store for release assets.** `--release` is the only path to publishing — it tags the commit and uploads the dist HTML (or zddc-server binaries) to Codeberg via `shared/publish-codeberg-release.sh`. Nothing under `website/releases/` other than `index.html` and `manifest.json` is checked in; the `*.html` per-version files and zddc-server binaries are gitignored. Don't reintroduce them.
- **Pre-release semver for alpha/beta.** On-page label embeds `vX.Y.Z-{alpha,beta}` where X.Y.Z is the next-stable target (patch+1 from latest clean `<tool>-vX.Y.Z` tag). zddc-server image tags additionally carry a `.N` counter (`v0.0.8-alpha.1`, `.2`, …) since every release is tagged; HTML tools omit the counter (alpha/beta cuts aren't tagged). Stable tags are clean `vX.Y.Z`. - **`$CODEBERG_TOKEN` must be exported** before any `--release` invocation. Without it, `publish_codeberg_release` aborts with a clear error.
- **Alpha exception — every plain build dirties `website/releases/<tool>_alpha.html`.** Every `tool/build.sh` (no flags) mirrors the just-built dist file into `<tool>_alpha.html` as a real copy (not symlink — the canonical Caddy serves only `website/` and can't follow `../` paths). Side-effect: dev builds dirty those files; commit alongside the source change or `git checkout` to discard. - **Pre-release semver for alpha/beta.** On-page label embeds `vX.Y.Z-{alpha,beta}` where X.Y.Z is the next-stable target (patch+1 from latest clean `<tool>-vX.Y.Z` tag). Every alpha/beta cut is tagged with a `.N` counter (`<tool>-v0.0.3-alpha.1`, `.2`, …) that resets when the next stable is cut. Stable tags are clean `<tool>-vX.Y.Z`.
- **Plain `sh tool/build.sh` is a dev build.** The on-page label says `vX.Y.Z-alpha · <full-ts> · <sha>[-dirty]` (full timestamp + dirty marker distinguish iterative dev work from a formal `--release alpha` cut), but no Codeberg upload happens. To publish, re-run with `--release alpha`.
- **Always build before running tests** — Playwright opens `dist/tool.html` via `file://`. - **Always build before running tests** — Playwright opens `dist/tool.html` via `file://`.
- **`</` in JS string/template literals breaks inline `<script>`** embedding. `shared/build-lib.sh` provides `escape_js_close_tags`; every tool's `build.sh` runs JS through it before inlining. - **`</` in JS string/template literals breaks inline `<script>`** embedding. `shared/build-lib.sh` provides `escape_js_close_tags`; every tool's `build.sh` runs JS through it before inlining.
- **All ZDDC parsing/formatting/hashing goes through `window.zddc`** (from `shared/zddc.js` + `shared/hash.js` + `shared/zddc-filter.js`). API: `parseFilename`, `parseFolder`, `parseRevision`, `formatFilename`, `formatFolder`, `compareRevisions`, `isValidStatus`, `splitExtension`, `joinExtension`, `crypto.{sha256Hex, sha256String, sha256File, bytesToHex}`, `filter.{parse, matches}`. File objects across tools use `trackingNumber` (string) and `extension` (string, **no leading dot** — use `zddc.joinExtension(name, ext)` to build a filename). Add edge cases to `tests/zddc.spec.js`, not per-tool tests. - **All ZDDC parsing/formatting/hashing goes through `window.zddc`** (from `shared/zddc.js` + `shared/hash.js` + `shared/zddc-filter.js`). API: `parseFilename`, `parseFolder`, `parseRevision`, `formatFilename`, `formatFolder`, `compareRevisions`, `isValidStatus`, `splitExtension`, `joinExtension`, `crypto.{sha256Hex, sha256String, sha256File, bytesToHex}`, `filter.{parse, matches}`. File objects across tools use `trackingNumber` (string) and `extension` (string, **no leading dot** — use `zddc.joinExtension(name, ext)` to build a filename). Add edge cases to `tests/zddc.spec.js`, not per-tool tests.

View file

@ -12,12 +12,12 @@ The name "Zero Day Document Control" comes from the convention itself — adopt
| Tool | What it does | | Tool | What it does |
|------|--------------| |------|--------------|
| **[Archive Browser](https://zddc.varasys.io/releases/archive_stable.html)** | Browse, search, and filter a project archive folder. Group by transmittal, export selections as ZIP. | | **Archive Browser** | Browse, search, and filter a project archive folder. Group by transmittal, export selections as ZIP. |
| **[Transmittal Creator](https://zddc.varasys.io/releases/transmittal_stable.html)** | Self-contained HTML transmittal records with SHA-256 checksums and optional digital signatures. | | **Transmittal Creator** | Self-contained HTML transmittal records with SHA-256 checksums and optional digital signatures. |
| **[Document Classifier](https://zddc.varasys.io/releases/classifier_stable.html)** | Spreadsheet-like bulk-renamer that copy/pastes with Excel and writes back to disk. | | **Document Classifier** | Spreadsheet-like bulk-renamer that copy/pastes with Excel and writes back to disk. |
| **[Markdown Editor](https://zddc.varasys.io/releases/mdedit_stable.html)** | Browser-based markdown editor with YAML front matter, TOC, and direct local file access. | | **Markdown Editor** | Browser-based markdown editor with YAML front matter, TOC, and direct local file access. |
Each tool is published in three channels (stable, beta, alpha) at `https://zddc.varasys.io/releases/<tool>_<channel>.html`. Append `?v=alpha` (or `?v=0.0.4`, etc.) to any URL to switch versions for one request. See [`bootstrap/README.md`](bootstrap/README.md) for the install / pin / audit story. Each tool is published in three channels (stable, beta, alpha) as Codeberg release assets, browsable at <https://zddc.varasys.io/releases/>. Append `?v=alpha` (or `?v=0.0.4`, etc.) to any deployment URL to switch versions for one request. See [`bootstrap/README.md`](bootstrap/README.md) for the install / pin / audit story.
## File-naming convention ## File-naming convention

View file

@ -46,8 +46,12 @@ A typical `zddc-server` deployment looks like this:
- **At deployment root** (`<ZDDC_ROOT>/<tool>.html`), put either: - **At deployment root** (`<ZDDC_ROOT>/<tool>.html`), put either:
- the actual built tool HTML — fully self-contained install, no external - the actual built tool HTML — fully self-contained install, no external
dependencies; or dependencies; or
- a level-2 bootstrap — fetches a specific channel or pinned version from - a level-2 bootstrap — resolves the desired channel against
`https://zddc.varasys.io/releases/<tool>_<channel|v…>.html`. `https://zddc.varasys.io/releases/manifest.json` (or skips that step for an
explicit `?v=X.Y.Z` pin) and fetches the asset from
`https://zddc.varasys.io/releases/<tag>/<tool>_v<X.Y.Z>.html`. Caddy at
`zddc.varasys.io` reverse-proxies that to the corresponding Codeberg
release-asset URL.
The site administrator switches the whole site to a channel by re-running The site administrator switches the whole site to a channel by re-running
the `track-<channel>` install snippet from the home page — that overwrites the `track-<channel>` install snippet from the home page — that overwrites
@ -74,15 +78,16 @@ pin, or pass a `?v=` URL parameter for a per-request override.
### 1. Permanent pin (edit the stub) ### 1. Permanent pin (edit the stub)
Each stub has one `fallback`/`upstream` constant. Edit it once and the The level-2 stub resolves channels via `manifest.json` at runtime, so a
choice sticks for everyone using that file. "pin to current stable" is the default — no editing required. To pin
this single tool to a specific version permanently, replace the level-2
stub with one that bypasses the manifest:
| URL | Behavior | | To pin | Replace stub body with a fetch of |
|------------------------------------------------------------------|-----------------------------------------| |-----------------------------------------|-------------------------------------------------------------------------|
| `https://zddc.varasys.io/releases/<tool>_stable.html` | current stable; auto-updates within stable | | Exact stable version `vX.Y.Z` | `https://zddc.varasys.io/releases/<tool>-vX.Y.Z/<tool>_vX.Y.Z.html` |
| `https://zddc.varasys.io/releases/<tool>_beta.html` | latest beta build (mutable) | | Specific alpha/beta build | `https://zddc.varasys.io/releases/<tool>-vX.Y.Z-alpha.N/<tool>_vX.Y.Z-alpha.N.html` |
| `https://zddc.varasys.io/releases/<tool>_alpha.html` | latest alpha build (mutable) | | Channel default (current implementation)| Resolves `<tool>-<channel>` against `releases/manifest.json` at runtime |
| `https://zddc.varasys.io/releases/<tool>_v1.2.3.html` | pinned to exact stable version |
### 2. Per-request `?v=` parameter ### 2. Per-request `?v=` parameter
@ -123,10 +128,13 @@ grep -rn "fallback\|upstream" <ZDDC_ROOT>
A level-2 fetch is cross-origin (deployment → `zddc.varasys.io`). The A level-2 fetch is cross-origin (deployment → `zddc.varasys.io`). The
upstream must serve `Access-Control-Allow-Origin: *` (or a list including upstream must serve `Access-Control-Allow-Origin: *` (or a list including
your deployment origin) on the released HTML files. Verify with: your deployment origin) on `manifest.json` and on each released asset.
Verify with:
```sh ```sh
curl -I https://zddc.varasys.io/releases/archive_stable.html | grep -i access-control curl -I https://zddc.varasys.io/releases/manifest.json | grep -i access-control
curl -I https://zddc.varasys.io/releases/archive-v0.0.2/archive_v0.0.2.html \
| grep -i access-control
``` ```
Level-1 fetches are same-origin so no CORS is involved. Level-1 fetches are same-origin so no CORS is involved.

View file

@ -207,9 +207,11 @@ compute_build_label() {
_next_stable=$(_next_stable_for_tool "$_tool") _next_stable=$(_next_stable_for_tool "$_tool")
if [ "$_flag" != "--release" ]; then if [ "$_flag" != "--release" ]; then
# Plain builds mirror to website/releases/<tool>_alpha.html, so they ARE # Plain builds are dev builds — labeled as the alpha channel because
# alpha builds. Full timestamp (granular than date) and -dirty marker # that's what the next formal cut would produce, but no Codeberg upload
# distinguish iterative dev builds from formal `--release alpha` cuts. # happens until `--release alpha` is invoked. Full timestamp (granular
# than date) and -dirty marker distinguish iterative dev builds from
# formal `--release alpha` cuts (which stamp date-only).
_sha=$(git -C "$root_dir" rev-parse --short=7 HEAD 2>/dev/null || echo "unknown") _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 if ! git -C "$root_dir" diff --quiet HEAD 2>/dev/null; then
_sha="${_sha}-dirty" _sha="${_sha}-dirty"

View file

@ -11,29 +11,35 @@ A purpose-built HTTPS file server for ZDDC document archives. Designed to replac
- **Virtual `.archive` index** — resolve the earliest revision of any tracked document by URL - **Virtual `.archive` index** — resolve the earliest revision of any tracked document by URL
- **Filesystem watcher** — archive index updates automatically when files change - **Filesystem watcher** — archive index updates automatically when files change
- **Flexible TLS modes** — self-signed, real certificates, or plain HTTP - **Flexible TLS modes** — self-signed, real certificates, or plain HTTP
- **Podman-native** — multi-stage build, non-root runtime, SELinux-compatible volumes - **Single static binary** — CGO-free, no runtime dependencies; cross-compiled to Linux/macOS/Windows
## Quick Start ## Quick Start
```sh zddc-server ships as a cross-compiled binary distributed via Codeberg release assets. The website at `zddc.varasys.io` reverse-proxies download URLs to Codeberg.
# Build the container image
podman build -t zddc-server .
# Run against your archive root
podman run --rm \
-v /srv/archive:/data:z \
-e ZDDC_ROOT=/data \
-p 8443:8443 \
zddc-server
```
Or with podman-compose:
```sh ```sh
ZDDC_DATA_DIR=/srv/archive podman-compose up --build # Download the latest stable binary for your platform from
# https://zddc.varasys.io/releases/ (page lists all platforms + channels)
curl -L -o zddc-server \
https://zddc.varasys.io/releases/zddc-server-vX.Y.Z/zddc-server-linux-amd64
chmod +x zddc-server
# Run against your archive root (HTTPS on :8443 with an in-memory self-signed cert)
ZDDC_ROOT=/srv/archive ./zddc-server
``` ```
> **Docker users:** Replace `podman` with `docker` and `podman-compose` with `docker-compose`. Remove the `:z` volume suffix (that is a SELinux/podman convention). Or build from source (requires Go 1.24+):
```sh
git clone https://codeberg.org/VARASYS/ZDDC.git
cd ZDDC/zddc
go build -o zddc-server ./cmd/zddc-server
ZDDC_ROOT=/srv/archive ./zddc-server
```
For plain HTTP behind a reverse proxy, set `ZDDC_TLS_CERT=none` and `ZDDC_INSECURE_DIRECT=1` — see "TLS" below.
There is no Containerfile / Dockerfile / compose file in this repo. Operators who want to run zddc-server inside a container can write a minimal Dockerfile that copies the static binary into a `scratch` or `alpine` base, or use the chart Dockerfiles in `tnd-zddc-chart` (which compile from source at build time).
## Environment Variables ## Environment Variables
@ -43,6 +49,7 @@ ZDDC_DATA_DIR=/srv/archive podman-compose up --build
| `ZDDC_ADDR` | `:8443` | Bind address (host:port) | | `ZDDC_ADDR` | `:8443` | Bind address (host:port) |
| `ZDDC_TLS_CERT` | *(empty)* | Path to PEM certificate file. `none` = plain HTTP (no TLS); empty = generate self-signed | | `ZDDC_TLS_CERT` | *(empty)* | Path to PEM certificate file. `none` = plain HTTP (no TLS); empty = generate self-signed |
| `ZDDC_TLS_KEY` | *(empty)* | Path to PEM private key file. Required when `ZDDC_TLS_CERT` is a file path; ignored otherwise | | `ZDDC_TLS_KEY` | *(empty)* | Path to PEM private key file. Required when `ZDDC_TLS_CERT` is a file path; ignored otherwise |
| `ZDDC_INSECURE_DIRECT` | *(empty)* | Must be `1` when `ZDDC_TLS_CERT=none` and the bind address is non-loopback. Acknowledges that an authenticating reverse proxy is in front of zddc-server; without it, plain-HTTP non-loopback startup is refused |
| `ZDDC_LOG_LEVEL` | `info` | Log level: `debug`, `info`, `warn`, `error` | | `ZDDC_LOG_LEVEL` | `info` | Log level: `debug`, `info`, `warn`, `error` |
| `ZDDC_INDEX_PATH` | `.archive` | URL path segment name for the virtual archive index | | `ZDDC_INDEX_PATH` | `.archive` | URL path segment name for the virtual archive index |
| `ZDDC_EMAIL_HEADER` | `X-Auth-Request-Email` | HTTP request header containing the authenticated user's email (the oauth2-proxy / nginx auth-request convention) | | `ZDDC_EMAIL_HEADER` | `X-Auth-Request-Email` | HTTP request header containing the authenticated user's email (the oauth2-proxy / nginx auth-request convention) |
@ -73,16 +80,15 @@ middleware echoes the matched origin back per-request and sets
Set `ZDDC_TLS_CERT=none` to run without TLS. Recommended when an upstream reverse proxy Set `ZDDC_TLS_CERT=none` to run without TLS. Recommended when an upstream reverse proxy
(nginx, Caddy, Traefik) terminates external TLS and talks to zddc-server over plain HTTP (nginx, Caddy, Traefik) terminates external TLS and talks to zddc-server over plain HTTP
on a private network: on a private network. zddc-server requires `ZDDC_INSECURE_DIRECT=1` for any non-loopback
bind in this mode — an explicit acknowledgement that an authenticating proxy sits in front:
```sh ```sh
podman run --rm \ ZDDC_ROOT=/srv/archive \
-v /srv/archive:/data:z \ ZDDC_TLS_CERT=none \
-e ZDDC_ROOT=/data \ ZDDC_ADDR=:8080 \
-e ZDDC_TLS_CERT=none \ ZDDC_INSECURE_DIRECT=1 \
-e ZDDC_ADDR=:8080 \ ./zddc-server
-p 8080:8080 \
zddc-server
``` ```
When `ZDDC_TLS_CERT` / `ZDDC_TLS_KEY` are empty (or when using real certificates), zddc-server generates an ECDSA P-256 When `ZDDC_TLS_CERT` / `ZDDC_TLS_KEY` are empty (or when using real certificates), zddc-server generates an ECDSA P-256
@ -93,11 +99,10 @@ and uses this server only for encrypted in-datacenter transport.
To use a real certificate (e.g. from Let's Encrypt or an internal CA): To use a real certificate (e.g. from Let's Encrypt or an internal CA):
```sh ```sh
podman run --rm \ ZDDC_ROOT=/srv/archive \
-v /etc/ssl/zddc:/certs:z,ro \ ZDDC_TLS_CERT=/etc/ssl/zddc/server.crt \
-e ZDDC_TLS_CERT=/certs/server.crt \ ZDDC_TLS_KEY=/etc/ssl/zddc/server.key \
-e ZDDC_TLS_KEY=/certs/server.key \ ./zddc-server
...
``` ```
## Authentication ## Authentication
@ -361,47 +366,32 @@ the actual built tool fetched by the self-contained install snippet, or a
level-1/level-2 bootstrap stub that fetches it. Then open it via the zddc-server URL; level-1/level-2 bootstrap stub that fetches it. Then open it via the zddc-server URL;
the app will auto-connect and scan the directory tree. the app will auto-connect and scan the directory tree.
## Container image ## Distribution
The runtime image is published to Codeberg's container registry via a Each release is a Codeberg git tag (`zddc-server-vX.Y.Z` for stable, `zddc-server-vX.Y.Z-{alpha,beta}.N` for pre-releases) with four pre-built binaries attached as release assets:
local-build-and-push script (`release-image.sh` at the repo root). Each
release publishes cascading channel tags: | File | Platform |
|---|---|
| `zddc-server-linux-amd64` | Linux (x86-64) |
| `zddc-server-darwin-amd64` | macOS (Intel) |
| `zddc-server-darwin-arm64` | macOS (Apple Silicon) |
| `zddc-server-windows-amd64.exe` | Windows (x86-64) |
All binaries are statically linked (CGO disabled), built with `-trimpath -ldflags="-s -w -X main.version=<ver>"`. No runtime dependencies.
Download URLs go through the website's Caddy proxy:
``` ```
codeberg.org/varasys/zddc-server:X.Y.Z # immutable, exact version https://zddc.varasys.io/releases/zddc-server-vX.Y.Z/zddc-server-linux-amd64
codeberg.org/varasys/zddc-server:stable # current stable
codeberg.org/varasys/zddc-server:beta # tracks stable-or-newer beta
codeberg.org/varasys/zddc-server:alpha # tracks beta-or-newer alpha
``` ```
`:latest` is intentionally not published — the project uses (Caddy reverse-proxies that to the Codeberg release-asset URL — operators only ever talk to `zddc.varasys.io`.) Browse all versions at <https://zddc.varasys.io/releases/>.
stable/beta/alpha channel terminology consistently.
Pull and run: There is no container image. The chart Dockerfiles in `tnd-zddc-chart` compile zddc-server from source at build time, fetching the right tag from Codeberg directly. If you want your own image, copy the static binary into a `FROM scratch` or `FROM alpine` base in a few lines.
```sh
podman run --rm -p 8443:8443 \
-v /srv/archive:/srv:Z \
-e ZDDC_TLS_CERT=none \
-e ZDDC_ADDR=:8080 \
-e ZDDC_INSECURE_DIRECT=1 \
codeberg.org/varasys/zddc-server:stable
```
The image:
- alpine-based, runs as non-root (UID 1000)
- exposes 8443 by default
- defaults `ZDDC_ROOT=/srv` (override or mount your archive there)
- bundles the landing + archive tool HTML at `/opt/zddc-server/web` for
self-contained demos (`ZDDC_ROOT=/opt/zddc-server/web`)
- declares `VOLUME /srv` so the operator's data mount is explicit
- ships a `HEALTHCHECK` for `docker run`; Kubernetes deployments override it
### Env-var contract (for chart consumers) ### Env-var contract (for chart consumers)
Downstream Helm charts and Compose files should set these explicitly rather Downstream Helm charts and Compose files should set these explicitly:
than relying on image defaults:
| Variable | Typical value (behind ingress + SSO) | Purpose | | Variable | Typical value (behind ingress + SSO) | Purpose |
|---|---|---| |---|---|---|
@ -414,82 +404,74 @@ than relying on image defaults:
See "Environment Variables" above for the full list. See "Environment Variables" above for the full list.
## Building ## Building from source
### Run as a container (build locally) Requires Go 1.24+.
```sh ```sh
podman build --target server -t zddc-server . # Single binary for the host platform
(cd zddc && go build -o zddc-server ./cmd/zddc-server)
# All four release platforms (cross-compiled, statically linked)
sh build.sh # at the repo root — silently skips if Go isn't on PATH
# → outputs to zddc/dist/zddc-server-{linux,darwin,windows}-*
``` ```
### Compile native binaries (no Go installation required) To run unit tests:
Use the `binaries` build target to cross-compile for all platforms using podman
as the build environment. Binaries are extracted directly to a local `dist/`
directory — no container runs on your host.
```sh ```sh
# From the zddc/ directory (cd zddc && go test ./...)
mkdir -p dist
podman build --target binaries -o dist/ .
``` ```
This produces: ## Release tagging
| File | Platform | `sh zddc/release.sh` is the canonical path. It tags the commit, cross-compiles the four binaries (native Go), and uploads them as Codeberg release assets via the shared `publish-codeberg-release.sh` helper.
|---|---|
| `dist/zddc-server-linux-amd64` | Linux (x86-64) |
| `dist/zddc-server-darwin-amd64` | macOS (Intel) |
| `dist/zddc-server-darwin-arm64` | macOS (Apple Silicon) |
| `dist/zddc-server-windows-amd64.exe` | Windows (x86-64) |
All binaries are statically linked (CGO disabled) — no runtime dependencies.
Run the binary directly:
```sh ```sh
# Linux / macOS sh zddc/release.sh # alpha cut, version auto-derived (default)
ZDDC_ROOT=/srv/archive ZDDC_TLS_CERT=none ZDDC_ADDR=:8080 ./dist/zddc-server-linux-amd64 sh zddc/release.sh alpha # same
sh zddc/release.sh beta # beta cut
# Windows (PowerShell) sh zddc/release.sh stable # stable cut, patch++ from latest stable
$env:ZDDC_ROOT="C:\archive"; $env:ZDDC_TLS_CERT="none"; $env:ZDDC_ADDR=":8080" sh zddc/release.sh stable 0.1.0 # stable cut, explicit version
.\dist\zddc-server-windows-amd64.exe
``` ```
> **Docker users:** Replace `podman build` with `docker build`. The `--target` and **Default channel is `alpha`** so a stable-equivalent tag never appears by accident during active development. Pass `beta` to soak; pass `stable` only when deliberately promoting. The script tags the commit but does NOT push — finish with `git push origin <branch>` and `git push origin <tag>`.
> `-o` flags work identically in Docker BuildKit (enabled by default in Docker 23+).
## Release Tagging Prerequisites:
Follow the repository convention: `zddc-server-vX.Y.Z`. Two coordinated - Go 1.24+ on PATH.
steps — git tag for auditability, then local image build and push: - `$CODEBERG_TOKEN` exported, scoped to write the VARASYS/ZDDC repo. Generate one at <https://codeberg.org/user/settings/applications>.
After the script returns successfully, regenerate the website's versions index from the new release list:
```sh ```sh
# 1. Tag the source. sh build.sh
git tag zddc-server-v0.0.4 git add website/releases/index.html website/releases/manifest.json
git push origin zddc-server-v0.0.4 git commit -m "release: zddc-server vX.Y.Z"
git push origin main
# 2. Build and push the runtime image. Default cascade is alpha-only, git push origin zddc-server-vX.Y.Z
# so `:stable` doesn't advance unless you deliberately promote.
sh release-image.sh 0.0.4 # → :0.0.4 :alpha (default)
sh release-image.sh 0.0.4 beta # → :0.0.4 :beta :alpha
sh release-image.sh 0.0.4 stable # → :0.0.4 :stable :beta :alpha
``` ```
Prerequisite: `podman login codeberg.org` (or `docker login`) once, Single-developer / solo-release flow by design — no CI babysitting, no separate dashboard to debug. The script fails loudly and visibly on the developer's terminal if anything goes wrong.
authenticating with your Codeberg username and a personal token with
`package:write` scope, generated at
<https://codeberg.org/user/settings/applications>. The token is your
local credential — it never lives in the repo.
Single-developer / solo-release flow by design — no CI babysitting, ### Versioning
no separate dashboard to debug when the registry doesn't show what
you expected. See `release-image.sh` for what each tag publishes. Pre-release semver. Stable cuts get clean `vX.Y.Z` tags. Alpha and beta cuts get `vX.Y.Z-alpha.N` / `vX.Y.Z-beta.N` where `X.Y.Z` is the next patch of the latest clean stable and `N` is a per-channel counter that resets when stable advances.
```
alpha → v0.0.8-alpha.1
alpha → v0.0.8-alpha.2
beta → v0.0.8-beta.1
alpha → v0.0.8-alpha.3 (alpha and beta count separately)
stable → v0.0.8 (counter resets at next-patch advance)
alpha → v0.0.9-alpha.1
```
Pre-release semver ordering (`0.0.8-alpha.1 < 0.0.8-alpha.2 < 0.0.8-beta.1 < 0.0.8`) is honored by all standard tooling Codeberg release sorting, `git tag --sort=-v:refname`, `sort -V`, npm, cargo so consumers can pin or compare versions without surprises.
--- ---
**Notes:** **Notes:**
- The container uses a multi-stage build
- The `.archive` virtual path resolves ZDDC tracking numbers to their earliest-received revision - The `.archive` virtual path resolves ZDDC tracking numbers to their earliest-received revision
- ACL is enforced via bottom-up `.zddc` file evaluation - ACL is enforced via bottom-up `.zddc` file evaluation