Configuration
Anatomy of nanook.toml · every section, what it does, what's optional.
nanook.toml is the source of truth. Everything the agent does comes from here.
Skeleton
[] # logging level + format
[] # admin server (HTTP + WS + Unix socket)
[] # where to look for dylib plugins
[] # alert delivery destinations
[[]] # one entry per metric source
[[]] # one entry per metric sink
[[]] # rules that fire on metric conditions
You don't need every section. The minimum useful config has one collector and one rule.
[log]
[]
= "info" # debug, info, warn, error
= "text" # text, json
[admin]
The admin server is what nanook ctl and nanook tui talk to. Disabled by default: set enabled = true to expose it.
[]
= true
= "127.0.0.1:9091" # HTTP/WS bind addr
= "/run/nanook.sock" # optional Unix socket
= "required" # "required" (default) or "none"
= [ # inline ssh-ed25519 lines
"ssh-ed25519 AAAAC3Nz... alice@laptop",
]
= "/etc/nanook/authorized_keys" # optional path to a key file
= "" # reserved, TLS not yet wired
= "" # reserved, TLS not yet wired
Clients sign every request with an Ed25519 SSH-style key. The server gates them against authorized (inline) or authorized_keys (file). With auth = "required" the agent refuses to start when no keys are configured; set auth = "none" to opt out on trusted networks.
TLS isn't wired yet: front the agent with a reverse proxy for encryption on the wire, or use the Unix socket on a single host. See Admin server for key generation, headers, and error codes.
[state]
[]
= "/var/lib/nanook/state.bin"
Where the agent persists in-flight state across restarts. Empty path (the default) means in-memory only: silences disappear when the process exits.
The file is postcard-encoded, not meant for hand-editing. A missing file at boot is a clean first run; a corrupt or version-incompatible file logs a warning and the agent boots empty.
Today only silences are persisted. Rule cooldowns, hit counters, and plugin state will follow on the same plumbing.
[engine]
Engine-level tuning. Today scopes only the cardinality guard.
[]
= 10000 # max distinct series per source. 0 disables.
= 2000 # max distinct series per (source, name). 0 disables.
Both caps are enforced at the ingest boundary in LatestStore::insert. Crossing either rejects the new series — existing ones keep updating. The reject is recorded on stats.dropped and exported as the self-metric nanook.engine.dropped, and a rate-limited warn line is logged once per (source, metric) per minute.
The defaults are intentionally high. Hitting them almost always means a label is unbounded, not that the cap is too tight. See nanook::engine::cardinality_exceeded.
[plugins]
[]
= ["/usr/lib/nanook", "./plugins"]
= [
"sha256:d3983dbf3dbc30aa4838bb16c38786d03af5faf2643db2a0cdab7444697ff28b",
]
= true # default true: refuse plugins whose digest isn't in `allowed`
= true # default true: refuse dirs not owned by us or world-writable
See Plugins for how plugins get discovered and validated.
[channels.<id>]
A channel is a named delivery target. Rules reference it by id.
[]
= "log"
[]
= "slack"
[]
= "${SLACK_WEBHOOK_URL}"
[]
= "exec"
[]
= "/usr/local/bin/page-oncall.sh"
Channel types: log, webhook, discord, slack, exec. See Channels for what each one accepts.
[[collectors]]
A collector is anything that produces metrics. Built-in kinds: cpu, mem, disk, net, http, tcp, dns, process, exec, load, temp, uptime. Plugin kinds are loaded from [plugins].dirs.
[[]]
= "cpu" # your handle for this collector
= "cpu" # defaults to `name` if omitted
= "5s" # how often to poll
= { = "prod" } # constant labels merged onto every sample
= "" # nanook-expr filter; samples that don't match are dropped
= "" # explicit plugin crate (rare)
[]
# kind-specific options (see `nanook doc <kind>`)
Two collectors can share a kind, give them different names.
[[]]
= "api"
= "http"
= "30s"
[]
= "https://api.example.com/healthz"
[[]]
= "homepage"
= "http"
= "60s"
[]
= "https://example.com"
Reference each one in expressions as api::http.status and homepage::http.latency. See nanook-expr.
[[adapters]]
An adapter exports metrics to an external system. It runs continuously and sees every metric the engine produces.
[[]]
= "scrape" # handle for this adapter
= "prometheus" # defaults to `name` if omitted
= "" # nanook-expr filter on metric name / labels
= "" # explicit plugin crate (rare)
[]
= 9090
= 300
Built-in adapter kinds: prometheus, statsd, dogstatsd, pulse, file, stdout, webhook. See Adapters.
[[alerts]]
[[]]
= "cpu.usage > 90%" # nanook-expr predicate (required)
= 3 # ticks the predicate must hold
= "ops" # channel id, must exist in [channels.<id>]
= "log" # log | webhook | discord | slack | exec (default log)
= "" # action-specific target (URL, command, ...)
= "" # template rendered against the alert event; see Alerts
= "5m" # min gap between fires for this rule
= { = "10m", = "exec", = "/usr/local/bin/page" }
See Alerts for everything [[alerts]] accepts and how escalation, cooldown, and silencing interact.
Environment substitution
Anywhere a string appears, ${VAR} is substituted from the process env. Unset vars are an error at config load. Use a default after a colon for optional vars:
= "${SLACK_WEBHOOK_URL}" # required, fails load if unset
= "${SLACK_WEBHOOK_URL:none}" # literal `none` if unset
= "${OPTIONAL:}" # empty string if unset
The default literal is taken verbatim, whitespace included.
To pass a literal $ (or ${VAR}) past env-subst (so a downstream layer like the shell or nanook-templates can do its own substitution), double the dollar sign:
= "$$VAR" # rewrites to `$VAR`
= "echo $${HOME}" # rewrites to `echo ${HOME}`, sh expands at exec
$$ collapses to $ once; env-subst never re-scans its own output.
File includes
Any opts string can be read from disk by prefixing it with @file:<path>. The file's contents replace the value at config-load time. Works for anything string-shaped: shell scripts, body templates, multi-line URLs, cert blobs.
[]
= "exec"
[]
= "@file:./scripts/notify.sh"
[[]]
= "cpu.usage > 90%"
= "ops"
= "@file:./templates/cpu_alert.tpl"
Paths resolve relative to the agent's working directory, not the location of nanook.toml. Use absolute paths if you care.
The file is read once at load time. Edits to the included file don't propagate until you reload the config.
Schema
nanook schema emits a JSON Schema (draft 2020-12) describing the agent config. Pipe it to a file and point your editor at it for autocomplete and inline validation.
See also
- nanook-expr · predicate language for
expr - nanook-templates · body templating
- CLI · every flag and subcommand