nixos/nvme-rs: init
This commit is contained in:
@@ -130,6 +130,8 @@
|
|||||||
|
|
||||||
- [Sshwifty](https://github.com/nirui/sshwifty), a Telnet and SSH client for your browser. Available as [services.sshwifty](#opt-services.sshwifty.enable).
|
- [Sshwifty](https://github.com/nirui/sshwifty), a Telnet and SSH client for your browser. Available as [services.sshwifty](#opt-services.sshwifty.enable).
|
||||||
|
|
||||||
|
- [nvme-rs](https://github.com/liberodark/nvme-rs), NVMe monitoring [services.nvme-rs](#opt-services.nvme-rs.enable).
|
||||||
|
|
||||||
## Backward Incompatibilities {#sec-release-25.11-incompatibilities}
|
## Backward Incompatibilities {#sec-release-25.11-incompatibilities}
|
||||||
|
|
||||||
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
|
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
|
||||||
|
|||||||
@@ -1499,6 +1499,7 @@
|
|||||||
./services/system/localtimed.nix
|
./services/system/localtimed.nix
|
||||||
./services/system/nix-daemon.nix
|
./services/system/nix-daemon.nix
|
||||||
./services/system/nscd.nix
|
./services/system/nscd.nix
|
||||||
|
./services/system/nvme-rs.nix
|
||||||
./services/system/saslauthd.nix
|
./services/system/saslauthd.nix
|
||||||
./services/system/self-deploy.nix
|
./services/system/self-deploy.nix
|
||||||
./services/system/swapspace.nix
|
./services/system/swapspace.nix
|
||||||
|
|||||||
204
nixos/modules/services/system/nvme-rs.nix
Normal file
204
nixos/modules/services/system/nvme-rs.nix
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
options,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib) types;
|
||||||
|
cfg = config.services.nvme-rs;
|
||||||
|
opt = options.services.nvme-rs;
|
||||||
|
settingsFormat = pkgs.formats.toml { };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.nvme-rs = {
|
||||||
|
enable = lib.mkEnableOption "nvme-rs, a monitoring service";
|
||||||
|
|
||||||
|
package = lib.mkPackageOption pkgs "nvme-rs" { };
|
||||||
|
|
||||||
|
settings = lib.mkOption {
|
||||||
|
type = types.submodule {
|
||||||
|
freeformType = settingsFormat.type;
|
||||||
|
options = {
|
||||||
|
check_interval_secs = lib.mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 3600;
|
||||||
|
description = "Check interval in seconds";
|
||||||
|
example = 86400;
|
||||||
|
};
|
||||||
|
|
||||||
|
thresholds = lib.mkOption {
|
||||||
|
type = types.submodule {
|
||||||
|
freeformType = settingsFormat.type;
|
||||||
|
options = {
|
||||||
|
temp_warning = lib.mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 55;
|
||||||
|
description = "Temperature warning threshold (°C)";
|
||||||
|
};
|
||||||
|
|
||||||
|
temp_critical = lib.mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 65;
|
||||||
|
description = "Temperature critical threshold (°C)";
|
||||||
|
};
|
||||||
|
|
||||||
|
wear_warning = lib.mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 20;
|
||||||
|
description = "Wear warning threshold (%)";
|
||||||
|
};
|
||||||
|
|
||||||
|
wear_critical = lib.mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 50;
|
||||||
|
description = "Wear critical threshold (%)";
|
||||||
|
};
|
||||||
|
|
||||||
|
spare_warning = lib.mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 50;
|
||||||
|
description = "Available spare warning threshold (%)";
|
||||||
|
};
|
||||||
|
|
||||||
|
error_threshold = lib.mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 100;
|
||||||
|
description = "Error count warning threshold";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
description = "Threshold configuration for NVMe monitoring";
|
||||||
|
};
|
||||||
|
|
||||||
|
email = lib.mkOption {
|
||||||
|
type = types.nullOr (
|
||||||
|
types.submodule {
|
||||||
|
freeformType = settingsFormat.type;
|
||||||
|
options = {
|
||||||
|
smtp_server = lib.mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "smtp.gmail.com";
|
||||||
|
description = "SMTP server address";
|
||||||
|
example = "mail.example.com";
|
||||||
|
};
|
||||||
|
|
||||||
|
smtp_port = lib.mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 587;
|
||||||
|
description = "SMTP server port";
|
||||||
|
};
|
||||||
|
|
||||||
|
smtp_username = lib.mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "SMTP username";
|
||||||
|
example = "your-email@gmail.com";
|
||||||
|
};
|
||||||
|
|
||||||
|
smtp_password_file = lib.mkOption {
|
||||||
|
type = types.path;
|
||||||
|
description = "File containing SMTP password";
|
||||||
|
example = "/run/secrets/smtp-password";
|
||||||
|
};
|
||||||
|
|
||||||
|
from = lib.mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Sender email address";
|
||||||
|
example = "nvme-monitor@example.com";
|
||||||
|
};
|
||||||
|
|
||||||
|
to = lib.mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Recipient email address";
|
||||||
|
example = "admin@example.com";
|
||||||
|
};
|
||||||
|
|
||||||
|
use_tls = lib.mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Use TLS for SMTP connection";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = null;
|
||||||
|
description = "Email notification configuration";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
description = ''
|
||||||
|
Configuration for nvme-rs in TOML format.
|
||||||
|
See the config.toml example for all available options.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services.nvme-rs.settings = opt.settings.default;
|
||||||
|
|
||||||
|
systemd.services.nvme-rs = {
|
||||||
|
description = "NVMe health monitoring service";
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|
||||||
|
serviceConfig =
|
||||||
|
let
|
||||||
|
settingsWithoutNull =
|
||||||
|
if cfg.settings.email == null then lib.removeAttrs cfg.settings [ "email" ] else cfg.settings;
|
||||||
|
configFile = settingsFormat.generate "nvme-rs.toml" settingsWithoutNull;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
ExecStart = lib.escapeShellArgs [
|
||||||
|
"${lib.getExe cfg.package}"
|
||||||
|
"daemon"
|
||||||
|
"--config"
|
||||||
|
"${configFile}"
|
||||||
|
];
|
||||||
|
|
||||||
|
DynamicUser = true;
|
||||||
|
SupplementaryGroups = [ "disk" ];
|
||||||
|
CapabilityBoundingSet = [ "CAP_SYS_ADMIN" ];
|
||||||
|
AmbientCapabilities = [ "CAP_SYS_ADMIN" ];
|
||||||
|
LimitCORE = 0;
|
||||||
|
LimitNOFILE = 65535;
|
||||||
|
LockPersonality = true;
|
||||||
|
MemorySwapMax = 0;
|
||||||
|
MemoryZSwapMax = 0;
|
||||||
|
PrivateTmp = true;
|
||||||
|
ProcSubset = "pid";
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
ProtectHome = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectKernelLogs = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
ProtectProc = "invisible";
|
||||||
|
ProtectSystem = "strict";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = "10s";
|
||||||
|
RestrictAddressFamilies = [
|
||||||
|
"AF_INET"
|
||||||
|
"AF_INET6"
|
||||||
|
"AF_UNIX"
|
||||||
|
];
|
||||||
|
RestrictNamespaces = true;
|
||||||
|
RestrictRealtime = true;
|
||||||
|
SystemCallArchitectures = "native";
|
||||||
|
SystemCallFilter = [
|
||||||
|
"@system-service"
|
||||||
|
"@resources"
|
||||||
|
"~@privileged"
|
||||||
|
];
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
UMask = "0077";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = [ cfg.package ];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1072,6 +1072,7 @@ in
|
|||||||
ntpd = runTest ./ntpd.nix;
|
ntpd = runTest ./ntpd.nix;
|
||||||
ntpd-rs = runTest ./ntpd-rs.nix;
|
ntpd-rs = runTest ./ntpd-rs.nix;
|
||||||
nvidia-container-toolkit = runTest ./nvidia-container-toolkit.nix;
|
nvidia-container-toolkit = runTest ./nvidia-container-toolkit.nix;
|
||||||
|
nvme-rs = runTest ./nvme-rs.nix;
|
||||||
nvmetcfg = runTest ./nvmetcfg.nix;
|
nvmetcfg = runTest ./nvmetcfg.nix;
|
||||||
nyxt = runTest ./nyxt.nix;
|
nyxt = runTest ./nyxt.nix;
|
||||||
nzbget = runTest ./nzbget.nix;
|
nzbget = runTest ./nzbget.nix;
|
||||||
|
|||||||
157
nixos/tests/nvme-rs.nix
Normal file
157
nixos/tests/nvme-rs.nix
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
{ lib, pkgs, ... }:
|
||||||
|
{
|
||||||
|
name = "nvme-rs";
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
maintainers = with lib.maintainers; [ liberodark ];
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes = {
|
||||||
|
monitor =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
|
virtualisation = {
|
||||||
|
emptyDiskImages = [
|
||||||
|
512
|
||||||
|
512
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
nvme-rs
|
||||||
|
jq
|
||||||
|
];
|
||||||
|
|
||||||
|
services.nvme-rs = {
|
||||||
|
enable = true;
|
||||||
|
package = pkgs.nvme-rs;
|
||||||
|
settings = {
|
||||||
|
check_interval_secs = 60;
|
||||||
|
|
||||||
|
thresholds = {
|
||||||
|
temp_warning = 50;
|
||||||
|
temp_critical = 60;
|
||||||
|
wear_warning = 15;
|
||||||
|
wear_critical = 40;
|
||||||
|
spare_warning = 60;
|
||||||
|
error_threshold = 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
email = {
|
||||||
|
smtp_server = "mail";
|
||||||
|
smtp_port = 25;
|
||||||
|
smtp_username = "nvme-monitor@example.com";
|
||||||
|
smtp_password_file = "/run/secrets/smtp-password";
|
||||||
|
from = "NVMe Monitor <nvme-monitor@example.com>";
|
||||||
|
to = "admin@example.com";
|
||||||
|
use_tls = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"f /run/secrets/smtp-password 0600 root root - testpassword"
|
||||||
|
];
|
||||||
|
|
||||||
|
networking.firewall.enable = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
mail =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
|
services.postfix = {
|
||||||
|
enable = true;
|
||||||
|
hostname = "mail";
|
||||||
|
domain = "example.com";
|
||||||
|
|
||||||
|
networks = [ "0.0.0.0/0" ];
|
||||||
|
relayDomains = [ "example.com" ];
|
||||||
|
localRecipients = [ "admin" ];
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
main = {
|
||||||
|
inet_interfaces = "all";
|
||||||
|
inet_protocols = "ipv4";
|
||||||
|
smtpd_recipient_restrictions = "permit_mynetworks";
|
||||||
|
smtpd_relay_restrictions = "permit_mynetworks";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users.admin = {
|
||||||
|
isNormalUser = true;
|
||||||
|
home = "/home/admin";
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall = {
|
||||||
|
allowedTCPPorts = [ 25 ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
client =
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
|
virtualisation = {
|
||||||
|
emptyDiskImages = [ 256 ];
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
nvme-rs
|
||||||
|
jq
|
||||||
|
];
|
||||||
|
|
||||||
|
environment.etc."nvme-rs/config.toml".text = ''
|
||||||
|
check_interval_secs = 3600
|
||||||
|
|
||||||
|
[thresholds]
|
||||||
|
temp_warning = 55
|
||||||
|
temp_critical = 65
|
||||||
|
wear_warning = 20
|
||||||
|
wear_critical = 50
|
||||||
|
spare_warning = 50
|
||||||
|
error_threshold = 5000
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript =
|
||||||
|
{ nodes, ... }:
|
||||||
|
''
|
||||||
|
import json
|
||||||
|
|
||||||
|
start_all()
|
||||||
|
|
||||||
|
for machine in [monitor, mail, client]:
|
||||||
|
machine.wait_for_unit("multi-user.target")
|
||||||
|
|
||||||
|
mail.wait_for_unit("postfix.service")
|
||||||
|
mail.wait_for_open_port(25)
|
||||||
|
|
||||||
|
client.succeed("nvme-rs check || true")
|
||||||
|
client.succeed("nvme-rs check --config /etc/nvme-rs/config.toml || true")
|
||||||
|
|
||||||
|
output = client.succeed("nvme-rs check --format json || echo '[]'")
|
||||||
|
data = json.loads(output)
|
||||||
|
assert isinstance(data, list), "JSON output should be a list"
|
||||||
|
|
||||||
|
monitor.wait_for_unit("nvme-rs.service")
|
||||||
|
monitor.succeed("systemctl is-active nvme-rs.service")
|
||||||
|
|
||||||
|
config_path = monitor.succeed(
|
||||||
|
"systemctl status nvme-rs | grep -oE '/nix/store[^ ]*nvme-rs.toml' | head -1"
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
if config_path:
|
||||||
|
monitor.succeed(f"grep 'check_interval_secs = 60' {config_path}")
|
||||||
|
monitor.succeed(f"grep 'temp_warning = 50' {config_path}")
|
||||||
|
monitor.succeed(f"grep 'smtp_server = \"mail\"' {config_path}")
|
||||||
|
|
||||||
|
logs = monitor.succeed("journalctl -u nvme-rs.service -n 20 --no-pager")
|
||||||
|
assert "Starting NVMe monitor daemon" in logs or "Check interval" in logs
|
||||||
|
|
||||||
|
monitor.succeed("test -f /run/secrets/smtp-password")
|
||||||
|
|
||||||
|
monitor.succeed("nc -zv mail 25")
|
||||||
|
monitor.fail("nvme-rs daemon --config /nonexistent.toml 2>&1 | grep -E 'Failed to read'")
|
||||||
|
'';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user