1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
{
description = "Host telemetry collectors and a Prometheus/Loki/Grafana server, as two separable NixOS modules";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
outputs = { self, nixpkgs }:
let
systems = [ "x86_64-linux" "aarch64-linux" ];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
in
{
# Paths rather than functions, so the modules are evaluated with the *consumer's*
# nixpkgs. This flake's own nixpkgs input therefore only affects packages, checks
# and devShells -- importing these modules never pulls in a second nixpkgs.
nixosModules = {
collectors = ./modules/collectors.nix;
server = ./modules/server.nix;
default = ./modules;
};
# The textfile collectors, runnable by hand. Both take --output PATH and write
# Prometheus exposition format; running them against a scratch file is how you
# check what a machine actually reports.
packages = forAllSystems (pkgs:
# callPackages, not callPackage: the file returns an attrset of
# derivations, and callPackage would wrap the whole set in
# override/overrideDerivation attrs that are not derivations.
let collectors = pkgs.callPackages ./modules/packages.nix { };
in collectors // { default = collectors.zfs-metrics; }
);
checks = forAllSystems (pkgs: {
# promtool over the recording rules. The server module also runs this at build
# time via services.prometheus.checkConfig, but that only fires for people who
# actually deploy it.
prometheus-rules = pkgs.runCommand "check-prometheus-rules"
{ nativeBuildInputs = [ pkgs.prometheus.cli ]; }
''
promtool check rules ${./rules}/*.yml
touch "$out"
'';
# The dashboard JSON is generated by the scripts next to it and committed, so it
# can rot silently if someone edits a generator without re-running it. Regenerate
# here and diff.
dashboards-current = pkgs.runCommand "check-dashboards-current"
{ nativeBuildInputs = [ pkgs.python3 pkgs.diffutils ]; }
''
cp -r ${./dashboards} work
chmod -R u+w work
cd work
# Note the loop variable is not called "out": that is the derivation's own
# output path, and shadowing it makes the final touch silently write
# somewhere else and the build fail with "failed to produce output path".
check() {
local generator="$1" committed="$2"
python3 "$generator" > regenerated.json
if ! diff -u "$committed" regenerated.json; then
echo
echo "dashboards/$committed is stale."
echo "Re-run: python3 dashboards/$generator > dashboards/$committed"
exit 1
fi
}
check gen_smart.py smart.json
check gen_zfs.py zfs-pools.json
touch "$out"
'';
# Both halves have to evaluate, separately and together. Catches option typos
# and missing mkIf guards without needing a real machine.
modules-eval = (nixpkgs.lib.nixosSystem {
system = pkgs.stdenv.hostPlatform.system;
modules = [
self.nixosModules.default
({ lib, ... }: {
services.telemetry.collectors.enable = true;
services.telemetry.server = {
enable = true;
collectorHosts = [ "one" "two" ];
proxy.enable = true;
# Never read during evaluation; only has to satisfy the assertion.
grafana.secretKeyFile = "/run/secrets/grafana-secret-key";
};
boot.loader.grub.devices = [ "/dev/sda" ];
fileSystems."/" = { device = "/dev/sda1"; fsType = "ext4"; };
system.stateVersion = "25.11";
})
];
}).config.system.build.toplevel;
});
devShells = forAllSystems (pkgs: {
default = pkgs.mkShell {
packages = [
pkgs.python3 # the dashboard generators and collectors
pkgs.prometheus.cli # promtool, for the recording rules
pkgs.prometheus # a real Prometheus to run dashboard queries against
pkgs.jq
pkgs.smartmontools
];
};
});
formatter = forAllSystems (pkgs: pkgs.nixpkgs-fmt);
};
}
|