ZDDC/helm
ZDDC 6b973906c3 feat(server): refuse to start without root .zddc; default CORS to empty
Two safe-by-default flips, both opt-out via explicit acknowledgement.

1. --insecure / ZDDC_INSECURE=1: zddc-server now refuses to start when
   no <ZDDC_ROOT>/.zddc exists. With no .zddc anywhere in the chain,
   AllowedWithChain falls through to "HasAnyFile=false → allow" and
   the tree is publicly accessible to anonymous callers — almost never
   what an operator wants on a fresh deployment, and previously a
   silent footgun. The flag is the escape hatch for deliberately-
   public archives (no .zddc anywhere by design).

2. ZDDC_CORS_ORIGIN now defaults to empty (CORS disabled) instead of
   the canonical "https://zddc.varasys.io". The embedded-tools install
   path serves tools and data same-origin, so the default never needed
   to permit cross-origin XHRs from a third-party host. Every deployment
   was implicitly trusting zddc.varasys.io to make authenticated XHRs
   on behalf of every logged-in user; if that origin were ever
   compromised, the blast radius extended to every customer server.
   Operators who deliberately use the CDN-bootstrap pattern or self-
   hosted tools at a different host now set the value explicitly.

Helm chart values updated accordingly: prod default is empty; dev
keeps localhost:8000 for tool-iteration workflows. Existing deployments
that depended on the old defaults will need to either set the value
explicitly or pass --insecure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 17:40:34 -05:00
..
zddc-server-dev feat(server): refuse to start without root .zddc; default CORS to empty 2026-05-04 17:40:34 -05:00
zddc-server-prod feat(server): refuse to start without root .zddc; default CORS to empty 2026-05-04 17:40:34 -05:00
README.md feat: example helm charts for zddc-server (production + dev) 2026-04-30 09:48:02 -05:00

Helm charts

Two example charts for deploying zddc-server on Kubernetes. Both compile zddc-server from source via an init container — no container image needs to be pulled from a registry, and no binary needs to be built ahead of time. The init container clones the repo at a configured git ref and runs go build; the main container is plain alpine + the freshly built static binary.

Charts

Chart When to use
zddc-server-prod/ Production. Pin zddc.gitRef to a stable tag (zddc-server-vX.Y.Z). Slower probe cadence; image-pull policy IfNotPresent.
zddc-server-dev/ Development / soak. Tracks main by default; helm upgrade triggers a pod recreate so each rollout pulls the latest commit. Faster probes; debug-level logging (request headers logged — sensitive).

The chart values are nearly identical between the two; the differences are encoded as defaults in each chart's values.yaml.example.

Quick start

# Pre-requisite: a PersistentVolumeClaim for ZDDC_ROOT data
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: zddc-root
spec:
  accessModes: [ReadWriteMany]    # or RWO if single replica is fine
  resources: { requests: { storage: 100Gi } }
  storageClassName: your-shared-fs   # NFS, CephFS, SMB, etc.
EOF

# Production install
cp helm/zddc-server-prod/values.yaml.example my-prod-values.yaml
$EDITOR my-prod-values.yaml          # set zddc.gitRef, hostnames, etc.
helm install zddc-server-prod helm/zddc-server-prod/ -f my-prod-values.yaml

# Dev install (tracks main HEAD)
cp helm/zddc-server-dev/values.yaml.example my-dev-values.yaml
$EDITOR my-dev-values.yaml
helm install zddc-server-dev helm/zddc-server-dev/ -f my-dev-values.yaml

# Trigger a rebuild from latest main HEAD (dev chart)
helm upgrade zddc-server-dev helm/zddc-server-dev/ -f my-dev-values.yaml

What the chart does and doesn't do

Does:

  • Clones the configured zddc.gitRepo at zddc.gitRef in an init container, builds the Go binary, copies it to a shared emptyDir, and starts the main container against that binary.
  • Wires the ZDDC_* environment-variable contract (root path, addr, email header, CORS allowlist, log level, index path).
  • Mounts a caller-supplied PersistentVolumeClaim at ZDDC_ROOT.
  • Optionally creates an Ingress (ingress.enabled: true).

Does not:

  • Create the PVC. Operators provision storage themselves; the chart only references it by name.
  • Manage TLS for the pod. zddc-server runs in plain HTTP mode behind whatever ingress / authenticating reverse proxy the cluster already has. ZDDC_TLS_CERT=none and ZDDC_INSECURE_DIRECT=1 are hardcoded in the templates because the chart is opinionated about the TLS-terminated-upstream deployment shape.
  • Authenticate users. zddc-server reads the user's email from a header set by the upstream proxy (X-Auth-Request-Email by default). The chart does not deploy oauth2-proxy / nginx-auth-request / Pomerium / etc. — bring your own.
  • Manage secrets. values.yaml.example contains no secrets and never should. ACL email lists belong in .zddc files inside the data volume; image-pull credentials and TLS certs (if you enable ingress TLS) reference Kubernetes secrets you've created separately.

Why build from source instead of using a registry image

Three reasons:

  1. Reproducibility. The init container's logs show exactly which git ref was built. There's no opaque "what did I deploy" question that a registry tag can introduce.
  2. One distribution channel. Codeberg release-asset binaries already exist for direct downloads; the chart compiles its own binary from the same source git ref so there's nothing extra to maintain (no separate image registry, no image-promotion pipeline).
  3. Smaller blast radius. A compromised build image affects only pods that pull during the compromise window. A compromised registry image stays compromised across rollbacks until the digest is rotated.

The cost: every pod start takes 30-60s to clone + go build instead of pulling a pre-baked image. Acceptable for both chart audiences (production rollouts are infrequent; dev rollouts trade build time for tracking-main convenience).

Linting

helm lint helm/zddc-server-prod/
helm lint helm/zddc-server-dev/

# Render to inspect (uses default values from values.yaml.example):
helm template test-prod helm/zddc-server-prod/ \
  --values helm/zddc-server-prod/values.yaml.example