config

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 19, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func LogStorageInitError

func LogStorageInitError(logger *slog.Logger, kind, path string, err error)

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

func WarnIfFreshDataDir(logger *slog.Logger, kind, dir string)

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 Cache

type Cache struct {
	L1MaxCost              int64 `yaml:"l1_max_cost" env:"WH_CACHE_L1_MAX_COST" env-default:"67108864"`
	TimestampBucketSeconds int   `yaml:"timestamp_bucket_seconds" env:"WH_CACHE_TIMESTAMP_BUCKET_SECONDS" env-default:"60"`
}

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.

func Load

func Load(path string) (*Config, error)

Load reads config from a YAML file (if it exists) with env var overrides.

func (*Config) Validate

func (c *Config) Validate() error

Validate checks the loaded configuration for logical consistency.

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 Dedupe

type Dedupe struct {
	Enabled   bool   `yaml:"enabled" env:"WH_DEDUPE_ENABLED" env-default:"false"`
	IDField   string `yaml:"id_field" env:"WH_DEDUPE_ID_FIELD" env-default:"event_id"`
	RequireID bool   `yaml:"require_id" env:"WH_DEDUPE_REQUIRE_ID" env-default:"false"`
}

type MQ

type MQ struct {
	GapWindowMinutes int `yaml:"gap_window_minutes" env:"WH_MQ_GAP_WINDOW_MINUTES" env-default:"15"`
	MaxBytesGB       int `yaml:"max_bytes_gb" env:"WH_MQ_MAX_BYTES_GB" env-default:"50"`
}

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 OTelTraces struct {
	Enabled    bool    `yaml:"enabled" env:"WH_OTEL_TRACES_ENABLED" env-default:"true"`
	SampleRate float64 `yaml:"sample_rate" env:"WH_OTEL_TRACES_SAMPLE_RATE" env-default:"1.0"`
}

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 Server

type Server struct {
	Port               int      `yaml:"port" env:"WH_SERVER_PORT" env-default:"8080"`
	ShutdownTimeout    int      `yaml:"shutdown_timeout" env:"WH_SERVER_SHUTDOWN_TIMEOUT" env-default:"10"`
	CORSAllowedOrigins []string `yaml:"cors_allowed_origins" env:"WH_SERVER_CORS_ALLOWED_ORIGINS" env-default:"*"`
}

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"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL