Files
zeekayandHanzo Dev a02b3156cf A site gets analytics by being served
Every HTML document this server sends now goes out carrying the Hanzo tag,
stamped with the site's publishable key. A site gets analytics by being served
rather than by remembering to paste something, and one rebuilt from a template
cannot lose it.

The key lives in the deployment, not in the bundle. Baking it at build time is
what let the hanzo.ai apex serve a copy carrying no key at all, and www 0.1.15
serve one whose project had been deleted: in both cases a rebuild was the only
way to fix a value that was never part of the build.

Only text/html under 1 MB with a </head> is touched, and the bytes are returned
unchanged on every other path — no key, an unquotable one, no head, an asset, a
file that cannot be read. The file on the backend is never modified; the tag
exists only in the response.

admit adds api.hanzo.ai to the script-src and connect-src of the policy the
response is already carrying, and only when a tag actually went in. The server
that puts the script in the page is the only thing that knows it is there, so it
is the only thing that can say so — and without it the tag is inert on 22 of the
23 sites this image serves, whose policies name api.hanzo.ai in connect-src for
/v1/event but not in script-src.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 16:54:07 -07:00

2.3 KiB

static

Static file server for Hanzo Ingress (Traefik v3) and standalone SPA runtime.

Two uses

  1. Yaegi plugin embedded in hanzoai/ingress (statiq.go) — serves files at the edge.
  2. Standalone binary (cmd/static, FROM scratch image) — base image for SPAs. Serves /public and templates /public/config.json from SPA_* env vars on startup.

Runtime config (standalone binary)

The entrypoint writes config.json before serving. Env vars prefixed SPA_ become camelCase keys:

Env var Key in config.json Type
SPA_ENV env string
SPA_API_HOST apiHost string
SPA_IAM_HOST iamHost string
SPA_RPC_HOST rpcHost string
SPA_ID_HOST idHost string
SPA_CHAIN_ID chainId number (auto-detect all-digits)
SPA_FEATURE_* feature* bool (auto-detect true/false) or string

If no SPA_* vars are set the placeholder config.json in the image is left untouched — the SPA falls back to its own defaults.

v (schema version) is always 1. SPAs read /config.json at boot and validate.

Why at startup: deterministic ordering, one shot, no sidecar, no shell, no race with the HTTP server. Works in FROM scratch because the writer is Go.

The tag

Every HTML document the server sends carries the Hanzo tag, stamped with the site's publishable key (key in the plugin config; --key / HANZO_KEY for the standalone binary). No key, no tag. tag.go.

The key lives in the DEPLOYMENT, not in the bundle. Baking it at build time is what let the hanzo.ai apex serve a copy carrying no key at all, and www 0.1.15 serve one whose project had been deleted — a rebuild was the only way to fix either.

Fail-safe by construction: only text/html under 1 MB with a </head> is touched, the bytes are returned unchanged on every other path, a key that is not pk- or is not attribute-safe injects nothing, and the file on the backend is never modified.

admit adds https://api.hanzo.ai to the script-src/connect-src of the policy the response is already carrying, and only when a tag went in. Without it the tag is inert on 22 of the 23 sites this image serves: their CSPs name api.hanzo.ai in connect-src for /v1/event but not in script-src, so the script never loads.

Tests

go test ./cmd/static