aboutsummaryrefslogtreecommitdiffstats
path: root/flake.nix
blob: 1a9c661ef55f54b7b91be17786c93d1be71dd746 (plain)
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);
    };
}