1{ config, lib, pkgs, ... }:
2let
3 inherit (lib) maintainers;
4 inherit (lib.meta) getExe;
5 inherit (lib.modules) mkIf mkMerge;
6 inherit (lib.options) literalExpression mkEnableOption mkOption mkPackageOption;
7 inherit (lib.types) bool enum nullOr port str submodule;
8
9 cfg = config.services.scrutiny;
10 # Define the settings format used for this program
11 settingsFormat = pkgs.formats.yaml { };
12in
13{
14 options = {
15 services.scrutiny = {
16 enable = mkEnableOption "Scrutiny, a web application for drive monitoring";
17
18 package = mkPackageOption pkgs "scrutiny" { };
19
20 openFirewall = mkEnableOption "opening the default ports in the firewall for Scrutiny";
21
22 influxdb.enable = mkOption {
23 type = bool;
24 default = true;
25 description = ''
26 Enables InfluxDB on the host system using the `services.influxdb2` NixOS module
27 with default options.
28
29 If you already have InfluxDB configured, or wish to connect to an external InfluxDB
30 instance, disable this option.
31 '';
32 };
33
34 settings = mkOption {
35 description = ''
36 Scrutiny settings to be rendered into the configuration file.
37
38 See https://github.com/AnalogJ/scrutiny/blob/master/example.scrutiny.yaml.
39 '';
40 default = { };
41 type = submodule {
42 freeformType = settingsFormat.type;
43
44 options.web.listen.port = mkOption {
45 type = port;
46 default = 8080;
47 description = "Port for web application to listen on.";
48 };
49
50 options.web.listen.host = mkOption {
51 type = str;
52 default = "0.0.0.0";
53 description = "Interface address for web application to bind to.";
54 };
55
56 options.web.listen.basepath = mkOption {
57 type = str;
58 default = "";
59 example = "/scrutiny";
60 description = ''
61 If Scrutiny will be behind a path prefixed reverse proxy, you can override this
62 value to serve Scrutiny on a subpath.
63 '';
64 };
65
66 options.log.level = mkOption {
67 type = enum [ "INFO" "DEBUG" ];
68 default = "INFO";
69 description = "Log level for Scrutiny.";
70 };
71
72 options.web.influxdb.scheme = mkOption {
73 type = str;
74 default = "http";
75 description = "URL scheme to use when connecting to InfluxDB.";
76 };
77
78 options.web.influxdb.host = mkOption {
79 type = str;
80 default = "0.0.0.0";
81 description = "IP or hostname of the InfluxDB instance.";
82 };
83
84 options.web.influxdb.port = mkOption {
85 type = port;
86 default = 8086;
87 description = "The port of the InfluxDB instance.";
88 };
89
90 options.web.influxdb.tls.insecure_skip_verify = mkEnableOption "skipping TLS verification when connecting to InfluxDB";
91
92 options.web.influxdb.token = mkOption {
93 type = nullOr str;
94 default = null;
95 description = "Authentication token for connecting to InfluxDB.";
96 };
97
98 options.web.influxdb.org = mkOption {
99 type = nullOr str;
100 default = null;
101 description = "InfluxDB organisation under which to store data.";
102 };
103
104 options.web.influxdb.bucket = mkOption {
105 type = nullOr str;
106 default = null;
107 description = "InfluxDB bucket in which to store data.";
108 };
109 };
110 };
111
112 collector = {
113 enable = mkEnableOption "the Scrutiny metrics collector";
114
115 package = mkPackageOption pkgs "scrutiny-collector" { };
116
117 schedule = mkOption {
118 type = str;
119 default = "*:0/15";
120 description = ''
121 How often to run the collector in systemd calendar format.
122 '';
123 };
124
125 settings = mkOption {
126 description = ''
127 Collector settings to be rendered into the collector configuration file.
128
129 See https://github.com/AnalogJ/scrutiny/blob/master/example.collector.yaml.
130 '';
131 default = { };
132 type = submodule {
133 freeformType = settingsFormat.type;
134
135 options.host.id = mkOption {
136 type = nullOr str;
137 default = null;
138 description = "Host ID for identifying/labelling groups of disks";
139 };
140
141 options.api.endpoint = mkOption {
142 type = str;
143 default = "http://localhost:${toString cfg.settings.web.listen.port}";
144 defaultText = literalExpression ''"http://localhost:''${config.services.scrutiny.settings.web.listen.port}"'';
145 description = "Scrutiny app API endpoint for sending metrics to.";
146 };
147
148 options.log.level = mkOption {
149 type = enum [ "INFO" "DEBUG" ];
150 default = "INFO";
151 description = "Log level for Scrutiny collector.";
152 };
153 };
154 };
155 };
156 };
157 };
158
159 config = mkMerge [
160 (mkIf cfg.enable {
161 services.influxdb2.enable = cfg.influxdb.enable;
162
163 networking.firewall = mkIf cfg.openFirewall {
164 allowedTCPPorts = [ cfg.settings.web.listen.port ];
165 };
166
167 systemd.services.scrutiny = {
168 description = "Hard Drive S.M.A.R.T Monitoring, Historical Trends & Real World Failure Thresholds";
169 wantedBy = [ "multi-user.target" ];
170 after = [ "network.target" ] ++ lib.optional cfg.influxdb.enable "influxdb2.service";
171 wants = lib.optional cfg.influxdb.enable "influxdb2.service";
172 environment = {
173 SCRUTINY_VERSION = "1";
174 SCRUTINY_WEB_DATABASE_LOCATION = "/var/lib/scrutiny/scrutiny.db";
175 SCRUTINY_WEB_SRC_FRONTEND_PATH = "${cfg.package}/share/scrutiny";
176 };
177 serviceConfig = {
178 DynamicUser = true;
179 ExecStart = "${getExe cfg.package} start --config ${settingsFormat.generate "scrutiny.yaml" cfg.settings}";
180 Restart = "always";
181 StateDirectory = "scrutiny";
182 StateDirectoryMode = "0750";
183 };
184 };
185 })
186 (mkIf cfg.collector.enable {
187 services.smartd = {
188 enable = true;
189 extraOptions = [
190 "-A /var/log/smartd/"
191 "--interval=600"
192 ];
193 };
194
195 systemd = {
196 services.scrutiny-collector = {
197 description = "Scrutiny Collector Service";
198 after = lib.optional cfg.enable "scrutiny.service";
199 wants = lib.optional cfg.enable "scrutiny.service";
200 environment = {
201 COLLECTOR_VERSION = "1";
202 COLLECTOR_API_ENDPOINT = cfg.collector.settings.api.endpoint;
203 };
204 serviceConfig = {
205 Type = "oneshot";
206 ExecStart = "${getExe cfg.collector.package} run --config ${settingsFormat.generate "scrutiny-collector.yaml" cfg.collector.settings}";
207 };
208 startAt = cfg.collector.schedule;
209 };
210
211 timers.scrutiny-collector.timerConfig.Persistent = true;
212 };
213 })
214 ];
215
216 meta.maintainers = [ maintainers.jnsgruk ];
217}