aboutsummaryrefslogtreecommitdiffstats
path: root/README.md
AgeCommit message (Collapse)AuthorFilesLines
2026-08-27generate Grafana's secret key by defaultTim Keller1-17/+17
Grafana's upstream default for security.secret_key is a constant compiled into every release, which is why nixpkgs 26.05 stopped passing it through and started asserting. Requiring secretKeyFile made that assertion the first thing anyone hit, and forced a secret manager plus a container bindMount on a deployment whose Grafana database holds no credentials at all -- both datasources are provisioned without any. generateSecretKey defaults to true (and to false as soon as secretKeyFile is set), writing 32 random bytes to Grafana's data directory on first start. The unit runs as the grafana user into a directory users.users.grafana.createHome already owns, so there is no chown and nothing fights Grafana for the directory. UMask=0077 makes the file 0600 on creation and an unconditional chmod repairs one left readable by a previous version or a manual copy. The tradeoff is stated in the option description rather than hidden: the key lives beside the database it encrypts, so it defends against a stolen grafana.db, not against filesystem access. secretKeyFile remains for when the database holds a datasource password or a contact-point token. Verified both paths build: with no secret wiring the generator unit is present and LoadCredential points into the data directory; with secretKeyFile set the generator is absent and LoadCredential points at the supplied path. Both emit secret_key=$__file{...} into config.ini. The script is idempotent, leaves an existing key untouched, and restores 0600 on a file chmodded to 0644. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JKczN1f66kmLxNBLX37voQ
2026-08-27generate the dashboards at build time instead of committing themTim Keller1-8/+9
The JSON is machine-written and the generator beside it is the only source of truth, so committing both only created a way for them to drift -- which is what the dashboards-current check existed to police. modules/dashboards.nix now runs gen_zfs.py / gen_smart.py in the derivation, so a stale dashboard cannot be built at all and the check is gone. Note there is no third option: flakes copy only git-tracked files into the store, so a gitignored dashboards/*.json cannot be read from disk either. It is generate, or commit. Verified equivalent -- both generated dashboards are byte-identical to the JSON this commit deletes. Adds a dashboards-nonempty check. Factoring the shared jq invocation out first introduced a bug it caught: the multi-line string ended in a newline, so the callers' `< $src > "$out"` landed on a line of its own as a separate command, truncating every dashboard to zero bytes while the build still succeeded. All four provisioned dashboards were empty. jqFixup is now a single line, and the check asserts each dashboard is non-empty and has a title, uid and panels. Both failure modes are exercised: an empty dashboard and one with no panels each fail the check. It also asserts it found four files, because the first version globbed *.json against linkFarm entries named without the suffix and passed vacuously. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JKczN1f66kmLxNBLX37voQ
2026-08-27split into a flake with separable collectors/server modulesTim Keller1-0/+280
Replaces the single lgtm.nix with a flake exposing two NixOS modules under services.telemetry, so the web services can move into a container declared in the consuming config rather than one hardcoded here. modules/collectors.nix host-side: node + smartctl exporters, the ZFS and SMART textfile collector timers, Alloy journal shipping. Must run on the host -- reads /proc, /sys, raw block devices and ZFS ioctls. modules/server.nix Prometheus, Loki, Grafana, optional Caddy. modules/common.nix ports, shared by both halves. modules/dashboards.nix jq fixup and grafana.com fetches. modules/packages.nix the two collectors as ordinary packages. Modules are exported as paths, not functions, so they evaluate with the consumer's nixpkgs; the flake's own input only affects packages/checks/devShells. Multi-host support. collectorHosts is a list, both scrape jobs relabel __address__ into a `host` label, and all 133 queries in the two generated dashboards are scoped to host=~"$host" behind a single-select Host picker. `instance` could not serve as that key: one machine is scraped twice on different ports (textfile metrics on :9100, smartctl_* on :9633) and the recording rules join across that boundary. Grafana's secret key becomes a plain secretKeyFile path wired through LoadCredential, with an assertion -- nixpkgs 26.05 dropped the default. No sops-nix input. nix flake check runs promtool over the rules, diffs the committed dashboard JSON against what the generators produce, and evaluates both modules. Also fixes smart-selftest emitting a placeholder identity line when smartctl cannot open a device. smartctl exits 0 with well-formed but empty JSON in that case, so "SSD - unknown model - no serial" was reaching a dashboard row title; it now emits nothing and lets the smartctl:ident fallback apply. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JKczN1f66kmLxNBLX37voQ