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).
|
||||
|
||||
- [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}
|
||||
|
||||
<!-- 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/nix-daemon.nix
|
||||
./services/system/nscd.nix
|
||||
./services/system/nvme-rs.nix
|
||||
./services/system/saslauthd.nix
|
||||
./services/system/self-deploy.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-rs = runTest ./ntpd-rs.nix;
|
||||
nvidia-container-toolkit = runTest ./nvidia-container-toolkit.nix;
|
||||
nvme-rs = runTest ./nvme-rs.nix;
|
||||
nvmetcfg = runTest ./nvmetcfg.nix;
|
||||
nyxt = runTest ./nyxt.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