Adds tini to the runtime image and routes ENTRYPOINT through it so
zddc-server runs as PID 2 with proper orphan reaping and signal
forwarding. Today zddc-server is a single-process server and the change
is invisible; the motivation is the upcoming render path that will
shell out to pandoc (which itself shells out to xelatex / lua filters /
dot) — any grandchild orphaned by a mid-run crash gets reparented to
PID 1, and a Go server is not the right thing to put in charge of
reaping subprocesses it never spawned.
tini is ~24KB and does exactly this one job. Putting it in the upstream
image (rather than each downstream consumer's Dockerfile) means every
deployment of codeberg.org/varasys/zddc-server gets the fix for free,
including the Burns & McDonnell prod chart wrapper that's about to land.
Cut a new release with `sh release-image.sh <version> stable` to
publish.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The .archive virtual directory now emits both <tracking>.html (highest
base rev) and <tracking>_<rev>.html (each specific base rev) so HTML
documents can deep-link to a known revision and have it resolve to the
first chronologically received copy. Modifier files (<rev>+C1 etc.) stay
reachable via the resolver but aren't surfaced in the listing.
.archive at any folder depth serves the same global index — the depth
exists so offline HTML can use ../.archive/<tracking>.html and let the
browser resolve it before the request reaches the server. The earlier
attempt at scoping listings to the contextPath subtree was wrong; gating
is purely by ACL: contextPath gates the listing endpoint, and each
entry's resolved file gets its own per-target ACL check (404 on denial,
not 403, so cross-subtree existence isn't disclosed).
Adds the first tests for the previously untested archive package, plus
end-to-end ACL coverage for the handler (cascade direction, default-deny
once any .zddc exists, anonymous denied under allow:[\"*@…\"], stable
Location across contextPaths).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- release-image.sh now defaults to alpha (was stable). Active dev no
longer silently advances :stable; that tag only moves on a deliberate
`sh release-image.sh <ver> stable`. Same cascade logic, reordered
default. Updated AGENTS.md and zddc/README.md sections accordingly.
- zddc/Containerfile: dropped the "see .woodpecker.yml" comment since
that file no longer exists; pointed the docs to release-image.sh.
- build.sh: dropped the "CI builds the runtime container directly"
parenthetical; the cross-compiled host-binaries build is the only
thing that step actually produces.
Why alpha as the default: caught it during active development —
:stable kept advancing every release because the script defaulted
there. Solo workflow + alpha default = `:stable` is a deliberate
gesture, not a side-effect.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes .woodpecker.yml and replaces the tag-triggered image publish flow
with a local-build-and-push script (release-image.sh).
Why: the CI added two indirections (Woodpecker dashboard, Codeberg secrets
config) that aren't worth the cost for a single-developer release flow.
When the previous release didn't show up in the package registry, "did
the release happen?" required checking three places (the git tag, the
CI dashboard, the registry); with local builds, success or failure is
visible in the developer's terminal immediately.
The cascade behavior is preserved: `sh release-image.sh 0.0.3` publishes
:0.0.3 :stable :beta :alpha just like the .woodpecker.yml job did. Beta
and alpha channels work identically (`sh release-image.sh 0.0.3-beta.1
beta` → :0.0.3-beta.1 :beta :alpha).
The git-tag convention stays (`zddc-server-vX.Y.Z`); now you tag *and*
run the script as two coordinated steps. AGENTS.md "Release tagging" and
zddc/README.md "Release Tagging" / "Container image" updated to reflect
the new flow. No code change in the binary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ACLMiddleware now slog.Debug's the configured email-header name, the
observed value at that name, and the full r.Header map on every request.
Off at the default INFO log level; enable per-pod with ZDDC_LOG_LEVEL=debug.
Motivated by debugging the X-Auth-Request-Email passthrough chain — when
access logs show email=anonymous, /.admin/whoami is unreachable (the
admin gate requires a non-empty email, which is the chicken-and-egg).
The debug log line dumps headers without the gate, so an operator can
identify whichever header name the upstream proxy is actually setting
(X-Forwarded-User, X-Forwarded-Email, Remote-User, X-Authentik-Email,
etc.) and adjust ZDDC_EMAIL_HEADER accordingly.
The debug-level dump captures auth tokens and cookies along with
everything else; safe in dev clusters, not appropriate for production
unless the operator is comfortable with the trade-off. README documents
the trade-off in the Admin Debug Page section.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Block style (one '- entry' per line) is recommended for hand-edited config
in this repo: cleaner diffs, easier to comment per-entry, no surprise YAML
quoting traps. The Admin Debug Page example mixed admins (block) with
acl.allow (flow); flip allow to block too for consistency.
Inline-in-table flow-style examples (lines 143-145) stay flow — block
style would mangle the cell layout — and that's a fine exception when the
list lives inside a one-cell context.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Listings now filter both '.' and '_' prefixes:
- '.' entries: excluded from listings AND 404 on direct HTTP access
(existing behavior). For invisible side-state like .devshell.
- '_' entries: excluded from listings only — direct URL access still
works. For operator scaffolding like install.zip's _template/
directory of bootstrap stubs that should be reachable but should
not appear in the project picker.
Filter applied at both listing entry points: ServeProjectList (the
project picker JSON at GET / Accept: application/json) and the generic
listing/FromDirEntries (used by ServeDirectory for sub-directory
browse listings).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three improvements bundled because they all ship as zddc-server v0.0.2:
* /.admin/ debug dashboard with /whoami, /config, /logs sub-routes.
Authorization via a top-level `admins:` glob list in <ZDDC_ROOT>/.zddc
(root-only — subdir entries deliberately ignored to prevent privilege
escalation via subtree write access). Non-admin requests get 404 so the
page is invisible. Recent logs surface via a 500-entry slog ring buffer
teed off the existing TextHandler. Lets operators debug without
kubectl exec.
* Default ZDDC_EMAIL_HEADER changes from `X-Email` to
`X-Auth-Request-Email` — the oauth2-proxy / nginx auth-request
convention that the TND helm chart already sets explicitly.
Operators who set the env var explicitly are unaffected; deployments
relying on the previous default need to set ZDDC_EMAIL_HEADER=X-Email
or update their proxy.
* dispatch() rejects any URL whose segments contain a dot prefix other
than the recognized virtual prefixes (.admin, cfg.IndexPath /
.archive). Matches the existing listing-pipeline filter so hidden
subtrees on the served PVC (e.g. /srv/.devshell — used by the
in-cluster dev-shell for persistent home-dir state) become
unreachable via direct HTTP fetch, not just hidden in listings.
Refreshes the X-Email reference in website/index.html accordingly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The renamed channel naming (latest → stable) only landed in the
filesystem layout and the bootstrap; the image-publish pipeline still
applied :latest as an alias for :stable on stable releases. Drop it
to avoid mixed terminology.
.woodpecker.yml: stable releases now apply only :X.Y.Z, :stable,
:beta, :alpha. zddc/README.md updated to show stable/beta/alpha
channel tags and explicitly note :latest is not published.
To clean up the existing :latest tag on the registry (one-time):
curl -X DELETE \
-H "Authorization: token $CODEBERG_TOKEN" \
https://codeberg.org/api/v1/packages/varasys/container/zddc-server/latest
or via the web UI at codeberg.org/VARASYS/-/packages/container/zddc-server.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 'latest' label for the current-stable channel was inconsistent
with the channel set we use elsewhere (alpha / beta / stable). Rename
to 'stable' so URLs, file names, zip names, and image tags all line
up with the channel terminology used in the bootstrap, AGENTS.md
discipline rules, and chart consumers.
File / artifact renames
- website/releases/<tool>_latest.html → <tool>_stable.html (5 files)
- website/track-latest.zip → track-stable.zip
- shared/build-lib.sh: promote_release writes/refreshes _stable.html
- bootstrap/level{1,2}.html.tmpl: channels map drops 'latest', keeps
'stable' as the canonical name. ?v=stable is now the explicit way
to switch to current-stable for one request (alongside ?v=alpha,
?v=beta, and ?v=X.Y.Z).
- build.sh: install.zip sources from <tool>_stable.html; emits
track-stable.zip instead of track-latest.zip.
Container image (.woodpecker.yml rewritten)
- Tag publishing now cascades:
zddc-server-vX.Y.Z → :X.Y.Z, :stable, :beta, :alpha, :latest
zddc-server-vX.Y.Z-beta.N → :X.Y.Z-beta.N, :beta, :alpha
zddc-server-vX.Y.Z-alpha.N → :X.Y.Z-alpha.N, :alpha
- :stable, :beta, :alpha are now first-class channel pointers; chart
consumers (e.g. tnd-zddc-chart) can FROM :beta for dev and FROM
:stable for prod.
- :latest kept as an alias for :stable per Docker convention.
Documentation sweep
- AGENTS.md, ARCHITECTURE.md, CLAUDE.md, README.md
- bootstrap/README.md, zddc/README.md
- website/index.html, website/zddc-server.html
- transmittal/template.html, transmittal/README.md
all updated to reference _stable.html / track-stable.zip / the
'stable' channel name. ARCHITECTURE.md's manual freshen example
points at ./freshen-channel instead of the old git-checkout snippet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Batch 1 of the chart-vs-project split. The project now ships a
hardened runtime image as part of every zddc-server release; downstream
deployments (e.g. the Burns & McDonnell Helm chart) will FROM this
image instead of cloning and building from source.
zddc/Containerfile (target: server)
- Tag the runtime stage `server` so `podman build --target server`
is unambiguous (the existing `binaries` target still works).
- Bake the bundled landing + archive tool HTML at /opt/zddc-server/web.
Useful for self-contained demos (`ZDDC_ROOT=/opt/zddc-server/web`)
and as a fallback web root when no external mount is supplied.
- Set fixed UID/GID 1000 for the non-root zddc user so volume
permissions are predictable across hosts.
- Add ENV ZDDC_ROOT=/srv default so a `podman run -v data:/srv` works
with no further config; explicit ZDDC_ROOT overrides.
- Declare VOLUME /srv to make the data-mount expectation explicit.
- Add OCI image labels (title, description, source, documentation,
license, vendor).
- Install ca-certificates so any future outbound HTTPS works.
- Add a HEALTHCHECK for `docker run` users (Kubernetes overrides).
build.sh
- Make the cross-platform podman binary build conditional on `podman`
being present. CI doesn't need it (the runtime container image's
own builder stage produces linux/amd64 internally), but having
build.sh sh-only-runnable means CI doesn't have to do nested
containers just to assemble dist/web.
- Reorder so `zddc/dist/web/` is assembled before the binary build
(allows the binary build to be skipped without breaking the bundle).
.woodpecker.yml (new)
- Triggers on tag push matching `zddc-server-v*`.
- Step 1 (alpine + sh): runs `sh build.sh` to assemble dist/web,
computes the image tag (`${TAG#zddc-server-v}` plus `latest`).
- Step 2 (docker-buildx plugin): builds and publishes
codeberg.org/varasys/zddc-server:{X.Y.Z, latest}. Auth via the
codeberg_user / codeberg_token Woodpecker secrets — these need
one-time setup in repo Settings; documented in zddc/README.md.
zddc/README.md
- New "Container image" section: pull URL, image properties (alpine,
non-root UID 1000, EXPOSE 8443, VOLUME /srv, baked web bundle),
example `podman run` invocation.
- New "Env-var contract (for chart consumers)" table: the variables
Helm charts and Compose files should set explicitly when running
behind a TLS-terminating reverse proxy with SSO. This is the
documented interface between project and downstream charts.
- "Release Tagging" section now points at .woodpecker.yml and lists
the two Woodpecker secrets that must be configured.
Validated locally:
podman build --target server -t zddc-server-test .
podman run -e ZDDC_ROOT=/opt/zddc-server/web -e ZDDC_TLS_CERT=none \
-e ZDDC_INSECURE_DIRECT=1 -e ZDDC_ADDR=:8080 \
-p 18080:8080 zddc-server-test
curl http://localhost:18080/ → HTTP 200, bundled landing tool.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ZDDC — Zero Day Document Control. A file-naming convention plus five
single-file HTML tools (archive, transmittal, classifier, mdedit,
landing) and an optional Go HTTP server (zddc-server) with ACL and a
virtual archive index. Self-contained, offline-capable, dependency-free.
See README.md for an overview, AGENTS.md and ARCHITECTURE.md for the
build/release/architecture detail, bootstrap/README.md for the
two-level deployment install pattern, and zddc/README.md for the
HTTP server.