{ 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: let dashboardsForCheck = pkgs.linkFarm "dashboards" (pkgs.lib.mapAttrsToList # The .json suffix matters: the check globs *.json, and a # linkFarm named without it silently matches nothing. (name: drv: { name = "${name}.json"; path = drv; }) # callPackages, not callPackage: the latter adds override/ # overrideDerivation attrs that are functions, not derivations. (pkgs.callPackages ./modules/dashboards.nix { dsUid = { prometheus = "prometheus"; loki = "loki"; }; })); in { # 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" ''; # An empty or malformed dashboard does not fail any build on its own -- it # provisions fine and simply renders nothing -- so assert on the content. dashboards-nonempty = pkgs.runCommand "check-dashboards-nonempty" { nativeBuildInputs = [ pkgs.jq ]; } '' shopt -s nullglob found=0 for f in ${dashboardsForCheck}/*.json; do found=$((found + 1)) name=$(basename "$f") [ -s "$f" ] || { echo "$name is empty"; exit 1; } jq -e '.title and .uid and (.panels | length > 0)' "$f" >/dev/null \ || { echo "$name has no title, uid or panels"; exit 1; } echo "$name: $(jq -r '"\(.title) — \(.panels | length) panels"' "$f")" done # A glob that matches nothing would otherwise make this check vacuous. [ "$found" -eq 4 ] || { echo "expected 4 dashboards, found $found"; exit 1; } touch "$out" ''; # Both halves have to evaluate, separately and together. Catches option typos # and missing mkIf guards without needing a real machine -- and because the # dashboards are generated during the build, this also runs both generators and # fails if either one crashes or emits something jq cannot parse. 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); }; }