From eca4c6ea1911e565a6d5debd407000a8d0318f2d Mon Sep 17 00:00:00 2001 From: Moraxyc Date: Thu, 20 Mar 2025 20:45:49 +0800 Subject: [PATCH 1/3] nixos/scrutiny: use genJqSecretsReplacementSnippet --- .../modules/services/monitoring/scrutiny.nix | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/nixos/modules/services/monitoring/scrutiny.nix b/nixos/modules/services/monitoring/scrutiny.nix index 3265d94f6cd3..121dfc0668ad 100644 --- a/nixos/modules/services/monitoring/scrutiny.nix +++ b/nixos/modules/services/monitoring/scrutiny.nix @@ -1,10 +1,11 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, utils, ... }: let inherit (lib) maintainers; inherit (lib.meta) getExe; inherit (lib.modules) mkIf mkMerge; inherit (lib.options) literalExpression mkEnableOption mkOption mkPackageOption; inherit (lib.types) bool enum nullOr port str submodule; + inherit (utils) genJqSecretsReplacementSnippet; cfg = config.services.scrutiny; # Define the settings format used for this program @@ -36,6 +37,11 @@ in Scrutiny settings to be rendered into the configuration file. See . + + Options containing secret data should be set to an attribute set + containing the attribute `_secret`. This attribute should be a string + or structured JSON with `quote = false;`, pointing to a file that + contains the value the option should be set to. ''; default = { }; type = submodule { @@ -130,6 +136,11 @@ in Collector settings to be rendered into the collector configuration file. See . + + Options containing secret data should be set to an attribute set + containing the attribute `_secret`. This attribute should be a string + or structured JSON with `quote = false;`, pointing to a file that + contains the value the option should be set to. ''; default = { }; type = submodule { @@ -177,6 +188,9 @@ in SCRUTINY_WEB_DATABASE_LOCATION = "/var/lib/scrutiny/scrutiny.db"; SCRUTINY_WEB_SRC_FRONTEND_PATH = "${cfg.package}/share/scrutiny"; }; + preStart = '' + ${genJqSecretsReplacementSnippet cfg.settings "/run/scrutiny/config.yaml"} + ''; postStart = '' for i in $(seq 300); do if "${lib.getExe pkgs.curl}" --fail --silent --head "http://${cfg.settings.web.listen.host}:${toString cfg.settings.web.listen.port}" >/dev/null; then @@ -191,8 +205,10 @@ in ''; serviceConfig = { DynamicUser = true; - ExecStart = "${getExe cfg.package} start --config ${settingsFormat.generate "scrutiny.yaml" cfg.settings}"; + ExecStart = "${getExe cfg.package} start --config /run/scrutiny/config.yaml"; Restart = "always"; + RuntimeDirectory = "scrutiny"; + RuntimeDirectoryMode = "0700"; StateDirectory = "scrutiny"; StateDirectoryMode = "0750"; }; @@ -216,9 +232,14 @@ in COLLECTOR_VERSION = "1"; COLLECTOR_API_ENDPOINT = cfg.collector.settings.api.endpoint; }; + preStart = '' + ${genJqSecretsReplacementSnippet cfg.collector.settings "/run/scrutiny-collector/config.yaml"} + ''; serviceConfig = { Type = "oneshot"; - ExecStart = "${getExe cfg.collector.package} run --config ${settingsFormat.generate "scrutiny-collector.yaml" cfg.collector.settings}"; + ExecStart = "${getExe cfg.collector.package} run --config /run/scrutiny-collector/config.yaml"; + RuntimeDirectory = "scrutiny-collector"; + RuntimeDirectoryMode = "0700"; }; startAt = cfg.collector.schedule; }; From a9085572f2a0d645b20a404262554991405119fa Mon Sep 17 00:00:00 2001 From: Moraxyc Date: Fri, 21 Mar 2025 13:41:11 +0800 Subject: [PATCH 2/3] nixosTests.scrutiny: add test for notification feature --- nixos/tests/scrutiny.nix | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/nixos/tests/scrutiny.nix b/nixos/tests/scrutiny.nix index a81dd60d8518..93a28c861dcf 100644 --- a/nixos/tests/scrutiny.nix +++ b/nixos/tests/scrutiny.nix @@ -15,7 +15,18 @@ import ./make-test-python.nix ( }: { services = { - scrutiny.enable = true; + scrutiny = { + enable = true; + settings = { + notify.urls = [ + { + _secret = pkgs.writeText "notify-script" "script://${pkgs.writeShellScript "touch-test-file" '' + echo "HelloWorld" > /run/scrutiny/hello + ''}"; + } + ]; + }; + }; scrutiny.collector.enable = true; }; @@ -78,6 +89,12 @@ import ./make-test-python.nix ( # Ensure the application is actually rendered by the Javascript machine.succeed("PYTHONUNBUFFERED=1 selenium-script") + + # Test notification and genJqSecretsReplacementSnippet + machine.succeed("curl -X POST http://localhost:8080/api/health/notify") + machine.wait_for_file("/run/scrutiny/hello") + output = machine.succeed("cat /run/scrutiny/hello") + assert "HelloWorld" in output ''; } ) From ab404806ffcb57f1d7bd68005ccd754626fce674 Mon Sep 17 00:00:00 2001 From: Moraxyc Date: Fri, 21 Mar 2025 13:49:47 +0800 Subject: [PATCH 3/3] nixosTests.scrutiny: migrate to runTest Part of #386873 --- nixos/tests/all-tests.nix | 2 +- nixos/tests/scrutiny.nix | 178 +++++++++++++++++++------------------- 2 files changed, 89 insertions(+), 91 deletions(-) diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 43103c6b91c1..98cdfc602c84 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1051,7 +1051,7 @@ in { scaphandre = handleTest ./scaphandre.nix {}; schleuder = handleTest ./schleuder.nix {}; scion-freestanding-deployment = handleTest ./scion/freestanding-deployment {}; - scrutiny = handleTest ./scrutiny.nix {}; + scrutiny = runTest ./scrutiny.nix; sddm = handleTest ./sddm.nix {}; sdl3 = handleTest ./sdl3.nix { }; seafile = handleTest ./seafile.nix {}; diff --git a/nixos/tests/scrutiny.nix b/nixos/tests/scrutiny.nix index 93a28c861dcf..17d984f4c1ff 100644 --- a/nixos/tests/scrutiny.nix +++ b/nixos/tests/scrutiny.nix @@ -1,100 +1,98 @@ -import ./make-test-python.nix ( - { lib, ... }: +{ lib, ... }: - { - name = "scrutiny"; - meta.maintainers = with lib.maintainers; [ jnsgruk ]; +{ + name = "scrutiny"; + meta.maintainers = with lib.maintainers; [ jnsgruk ]; - nodes = { - machine = - { - self, - pkgs, - lib, - ... - }: - { - services = { - scrutiny = { - enable = true; - settings = { - notify.urls = [ - { - _secret = pkgs.writeText "notify-script" "script://${pkgs.writeShellScript "touch-test-file" '' - echo "HelloWorld" > /run/scrutiny/hello - ''}"; - } - ]; - }; + nodes = { + machine = + { + self, + pkgs, + lib, + ... + }: + { + services = { + scrutiny = { + enable = true; + settings = { + notify.urls = [ + { + _secret = pkgs.writeText "notify-script" "script://${pkgs.writeShellScript "touch-test-file" '' + echo "HelloWorld" > /run/scrutiny/hello + ''}"; + } + ]; }; - 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 - ]; + scrutiny.collector.enable = true; }; - }; - # This is the test code that will check if our service is running correctly: - testScript = '' - start_all() - # Wait for Scrutiny to be available - machine.wait_for_unit("scrutiny") - machine.wait_for_open_port(8080) + 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 - # Ensure the API responds as we expect - output = machine.succeed("curl localhost:8080/api/health") - assert output == '{"success":true}' + options = Options() + options.add_argument("--headless") + service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}") # noqa: E501 - # Start the collector service to send some metrics - collect = machine.succeed("systemctl start scrutiny-collector.service") + driver = webdriver.Firefox(options=options, service=service) + driver.implicitly_wait(10) + driver.get("http://localhost:8080/web/dashboard") - # Ensure the application is actually rendered by the Javascript - machine.succeed("PYTHONUNBUFFERED=1 selenium-script") + wait = WebDriverWait(driver, 10).until( + EC.text_to_be_present_in_element( + (By.TAG_NAME, "body"), "Drive health at a glance") + ) - # Test notification and genJqSecretsReplacementSnippet - machine.succeed("curl -X POST http://localhost:8080/api/health/notify") - machine.wait_for_file("/run/scrutiny/hello") - output = machine.succeed("cat /run/scrutiny/hello") - assert "HelloWorld" in output - ''; - } -) + 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 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") + + # Test notification and genJqSecretsReplacementSnippet + machine.succeed("curl -X POST http://localhost:8080/api/health/notify") + machine.wait_for_file("/run/scrutiny/hello") + output = machine.succeed("cat /run/scrutiny/hello") + assert "HelloWorld" in output + ''; +}