Nix Observability Daemon
observability
nix
1{
2 description = "A simple self-contained daemon to gather nix statistics";
3
4 inputs = {
5 nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
6 rust-overlay.url = "github:oxalica/rust-overlay";
7 rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
8 };
9
10 outputs = {
11 self,
12 nixpkgs,
13 rust-overlay,
14 }: let
15 systems = ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"];
16 forAllSystems = f:
17 nixpkgs.lib.genAttrs systems (system:
18 f (import nixpkgs {
19 inherit system;
20 overlays = [(import rust-overlay)];
21 }));
22 in {
23 packages = forAllSystems (pkgs: {
24 default = pkgs.rustPlatform.buildRustPackage {
25 pname = "nod";
26 version = "0.1.0";
27 src = ./.;
28 cargoLock.lockFile = ./Cargo.lock;
29
30 nativeBuildInputs = [pkgs.pkg-config];
31 buildInputs =
32 [pkgs.sqlite]
33 ++ (
34 if pkgs.stdenv.isDarwin
35 then [pkgs.iconv]
36 else []
37 );
38 };
39 });
40 devShells = forAllSystems (pkgs: {
41 default = pkgs.mkShell {
42 buildInputs =
43 [
44 pkgs.rust-bin.stable.latest.default
45 pkgs.pkg-config
46 pkgs.sqlite
47 ]
48 ++ (
49 if pkgs.stdenv.isDarwin
50 then [pkgs.iconv]
51 else []
52 );
53 };
54 });
55
56 nixosModules.default = {
57 config,
58 lib,
59 pkgs,
60 ...
61 }: let
62 cfg = config.services.nod;
63 in {
64 options.services.nod = {
65 enable = lib.mkEnableOption "Nix Observability Daemon";
66 package = lib.mkOption {
67 type = lib.types.package;
68 default = self.packages.${pkgs.system}.default;
69 description = "The nod package to use.";
70 };
71 user = lib.mkOption {
72 type = lib.types.str;
73 default = "nod";
74 description = "User to run the nod daemon as.";
75 };
76 group = lib.mkOption {
77 type = lib.types.str;
78 default = "nod";
79 description = ''
80 Group for the nod daemon. Other services that need read access to the database (e.g. a monitoring agent) should be added to this group.
81 '';
82 };
83
84 socketPath = lib.mkOption {
85 type = lib.types.path;
86 default = "/run/nod/nod.sock";
87 description = "Path to the Unix socket. Exposed via NOD_SOCKET in the session environment.";
88 };
89 databasePath = lib.mkOption {
90 type = lib.types.path;
91 default = "/var/lib/nod/nod.db";
92 description = "Path to the SQLite database";
93 };
94 };
95
96 config = lib.mkIf cfg.enable {
97 users.users.${cfg.user} = {
98 isSystemUser = true;
99 group = cfg.group;
100 description = "Nix Observability Daemon";
101 };
102 users.groups.${cfg.group} = {};
103
104 # Tell nix to forward its internal JSON log to the socket
105 nix.settings.json-log-path = cfg.socketPath;
106
107 # Make the socket path available to interactive shells so users
108 # can run `nod stats` without passing --socket explicitly.
109 environment.sessionVariables.NOD_SOCKET = cfg.socketPath;
110
111 systemd.services.nod = {
112 description = "Nix Observability Daemon";
113 wantedBy = ["multi-user.target"];
114 after = ["network.target"];
115
116 serviceConfig = {
117 User = cfg.user;
118 Group = cfg.group;
119 ExecStart = "${cfg.package}/bin/nod daemon --db ${cfg.databasePath} --socket ${cfg.socketPath}";
120 Restart = "always";
121 StateDirectory = "nod";
122 StateDirectoryMode = "0750";
123 # /run/nod must be world-searchable so nix (running as any user) can reach the socket.
124 RuntimeDirectory = "nod";
125 RuntimeDirectoryMode = "0755";
126 # SQLite WAL mode requires write access to the -shm file even for read-only connections.
127 UMask = "0117";
128 };
129 };
130 };
131 };
132 };
133}