Skip to content

Server Guide

Zann server provides the API, shared vaults, and token issuance for CLI access.

  • HTTP API for clients (desktop and CLI)
  • Shared vault encryption and access control
  • Service account tokens for automation
Terminal window
git clone https://github.com/constXife/zann
cd zann
docker compose up -d
Terminal window
docker pull constxife/zann-server:latest

Start from config/config.example.yaml and supply required secrets via env:

  • ZANN_PASSWORD_PEPPER
  • ZANN_TOKEN_PEPPER
  • ZANN_SMK_FILE or server.master_key
  • ZANN_CONFIG_PATH

Common env vars:

  • ZANN_CONFIG_PATH - path to the server config file
  • ZANN_ENV - environment name (prod enables stricter output in health checks)
  • ZANN_PASSWORD_PEPPER / ZANN_PASSWORD_PEPPER_FILE
  • ZANN_TOKEN_PEPPER / ZANN_TOKEN_PEPPER_FILE
  • ZANN_SMK / ZANN_SMK_FILE

Secret key files must be regular files without group or world permissions. When a file is supplied directly through systemd LoadCredential=, the server also accepts systemd’s 0440 copy, but only when it is an immediate child of the current $CREDENTIALS_DIRECTORY; the same mode is rejected elsewhere.

Run database migrations via the server CLI:

Terminal window
zann-server migrate

Create and manage tokens for CLI automation:

Terminal window
zann-server token create ci-prod infra:/
zann-server token list
zann-server token revoke <token_id>

token create takes <name> <vault:prefixes> [ops]. The prefixes are / for a whole vault or a comma-separated list of subtrees, and ops defaults to read:

Terminal window
zann-server token create deployer infra:services/web read,write
zann-server token create audit infra:services,platform read --ttl 30d

Valid ops are read, write, read_history, read_previous and rotate. The rotate op grants coordinated rotation only and never force-abort. A prefix matches a path exactly or anything beneath it, and a token holding only prefixed rules cannot list the vault as a whole.

For server-side bootstrap flows, use the privileged provisioning helpers:

Terminal window
zann-server provision ensure-system-user
zann-server provision ensure-vault --name Infrastructure --slug infra
zann-server provision set-field --vault infra --path rlyeh/yogg/grafana --key value --kind password --value-file /run/secrets/grafana-client-secret
zann-server provision retype-item --vault infra --path rlyeh/yogg/mcp --from-type-id secret --source-format typed-v1 --history-source-format typed-v1 --to-type-id kv --if-exists
zann-server provision ensure-token yogg-grafana infra:rlyeh/yogg/grafana read --write-token-file /run/secrets/yogg-zann-token

provision set-field accepts plaintext only through --value-file. The input must be a regular UTF-8 file no larger than 256 KiB; its bytes, including leading/trailing whitespace and a final newline, are preserved exactly. New items use type secret by default. For that type, only the canonical password field value is accepted and the configured default secret policy is added automatically. Pass an explicit non-secret --type-id for generic typed items.

provision retype-item is an explicit repair helper for legacy items whose stored type no longer matches the server contract. It decrypts and validates the current payload and every retained history payload in memory, rewrites only their type metadata, and commits the item, history, and change record in one transaction. No plaintext is printed or written to disk. Both source and target types and the versioned current and retained-history encodings are required; a mismatch fails closed. Use --source-format typed-v1 and --history-source-format typed-v1 when both generations use the canonical typed encoding. The explicit legacy-secret-v0 format is restricted to source type secret and migrates the historical strict {value, policy, meta} shape. It may be selected independently for retained history after a current payload has already been rewritten. The current payload snapshot created by the command always uses --source-format; only history that predates the command uses --history-source-format. Neither historical encoding is recognized by normal secret API reads. Use --if-exists only for idempotent bootstrap migrations where an absent legacy item is expected.

Values generated by the machine secrets API (ensure and rotation) follow a named password policy:

GET /v1/vaults/{vault}/secrets lists secret metadata with bounded cursor pagination and never decrypts or returns values. Use prefix, limit (1-100), and the opaque cursor query parameters; prefix-scoped service accounts must request a covered subtree.

secrets:
policies_file: /etc/zann/secret-policies.yaml
default_policy: "default"
/etc/zann/secret-policies.yaml
default_policy: default
policies:
default:
length: 32
min_lowercase: 1
min_uppercase: 1
min_digits: 1

Coordinated rotation windows are configured under rotation: (lock_ttl_seconds, stale_retention_seconds, cleanup_interval_seconds, max_versions). The API these settings govern is documented in the Machine Secrets guide (docs/Machine-Secrets.md).

The server exposes a health check at:

GET /health

It includes component status (db, db_pool, kdf, oidc) and version info.

  • Prefer HTTPS and pin the server fingerprint in clients.
  • Keep token scopes narrow and rotate regularly.