at 25.11-pre 9.1 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 utils, 6 ... 7}: 8let 9 inherit (lib) maintainers; 10 inherit (lib.meta) getExe; 11 inherit (lib.modules) mkIf mkMerge; 12 inherit (lib.options) 13 literalExpression 14 mkEnableOption 15 mkOption 16 mkPackageOption 17 ; 18 inherit (lib.types) 19 bool 20 enum 21 nullOr 22 port 23 str 24 submodule 25 ; 26 inherit (utils) genJqSecretsReplacementSnippet; 27 28 cfg = config.services.scrutiny; 29 # Define the settings format used for this program 30 settingsFormat = pkgs.formats.yaml { }; 31in 32{ 33 options = { 34 services.scrutiny = { 35 enable = mkEnableOption "Scrutiny, a web application for drive monitoring"; 36 37 package = mkPackageOption pkgs "scrutiny" { }; 38 39 openFirewall = mkEnableOption "opening the default ports in the firewall for Scrutiny"; 40 41 influxdb.enable = mkOption { 42 type = bool; 43 default = true; 44 description = '' 45 Enables InfluxDB on the host system using the `services.influxdb2` NixOS module 46 with default options. 47 48 If you already have InfluxDB configured, or wish to connect to an external InfluxDB 49 instance, disable this option. 50 ''; 51 }; 52 53 settings = mkOption { 54 description = '' 55 Scrutiny settings to be rendered into the configuration file. 56 57 See <https://github.com/AnalogJ/scrutiny/blob/master/example.scrutiny.yaml>. 58 59 Options containing secret data should be set to an attribute set 60 containing the attribute `_secret`. This attribute should be a string 61 or structured JSON with `quote = false;`, pointing to a file that 62 contains the value the option should be set to. 63 ''; 64 default = { }; 65 type = submodule { 66 freeformType = settingsFormat.type; 67 68 options.web.listen.port = mkOption { 69 type = port; 70 default = 8080; 71 description = "Port for web application to listen on."; 72 }; 73 74 options.web.listen.host = mkOption { 75 type = str; 76 default = "0.0.0.0"; 77 description = "Interface address for web application to bind to."; 78 }; 79 80 options.web.listen.basepath = mkOption { 81 type = str; 82 default = ""; 83 example = "/scrutiny"; 84 description = '' 85 If Scrutiny will be behind a path prefixed reverse proxy, you can override this 86 value to serve Scrutiny on a subpath. 87 ''; 88 }; 89 90 options.log.level = mkOption { 91 type = enum [ 92 "INFO" 93 "DEBUG" 94 ]; 95 default = "INFO"; 96 description = "Log level for Scrutiny."; 97 }; 98 99 options.web.influxdb.scheme = mkOption { 100 type = str; 101 default = "http"; 102 description = "URL scheme to use when connecting to InfluxDB."; 103 }; 104 105 options.web.influxdb.host = mkOption { 106 type = str; 107 default = "0.0.0.0"; 108 description = "IP or hostname of the InfluxDB instance."; 109 }; 110 111 options.web.influxdb.port = mkOption { 112 type = port; 113 default = 8086; 114 description = "The port of the InfluxDB instance."; 115 }; 116 117 options.web.influxdb.tls.insecure_skip_verify = 118 mkEnableOption "skipping TLS verification when connecting to InfluxDB"; 119 120 options.web.influxdb.token = mkOption { 121 type = nullOr str; 122 default = null; 123 description = "Authentication token for connecting to InfluxDB."; 124 }; 125 126 options.web.influxdb.org = mkOption { 127 type = nullOr str; 128 default = null; 129 description = "InfluxDB organisation under which to store data."; 130 }; 131 132 options.web.influxdb.bucket = mkOption { 133 type = nullOr str; 134 default = null; 135 description = "InfluxDB bucket in which to store data."; 136 }; 137 }; 138 }; 139 140 collector = { 141 enable = mkEnableOption "the Scrutiny metrics collector" // { 142 default = cfg.enable; 143 defaultText = lib.literalExpression "config.services.scrutiny.enable"; 144 }; 145 146 package = mkPackageOption pkgs "scrutiny-collector" { }; 147 148 schedule = mkOption { 149 type = str; 150 default = "*:0/15"; 151 description = '' 152 How often to run the collector in systemd calendar format. 153 ''; 154 }; 155 156 settings = mkOption { 157 description = '' 158 Collector settings to be rendered into the collector configuration file. 159 160 See <https://github.com/AnalogJ/scrutiny/blob/master/example.collector.yaml>. 161 162 Options containing secret data should be set to an attribute set 163 containing the attribute `_secret`. This attribute should be a string 164 or structured JSON with `quote = false;`, pointing to a file that 165 contains the value the option should be set to. 166 ''; 167 default = { }; 168 type = submodule { 169 freeformType = settingsFormat.type; 170 171 options.host.id = mkOption { 172 type = nullOr str; 173 default = null; 174 description = "Host ID for identifying/labelling groups of disks"; 175 }; 176 177 options.api.endpoint = mkOption { 178 type = str; 179 default = "http://${cfg.settings.web.listen.host}:${toString cfg.settings.web.listen.port}"; 180 defaultText = literalExpression ''"http://''${config.services.scrutiny.settings.web.listen.host}:''${config.services.scrutiny.settings.web.listen.port}"''; 181 description = "Scrutiny app API endpoint for sending metrics to."; 182 }; 183 184 options.log.level = mkOption { 185 type = enum [ 186 "INFO" 187 "DEBUG" 188 ]; 189 default = "INFO"; 190 description = "Log level for Scrutiny collector."; 191 }; 192 }; 193 }; 194 }; 195 }; 196 }; 197 198 config = mkMerge [ 199 (mkIf cfg.enable { 200 services.influxdb2.enable = cfg.influxdb.enable; 201 202 networking.firewall = mkIf cfg.openFirewall { 203 allowedTCPPorts = [ cfg.settings.web.listen.port ]; 204 }; 205 206 systemd.services.scrutiny = { 207 description = "Hard Drive S.M.A.R.T Monitoring, Historical Trends & Real World Failure Thresholds"; 208 wantedBy = [ "multi-user.target" ]; 209 after = [ "network.target" ] ++ lib.optional cfg.influxdb.enable "influxdb2.service"; 210 wants = lib.optional cfg.influxdb.enable "influxdb2.service"; 211 environment = { 212 SCRUTINY_VERSION = "1"; 213 SCRUTINY_WEB_DATABASE_LOCATION = "/var/lib/scrutiny/scrutiny.db"; 214 SCRUTINY_WEB_SRC_FRONTEND_PATH = "${cfg.package}/share/scrutiny"; 215 }; 216 preStart = '' 217 ${genJqSecretsReplacementSnippet cfg.settings "/run/scrutiny/config.yaml"} 218 ''; 219 postStart = '' 220 for i in $(seq 300); do 221 if "${lib.getExe pkgs.curl}" --fail --silent --head "http://${cfg.settings.web.listen.host}:${toString cfg.settings.web.listen.port}" >/dev/null; then 222 echo "Scrutiny is ready (port is open)" 223 exit 0 224 fi 225 echo "Waiting for Scrutiny to open port..." 226 sleep 0.2 227 done 228 echo "Timeout waiting for Scrutiny to open port" >&2 229 exit 1 230 ''; 231 serviceConfig = { 232 DynamicUser = true; 233 ExecStart = "${getExe cfg.package} start --config /run/scrutiny/config.yaml"; 234 Restart = "always"; 235 RuntimeDirectory = "scrutiny"; 236 RuntimeDirectoryMode = "0700"; 237 StateDirectory = "scrutiny"; 238 StateDirectoryMode = "0750"; 239 }; 240 }; 241 }) 242 (mkIf cfg.collector.enable { 243 services.smartd = { 244 enable = true; 245 extraOptions = [ 246 "-A /var/log/smartd/" 247 "--interval=600" 248 ]; 249 }; 250 251 systemd = { 252 services.scrutiny-collector = { 253 description = "Scrutiny Collector Service"; 254 after = lib.optional cfg.enable "scrutiny.service"; 255 wants = lib.optional cfg.enable "scrutiny.service"; 256 environment = { 257 COLLECTOR_VERSION = "1"; 258 COLLECTOR_API_ENDPOINT = cfg.collector.settings.api.endpoint; 259 }; 260 preStart = '' 261 ${genJqSecretsReplacementSnippet cfg.collector.settings "/run/scrutiny-collector/config.yaml"} 262 ''; 263 serviceConfig = { 264 Type = "oneshot"; 265 ExecStart = "${getExe cfg.collector.package} run --config /run/scrutiny-collector/config.yaml"; 266 RuntimeDirectory = "scrutiny-collector"; 267 RuntimeDirectoryMode = "0700"; 268 }; 269 startAt = cfg.collector.schedule; 270 }; 271 272 timers.scrutiny-collector.timerConfig.Persistent = true; 273 }; 274 }) 275 ]; 276 277 meta.maintainers = [ maintainers.jnsgruk ]; 278}