Documentation
¶
Index ¶
- func LogStorageInitError(logger *slog.Logger, kind, path string, err error)
- func WarnIfFreshDataDir(logger *slog.Logger, kind, dir string)
- type Auth
- type Cache
- type ClickHouse
- type Config
- type DLQ
- type Dedupe
- type MQ
- type OTel
- type OTelLogs
- type OTelMetrics
- type OTelTraces
- type Pipes
- type Policy
- type Prometheus
- type Query
- type Schema
- type Server
- type Stream
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func LogStorageInitError ¶
LogStorageInitError emits an error log for a storage-init failure, with a UID-65532 hint when the failure looks like a permission denial — the dominant signature of a Docker bind mount whose host directory is owned by root rather than the distroless `nonroot` user. Keeps the actionable remediation right next to the error line so operators don't have to dig.
`kind` is a short label (e.g. "mq", "dedupe"). The caller still decides whether to exit; this only logs.
func WarnIfFreshDataDir ¶
WarnIfFreshDataDir logs a startup `WARN` if dir doesn't already exist or is empty, signalling that we're about to start with no prior state.
On a first-ever run this is expected; on every subsequent run it should be silent. So when the warning *does* fire on a redeploy, it's the most direct possible signal that the persistent volume isn't actually persisting: either it wasn't mounted, the wrong path was mounted, or the volume was recreated.
`kind` is a short label for the log message (e.g. "nats", "pebble").
Types ¶
type Auth ¶
type Auth struct {
JWTSecret string `yaml:"jwt_secret" env:"WH_AUTH_JWT_SECRET"`
JWKSURL string `yaml:"jwks_url" env:"WH_AUTH_JWKS_URL"`
RoleClaim string `yaml:"role_claim" env:"WH_AUTH_ROLE_CLAIM" env-default:"role"`
OperatorKey string `yaml:"operator_key" env:"WH_AUTH_OPERATOR_KEY"`
}
Auth configures JWT validation. There is no on/off switch: the middleware always runs. A request with no token, or an invalid/expired one, falls back to the policy default_role; elevated access requires a valid token whose role claim matches a granted role (or the policy admin_role). With neither JWTSecret nor JWKSURL set, no token can validate, so every request is the default role — a pure public deployment.
OperatorKey is an optional non-JWT credential for the operator running the deployment: a request presenting it via an "Authorization: Operator <key>" header (or the X-Operator-Key alias) is authorized as a full-access platform operator, independent of the JWT verifier, and keeps working even if the policy is missing/deleted (break-glass recovery). Empty (the default) disables it. Treat it as an admin secret.
type ClickHouse ¶
type ClickHouse struct {
Addr string `yaml:"addr" env:"WH_CH_ADDR" env-default:"localhost:9000"`
HTTPPort string `yaml:"http_port" env:"WH_CH_HTTP_PORT" env-default:"8123"`
HTTPScheme string `yaml:"http_scheme" env:"WH_CH_HTTP_SCHEME" env-default:"http"`
Database string `yaml:"database" env:"WH_CH_DATABASE" env-default:"default"`
Username string `yaml:"username" env:"WH_CH_USERNAME" env-default:"default"`
Password string `yaml:"password" env:"WH_CH_PASSWORD"`
QueryTimeout time.Duration `yaml:"query_timeout" env:"WH_CH_QUERY_TIMEOUT" env-default:"30s"`
}
type Config ¶
type Config struct {
// DataDir is the root for embedded state. NATS JetStream lives at
// `<DataDir>/nats`; Pebble (when dedupe is enabled) at `<DataDir>/pebble`.
// Subdirectory names are conventions, not config — one knob, one mount.
// In a container this MUST resolve to a host-backed volume; the relative
// `./data` default is fine for local binary use only.
DataDir string `yaml:"data_dir" env:"WH_DATA_DIR" env-default:"./data"`
Server Server `yaml:"server"`
ClickHouse ClickHouse `yaml:"clickhouse"`
MQ MQ `yaml:"mq"`
Dedupe Dedupe `yaml:"dedupe"`
Cache Cache `yaml:"cache"`
Auth Auth `yaml:"auth"`
Schema Schema `yaml:"schema"`
DLQ DLQ `yaml:"dlq"`
Policy Policy `yaml:"policy"`
Pipes Pipes `yaml:"pipes"`
OTel OTel `yaml:"otel"`
Prometheus Prometheus `yaml:"prometheus"`
Query Query `yaml:"query"`
Stream Stream `yaml:"stream"`
}
Config is the top-level application configuration.
type DLQ ¶
type DLQ struct {
Enabled bool `yaml:"enabled" env:"WH_DLQ_ENABLED" env-default:"true"`
}
DLQ configures the Dead Letter Queue for failed batch inserts.
type OTel ¶
type OTel struct {
Enabled bool `yaml:"enabled" env:"WH_OTEL_ENABLED" env-default:"false"`
Traces OTelTraces `yaml:"traces"`
Metrics OTelMetrics `yaml:"metrics"`
Logs OTelLogs `yaml:"logs"`
}
OTel configures the OpenTelemetry pipeline. `enabled` is the master switch; when false, no signals are initialized regardless of the per-signal toggles. The OTLP destination — endpoint, TLS, custom CA, mutual TLS, and auth headers — is configured through the standard OTEL_EXPORTER_OTLP_* environment variables read by the OpenTelemetry SDK, not WaveHouse config. See docs/src/content/docs/configuration.mdx.
type OTelLogs ¶
type OTelLogs struct {
Enabled bool `yaml:"enabled" env:"WH_OTEL_LOGS_ENABLED" env-default:"true"`
SampleRate float64 `yaml:"sample_rate" env:"WH_OTEL_LOGS_SAMPLE_RATE" env-default:"1.0"`
}
OTelLogs sample rate applies to OTLP export of DEBUG/INFO only. WARN and ERROR always export at 100% — dropping them silently during incidents is too dangerous to expose as a knob. Stdout receives 100% of records regardless of this rate (sampling for scraped-log pipelines like Loki/Promtail belongs at the scraper, not the application).
type OTelMetrics ¶
type OTelMetrics struct {
Enabled bool `yaml:"enabled" env:"WH_OTEL_METRICS_ENABLED" env-default:"true"`
}
type OTelTraces ¶
type Pipes ¶
type Pipes struct {
Dir string `yaml:"dir" env:"WH_PIPES_DIR" env-default:""`
}
Pipes configures named query pipes.
Dir is an OPTIONAL bootstrap source: on startup, any `.sql` files in it are loaded into the NATS KV pipe store. After bootstrap, the API/KV is the authoritative store — the directory is read-only at runtime and not rewritten. Empty default skips bootstrap entirely (most users will create pipes via the API). When set, mount the directory read-only in containers (e.g. `./my-pipes:/app/pipes:ro`) so it's clear it's a seed, not state.
type Policy ¶
type Policy struct {
FilePath string `yaml:"file_path" env:"WH_POLICY_FILE_PATH"`
}
Policy configures the access control policy engine.
FilePath has no default on purpose. When set, the file MUST exist and parse cleanly — policy.NewStore returns a fatal error otherwise, so a typo or a missing mount surfaces as a refused boot instead of a silent fail-closed (every request 403s, including admin). When left empty, the store comes up with no cached policy and the operator seeds via PUT /v1/ops/policy.
A baked-in default like "policy.yaml" would re-introduce the silent-lockout failure mode for any deployment that didn't ship that exact file at CWD, and it would imply a convention the operator never opted into — keep the path an explicit choice.
type Prometheus ¶
type Prometheus struct {
Enabled bool `yaml:"enabled" env:"WH_PROMETHEUS_ENABLED" env-default:"false"`
Path string `yaml:"path" env:"WH_PROMETHEUS_PATH" env-default:"/metrics"`
Port int `yaml:"port" env:"WH_PROMETHEUS_PORT" env-default:"0"`
}
Prometheus controls a Prometheus exposition endpoint served alongside (or independently of) the OTLP push exporter. It is its own top-level block so operators using Prometheus scraping (Grafana Alloy / Mimir / etc.) don't have to hunt through `[otel]` for an output they don't otherwise care about. Internally the same OTel MeterProvider drives both — the Prometheus exporter is an additional Reader — but the user-facing config keeps the two outputs distinct.
Works in any of three combinations: OTel only, Prometheus only, or both. If only Prometheus is enabled, the MeterProvider is still created so runtime + custom metrics flow to `/metrics`; no OTLP push is attempted.
Port `0` mounts the endpoint on the existing API server router. A non-zero port spins up a dedicated HTTP listener — useful for firewalling metrics off the public API surface in production.
type Query ¶
type Query struct {
// DefaultMaxRows is the result LIMIT applied to a structured query when the
// caller and policy specify none — the visible, tunable form of what used to
// be the hard-coded query.DefaultMaxRows. 0 falls back to that constant.
DefaultMaxRows int `yaml:"default_max_rows" env:"WH_QUERY_DEFAULT_MAX_ROWS" env-default:"10000"`
}
Query holds query-shaping defaults. Server-wide *resource* limits (memory, rows scanned, execution time) deliberately live in ClickHouse itself — its settings profiles and quotas, see docs/configuration — so they apply uniformly to every query (including raw admin SQL) and compose with the per-role caps WaveHouse adds via per-query settings. This block holds only the result-shaping default that is genuinely WaveHouse's to own.
type Schema ¶
type Schema struct {
RefreshInterval int `yaml:"refresh_interval" env:"WH_SCHEMA_REFRESH_INTERVAL" env-default:"60"` // seconds
}
Schema configures ClickHouse schema discovery.
type Stream ¶
type Stream struct {
// KeepaliveInterval is the effective per-connection SSE keepalive period: the
// longest a quiet GET /v1/stream connection goes without a write before the
// server sends a ":" keepalive comment. Keep it under your proxy/tunnel idle
// timeout (default 30s clears the common 55–60s nginx/ALB/Heroku window).
KeepaliveInterval time.Duration `yaml:"keepalive_interval" env:"WH_STREAM_KEEPALIVE_INTERVAL" env-default:"30s"`
// KeepaliveBuckets spreads keepalive writes across the interval so the server
// nudges ~1/N of connections per tick instead of all at once. Advanced knob;
// most deployments never change it.
KeepaliveBuckets int `yaml:"keepalive_buckets" env:"WH_STREAM_KEEPALIVE_BUCKETS" env-default:"3"`
}