Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scrutiny: init at 0.7.2 (+nix module, tests) #289934

Merged
merged 5 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions nixos/doc/manual/release-notes/rl-2405.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m

- [RustDesk](https://rustdesk.com), a full-featured open source remote control alternative for self-hosting and security with minimal configuration. Alternative to TeamViewer.

- [Scrutiny](https://github.com/AnalogJ/scrutiny), a S.M.A.R.T monitoring tool for hard disks with a web frontend.

- [systemd-lock-handler](https://git.sr.ht/~whynothugo/systemd-lock-handler/), a bridge between logind D-Bus events and systemd targets. Available as [services.systemd-lock-handler.enable](#opt-services.systemd-lock-handler.enable).

## Backward Incompatibilities {#sec-release-24.05-incompatibilities}
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,7 @@
./services/monitoring/riemann.nix
./services/monitoring/rustdesk-server.nix
./services/monitoring/scollector.nix
./services/monitoring/scrutiny.nix
./services/monitoring/smartd.nix
./services/monitoring/snmpd.nix
./services/monitoring/statsd.nix
Expand Down
221 changes: 221 additions & 0 deletions nixos/modules/services/monitoring/scrutiny.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.scrutiny;
# Define the settings format used for this program
settingsFormat = pkgs.formats.yaml { };
in
{
options = {
services.scrutiny = {
enable = lib.mkEnableOption "Enables the scrutiny web application.";

package = lib.mkPackageOptionMD pkgs "scrutiny" { };

openFirewall = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Open the default ports in the firewall for Scrutiny.";
};

influxdb.enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = lib.mdDoc ''
Enables InfluxDB on the host system using the `services.influxdb2` NixOS module
with default options.

If you already have InfluxDB configured, or wish to connect to an external InfluxDB
instance, disable this option.
'';
};

settings = lib.mkOption {
description = lib.mdDoc ''
Scrutiny settings to be rendered into the configuration file.

See https://github.com/AnalogJ/scrutiny/blob/master/example.scrutiny.yaml.
'';
default = { };
type = lib.types.submodule {
freeformType = settingsFormat.type;

options.web.listen.port = lib.mkOption {
type = lib.types.port;
default = 8080;
description = lib.mdDoc "Port for web application to listen on.";
};

options.web.listen.host = lib.mkOption {
type = lib.types.str;
default = "0.0.0.0";
description = lib.mdDoc "Interface address for web application to bind to.";
};

options.web.listen.basepath = lib.mkOption {
type = lib.types.str;
default = "";
example = "/scrutiny";
description = lib.mdDoc ''
If Scrutiny will be behind a path prefixed reverse proxy, you can override this
value to serve Scrutiny on a subpath.
'';
};

options.log.level = lib.mkOption {
type = lib.types.enum [ "INFO" "DEBUG" ];
default = "INFO";
description = lib.mdDoc "Log level for Scrutiny.";
};

options.web.influxdb.scheme = lib.mkOption {
type = lib.types.str;
default = "http";
description = lib.mdDoc "URL scheme to use when connecting to InfluxDB.";
};

options.web.influxdb.host = lib.mkOption {
type = lib.types.str;
default = "0.0.0.0";
description = lib.mdDoc "IP or hostname of the InfluxDB instance.";
};

options.web.influxdb.port = lib.mkOption {
type = lib.types.port;
default = 8086;
description = lib.mdDoc "The port of the InfluxDB instance.";
};

options.web.influxdb.tls.insecure_skip_verify = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc "Skip TLS verification when connecting to InfluxDB.";
};

options.web.influxdb.token = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc "Authentication token for connecting to InfluxDB.";
};

options.web.influxdb.org = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc "InfluxDB organisation under which to store data.";
};

options.web.influxdb.bucket = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc "InfluxDB bucket in which to store data.";
};
};
};

collector = {
enable = lib.mkEnableOption "Enables the scrutiny metrics collector.";

package = lib.mkPackageOptionMD pkgs "scrutiny-collector" { };

schedule = lib.mkOption {
type = lib.types.str;
default = "*:0/15";
description = lib.mdDoc ''
How often to run the collector in systemd calendar format.
'';
};

settings = lib.mkOption {
description = lib.mdDoc ''
Collector settings to be rendered into the collector configuration file.

See https://github.com/AnalogJ/scrutiny/blob/master/example.collector.yaml.
'';
default = { };
type = lib.types.submodule {
freeformType = settingsFormat.type;

options.host.id = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc "Host ID for identifying/labelling groups of disks";
};

options.api.endpoint = lib.mkOption {
type = lib.types.str;
default = "http://localhost:8080";
description = lib.mdDoc "Scrutiny app API endpoint for sending metrics to.";
};

options.log.level = lib.mkOption {
type = lib.types.enum [ "INFO" "DEBUG" ];
default = "INFO";
description = lib.mdDoc "Log level for Scrutiny collector.";
};
};
};
};
};
};

config = lib.mkIf (cfg.enable || cfg.collector.enable) {
services.influxdb2.enable = cfg.influxdb.enable;

networking.firewall = lib.mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.settings.web.listen.port ];
};

services.smartd = lib.mkIf cfg.collector.enable {
enable = true;
extraOptions = [
"-A /var/log/smartd/"
"--interval=600"
];
};

systemd = {
services = {
scrutiny = lib.mkIf cfg.enable {
description = "Hard Drive S.M.A.R.T Monitoring, Historical Trends & Real World Failure Thresholds";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
environment = {
SCRUTINY_VERSION = "1";
SCRUTINY_WEB_DATABASE_LOCATION = "/var/lib/scrutiny/scrutiny.db";
SCRUTINY_WEB_SRC_FRONTEND_PATH = "${cfg.package}/share/scrutiny";
};
serviceConfig = {
DynamicUser = true;
ExecStart = "${lib.getExe cfg.package} start --config ${settingsFormat.generate "scrutiny.yaml" cfg.settings}";
Restart = "always";
StateDirectory = "scrutiny";
StateDirectoryMode = "0750";
};
};

scrutiny-collector = lib.mkIf cfg.collector.enable {
description = "Scrutiny Collector Service";
environment = {
COLLECTOR_VERSION = "1";
COLLECTOR_API_ENDPOINT = cfg.collector.settings.api.endpoint;
};
serviceConfig = {
Type = "oneshot";
ExecStart = "${lib.getExe cfg.collector.package} run --config ${settingsFormat.generate "scrutiny-collector.yaml" cfg.collector.settings}";
};
};
};

timers = lib.mkIf cfg.collector.enable {
scrutiny-collector = {
timerConfig = {
OnCalendar = cfg.collector.schedule;
Persistent = true;
Unit = "scrutiny-collector.service";
};
};
};
};
};

meta.maintainers = [ lib.maintainers.jnsgruk ];
}
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,7 @@ in {
sanoid = handleTest ./sanoid.nix {};
scaphandre = handleTest ./scaphandre.nix {};
schleuder = handleTest ./schleuder.nix {};
scrutiny = handleTest ./scrutiny.nix {};
sddm = handleTest ./sddm.nix {};
seafile = handleTest ./seafile.nix {};
searx = handleTest ./searx.nix {};
Expand Down
70 changes: 70 additions & 0 deletions nixos/tests/scrutiny.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import ./make-test-python.nix ({ lib, ... }:

{
name = "scrutiny";
meta.maintainers = with lib.maintainers; [ jnsgruk ];

nodes = {
machine = { self, pkgs, lib, ... }: {
services = {
scrutiny.enable = true;
scrutiny.collector.enable = true;
};

environment.systemPackages =
let
seleniumScript = pkgs.writers.writePython3Bin "selenium-script"
{
libraries = with pkgs.python3Packages; [ selenium ];
} ''
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

options = Options()
options.add_argument("--headless")
service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}") # noqa: E501

driver = webdriver.Firefox(options=options, service=service)
driver.implicitly_wait(10)
driver.get("http://localhost:8080/web/dashboard")

wait = WebDriverWait(driver, 10).until(
EC.text_to_be_present_in_element(
(By.TAG_NAME, "body"), "Drive health at a glance")
)

body_text = driver.find_element(By.TAG_NAME, "body").text
assert "Temperature history for each device" in body_text

driver.close()
'';
in
with pkgs; [ curl firefox-unwrapped geckodriver seleniumScript ];
};
};
# This is the test code that will check if our service is running correctly:
testScript = ''
start_all()

# Wait for InfluxDB to be available
machine.wait_for_unit("influxdb2")
machine.wait_for_open_port(8086)

# Wait for Scrutiny to be available
machine.wait_for_unit("scrutiny")
machine.wait_for_open_port(8080)

# Ensure the API responds as we expect
output = machine.succeed("curl localhost:8080/api/health")
assert output == '{"success":true}'

# Start the collector service to send some metrics
collect = machine.succeed("systemctl start scrutiny-collector.service")

# Ensure the application is actually rendered by the Javascript
machine.succeed("PYTHONUNBUFFERED=1 selenium-script")
'';
})
53 changes: 53 additions & 0 deletions pkgs/by-name/sc/scrutiny-collector/package.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{ buildGoModule
, fetchFromGitHub
, makeWrapper
, smartmontools
, nixosTests
, lib
}:
let
version = "0.7.2";
in
buildGoModule rec {
inherit version;
pname = "scrutiny-collector";

src = fetchFromGitHub {
owner = "AnalogJ";
repo = "scrutiny";
rev = "refs/tags/v${version}";
hash = "sha256-UYKi+WTsasUaE6irzMAHr66k7wXyec8FXc8AWjEk0qs=";
};

subPackages = "collector/cmd/collector-metrics";

vendorHash = "sha256-SiQw6pq0Fyy8Ia39S/Vgp9Mlfog2drtVn43g+GXiQuI=";

buildInputs = [ makeWrapper ];

CGO_ENABLED = 0;

ldflags = [ "-extldflags=-static" ];

tags = [ "static" ];

installPhase = ''
runHook preInstall
mkdir -p $out/bin
cp $GOPATH/bin/collector-metrics $out/bin/scrutiny-collector-metrics
wrapProgram $out/bin/scrutiny-collector-metrics \
--prefix PATH : ${lib.makeBinPath [ smartmontools ]}
runHook postInstall
'';

passthru.tests.scrutiny-collector = nixosTests.scrutiny;

meta = {
description = "Hard disk metrics collector for Scrutiny.";
homepage = "https://github.com/AnalogJ/scrutiny";
license = lib.licenses.mit;
maintainers = with lib.maintainers; [ jnsgruk ];
mainProgram = "scrutiny-collector-metrics";
platforms = lib.platforms.linux;
};
}
Loading
Loading