0
0
Fork 0
mirror of https://github.com/NixOS/nixpkgs.git synced 2025-07-13 21:50:33 +03:00

nixos/power.ups: remove with lib;

This commit is contained in:
Felix Buehler 2024-08-27 20:43:33 +02:00
parent 8cf91e2c5b
commit d27bffefab

View file

@ -1,9 +1,6 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
# TODO: This is not secure, have a look at the file docs/security.txt inside # TODO: This is not secure, have a look at the file docs/security.txt inside
# the project sources. # the project sources.
with lib;
let let
cfg = config.power.ups; cfg = config.power.ups;
defaultPort = 3493; defaultPort = 3493;
@ -31,24 +28,24 @@ let
normalizedValue = normalizedValue =
lib.mapAttrs (key: val: lib.mapAttrs (key: val:
if lib.isList val if lib.isList val
then forEach val (elem: if lib.isList elem then elem else [elem]) then lib.forEach val (elem: if lib.isList elem then elem else [elem])
else else
if val == null if val == null
then [] then []
else [[val]] else [[val]]
) value; ) value;
mkValueString = concatMapStringsSep " " (v: mkValueString = lib.concatMapStringsSep " " (v:
let str = generators.mkValueStringDefault {} v; let str = lib.generators.mkValueStringDefault {} v;
in in
# Quote the value if it has spaces and isn't already quoted. # Quote the value if it has spaces and isn't already quoted.
if (hasInfix " " str) && !(hasPrefix "\"" str && hasSuffix "\"" str) if (lib.hasInfix " " str) && !(lib.hasPrefix "\"" str && lib.hasSuffix "\"" str)
then "\"${str}\"" then "\"${str}\""
else str else str
); );
in pkgs.writeText name (lib.generators.toKeyValue { in pkgs.writeText name (lib.generators.toKeyValue {
mkKeyValue = generators.mkKeyValueDefault { inherit mkValueString; } " "; mkKeyValue = lib.generators.mkKeyValueDefault { inherit mkValueString; } " ";
listsAsDuplicateKeys = true; listsAsDuplicateKeys = true;
} normalizedValue); } normalizedValue);
@ -57,7 +54,7 @@ let
installSecrets = source: target: secrets: installSecrets = source: target: secrets:
pkgs.writeShellScript "installSecrets.sh" '' pkgs.writeShellScript "installSecrets.sh" ''
install -m0600 -D ${source} "${target}" install -m0600 -D ${source} "${target}"
${concatLines (forEach secrets (name: '' ${lib.concatLines (lib.forEach secrets (name: ''
${pkgs.replace-secret}/bin/replace-secret \ ${pkgs.replace-secret}/bin/replace-secret \
'@${name}@' \ '@${name}@' \
"$CREDENTIALS_DIRECTORY/${name}" \ "$CREDENTIALS_DIRECTORY/${name}" \
@ -71,16 +68,16 @@ let
upsdUsers = pkgs.writeText "upsd.users" (let upsdUsers = pkgs.writeText "upsd.users" (let
# This looks like INI, but it's not quite because the # This looks like INI, but it's not quite because the
# 'upsmon' option lacks a '='. See: man upsd.users # 'upsmon' option lacks a '='. See: man upsd.users
userConfig = name: user: concatStringsSep "\n " (concatLists [ userConfig = name: user: lib.concatStringsSep "\n " (lib.concatLists [
[ [
"[${name}]" "[${name}]"
"password = \"@upsdusers_password_${name}@\"" "password = \"@upsdusers_password_${name}@\""
] ]
(optional (user.upsmon != null) "upsmon ${user.upsmon}") (lib.optional (user.upsmon != null) "upsmon ${user.upsmon}")
(forEach user.actions (action: "actions = ${action}")) (lib.forEach user.actions (action: "actions = ${action}"))
(forEach user.instcmds (instcmd: "instcmds = ${instcmd}")) (lib.forEach user.instcmds (instcmd: "instcmds = ${instcmd}"))
]); ]);
in concatStringsSep "\n\n" (mapAttrsToList userConfig cfg.users)); in lib.concatStringsSep "\n\n" (lib.mapAttrsToList userConfig cfg.users));
upsOptions = {name, config, ...}: upsOptions = {name, config, ...}:
@ -88,25 +85,25 @@ let
options = { options = {
# This can be inferred from the UPS model by looking at # This can be inferred from the UPS model by looking at
# /nix/store/nut/share/driver.list # /nix/store/nut/share/driver.list
driver = mkOption { driver = lib.mkOption {
type = types.str; type = lib.types.str;
description = '' description = ''
Specify the program to run to talk to this UPS. apcsmart, Specify the program to run to talk to this UPS. apcsmart,
bestups, and sec are some examples. bestups, and sec are some examples.
''; '';
}; };
port = mkOption { port = lib.mkOption {
type = types.str; type = lib.types.str;
description = '' description = ''
The serial port to which your UPS is connected. /dev/ttyS0 is The serial port to which your UPS is connected. /dev/ttyS0 is
usually the first port on Linux boxes, for example. usually the first port on Linux boxes, for example.
''; '';
}; };
shutdownOrder = mkOption { shutdownOrder = lib.mkOption {
default = 0; default = 0;
type = types.int; type = lib.types.int;
description = '' description = ''
When you have multiple UPSes on your system, you usually need to When you have multiple UPSes on your system, you usually need to
turn them off in a certain order. upsdrvctl shuts down all the turn them off in a certain order. upsdrvctl shuts down all the
@ -115,9 +112,9 @@ let
''; '';
}; };
maxStartDelay = mkOption { maxStartDelay = lib.mkOption {
default = null; default = null;
type = types.uniq (types.nullOr types.int); type = lib.types.uniq (lib.types.nullOr lib.types.int);
description = '' description = ''
This can be set as a global variable above your first UPS This can be set as a global variable above your first UPS
definition and it can also be set in a UPS section. This value definition and it can also be set in a UPS section. This value
@ -127,25 +124,25 @@ let
''; '';
}; };
description = mkOption { description = lib.mkOption {
default = ""; default = "";
type = types.str; type = lib.types.str;
description = '' description = ''
Description of the UPS. Description of the UPS.
''; '';
}; };
directives = mkOption { directives = lib.mkOption {
default = []; default = [];
type = types.listOf types.str; type = lib.types.listOf lib.types.str;
description = '' description = ''
List of configuration directives for this UPS. List of configuration directives for this UPS.
''; '';
}; };
summary = mkOption { summary = lib.mkOption {
default = ""; default = "";
type = types.lines; type = lib.types.lines;
description = '' description = ''
Lines which would be added inside ups.conf for handling this UPS. Lines which would be added inside ups.conf for handling this UPS.
''; '';
@ -154,33 +151,33 @@ let
}; };
config = { config = {
directives = mkOrder 10 ([ directives = lib.mkOrder 10 ([
"driver = ${config.driver}" "driver = ${config.driver}"
"port = ${config.port}" "port = ${config.port}"
''desc = "${config.description}"'' ''desc = "${config.description}"''
"sdorder = ${toString config.shutdownOrder}" "sdorder = ${toString config.shutdownOrder}"
] ++ (optional (config.maxStartDelay != null) ] ++ (lib.optional (config.maxStartDelay != null)
"maxstartdelay = ${toString config.maxStartDelay}") "maxstartdelay = ${toString config.maxStartDelay}")
); );
summary = summary =
concatStringsSep "\n " lib.concatStringsSep "\n "
(["[${name}]"] ++ config.directives); (["[${name}]"] ++ config.directives);
}; };
}; };
listenOptions = { listenOptions = {
options = { options = {
address = mkOption { address = lib.mkOption {
type = types.str; type = lib.types.str;
description = '' description = ''
Address of the interface for `upsd` to listen on. Address of the interface for `upsd` to listen on.
See `man upsd.conf` for details. See `man upsd.conf` for details.
''; '';
}; };
port = mkOption { port = lib.mkOption {
type = types.port; type = lib.types.port;
default = defaultPort; default = defaultPort;
description = '' description = ''
TCP port for `upsd` to listen on. TCP port for `upsd` to listen on.
@ -192,14 +189,14 @@ let
upsdOptions = { upsdOptions = {
options = { options = {
enable = mkOption { enable = lib.mkOption {
type = types.bool; type = lib.types.bool;
defaultText = literalMD "`true` if `mode` is one of `standalone`, `netserver`"; defaultText = lib.literalMD "`true` if `mode` is one of `standalone`, `netserver`";
description = "Whether to enable `upsd`."; description = "Whether to enable `upsd`.";
}; };
listen = mkOption { listen = lib.mkOption {
type = with types; listOf (submodule listenOptions); type = with lib.types; listOf (submodule listenOptions);
default = []; default = [];
example = [ example = [
{ {
@ -216,8 +213,8 @@ let
''; '';
}; };
extraConfig = mkOption { extraConfig = lib.mkOption {
type = types.lines; type = lib.types.lines;
default = ""; default = "";
description = '' description = ''
Additional lines to add to `upsd.conf`. Additional lines to add to `upsd.conf`.
@ -226,15 +223,15 @@ let
}; };
config = { config = {
enable = mkDefault (elem cfg.mode [ "standalone" "netserver" ]); enable = lib.mkDefault (lib.elem cfg.mode [ "standalone" "netserver" ]);
}; };
}; };
monitorOptions = { name, config, ... }: { monitorOptions = { name, config, ... }: {
options = { options = {
system = mkOption { system = lib.mkOption {
type = types.str; type = lib.types.str;
default = name; default = name;
description = '' description = ''
Identifier of the UPS to monitor, in this form: `<upsname>[@<hostname>[:<port>]]` Identifier of the UPS to monitor, in this form: `<upsname>[@<hostname>[:<port>]]`
@ -242,8 +239,8 @@ let
''; '';
}; };
powerValue = mkOption { powerValue = lib.mkOption {
type = types.int; type = lib.types.int;
default = 1; default = 1;
description = '' description = ''
Number of power supplies that the UPS feeds on this system. Number of power supplies that the UPS feeds on this system.
@ -251,17 +248,17 @@ let
''; '';
}; };
user = mkOption { user = lib.mkOption {
type = types.str; type = lib.types.str;
description = '' description = ''
Username from `upsd.users` for accessing this UPS. Username from `upsd.users` for accessing this UPS.
See `upsmon.conf` for details. See `upsmon.conf` for details.
''; '';
}; };
passwordFile = mkOption { passwordFile = lib.mkOption {
type = types.str; type = lib.types.str;
defaultText = literalMD "power.ups.users.\${user}.passwordFile"; defaultText = lib.literalMD "power.ups.users.\${user}.passwordFile";
description = '' description = ''
The full path to a file containing the password from The full path to a file containing the password from
`upsd.users` for accessing this UPS. The password file `upsd.users` for accessing this UPS. The password file
@ -270,8 +267,8 @@ let
''; '';
}; };
type = mkOption { type = lib.mkOption {
type = types.str; type = lib.types.str;
default = "master"; default = "master";
description = '' description = ''
The relationship with `upsd`. The relationship with `upsd`.
@ -281,30 +278,30 @@ let
}; };
config = { config = {
passwordFile = mkDefault cfg.users.${config.user}.passwordFile; passwordFile = lib.mkDefault cfg.users.${config.user}.passwordFile;
}; };
}; };
upsmonOptions = { upsmonOptions = {
options = { options = {
enable = mkOption { enable = lib.mkOption {
type = types.bool; type = lib.types.bool;
defaultText = literalMD "`true` if `mode` is one of `standalone`, `netserver`, `netclient`"; defaultText = lib.literalMD "`true` if `mode` is one of `standalone`, `netserver`, `netclient`";
description = "Whether to enable `upsmon`."; description = "Whether to enable `upsmon`.";
}; };
monitor = mkOption { monitor = lib.mkOption {
type = with types; attrsOf (submodule monitorOptions); type = with lib.types; attrsOf (submodule monitorOptions);
default = {}; default = {};
description = '' description = ''
Set of UPS to monitor. See `man upsmon.conf` for details. Set of UPS to monitor. See `man upsmon.conf` for details.
''; '';
}; };
settings = mkOption { settings = lib.mkOption {
type = nutFormat.type; type = nutFormat.type;
default = {}; default = {};
defaultText = literalMD '' defaultText = lib.literalMD ''
{ {
MINSUPPLIES = 1; MINSUPPLIES = 1;
RUN_AS_USER = "root"; RUN_AS_USER = "root";
@ -313,7 +310,7 @@ let
} }
''; '';
description = "Additional settings to add to `upsmon.conf`."; description = "Additional settings to add to `upsmon.conf`.";
example = literalMD '' example = lib.literalMD ''
{ {
MINSUPPLIES = 2; MINSUPPLIES = 2;
NOTIFYFLAG = [ NOTIFYFLAG = [
@ -326,29 +323,29 @@ let
}; };
config = { config = {
enable = mkDefault (elem cfg.mode [ "standalone" "netserver" "netclient" ]); enable = lib.mkDefault (lib.elem cfg.mode [ "standalone" "netserver" "netclient" ]);
settings = { settings = {
RUN_AS_USER = "root"; # TODO: replace 'root' by another username. RUN_AS_USER = "root"; # TODO: replace 'root' by another username.
MINSUPPLIES = mkDefault 1; MINSUPPLIES = lib.mkDefault 1;
NOTIFYCMD = mkDefault "${pkgs.nut}/bin/upssched"; NOTIFYCMD = lib.mkDefault "${pkgs.nut}/bin/upssched";
SHUTDOWNCMD = mkDefault "${pkgs.systemd}/bin/shutdown now"; SHUTDOWNCMD = lib.mkDefault "${pkgs.systemd}/bin/shutdown now";
MONITOR = flip mapAttrsToList cfg.upsmon.monitor (name: monitor: with monitor; [ system powerValue user "\"@upsmon_password_${name}@\"" type ]); MONITOR = lib.flip lib.mapAttrsToList cfg.upsmon.monitor (name: monitor: with monitor; [ system powerValue user "\"@upsmon_password_${name}@\"" type ]);
}; };
}; };
}; };
userOptions = { userOptions = {
options = { options = {
passwordFile = mkOption { passwordFile = lib.mkOption {
type = types.str; type = lib.types.str;
description = '' description = ''
The full path to a file that contains the user's (clear text) The full path to a file that contains the user's (clear text)
password. The password file is read on service start. password. The password file is read on service start.
''; '';
}; };
actions = mkOption { actions = lib.mkOption {
type = with types; listOf str; type = with lib.types; listOf str;
default = []; default = [];
description = '' description = ''
Allow the user to do certain things with upsd. Allow the user to do certain things with upsd.
@ -356,8 +353,8 @@ let
''; '';
}; };
instcmds = mkOption { instcmds = lib.mkOption {
type = with types; listOf str; type = with lib.types; listOf str;
default = []; default = [];
description = '' description = ''
Let the user initiate specific instant commands. Use "ALL" to grant all commands automatically. For the full list of what your UPS supports, use "upscmd -l". Let the user initiate specific instant commands. Use "ALL" to grant all commands automatically. For the full list of what your UPS supports, use "upscmd -l".
@ -365,8 +362,8 @@ let
''; '';
}; };
upsmon = mkOption { upsmon = lib.mkOption {
type = with types; nullOr (enum [ "primary" "secondary" ]); type = with lib.types; nullOr (enum [ "primary" "secondary" ]);
default = null; default = null;
description = '' description = ''
Add the necessary actions for a upsmon process to work. Add the necessary actions for a upsmon process to work.
@ -384,14 +381,14 @@ in
# powerManagement.powerDownCommands # powerManagement.powerDownCommands
power.ups = { power.ups = {
enable = mkEnableOption '' enable = lib.mkEnableOption ''
support for Power Devices, such as Uninterruptible Power support for Power Devices, such as Uninterruptible Power
Supplies, Power Distribution Units and Solar Controllers Supplies, Power Distribution Units and Solar Controllers
''; '';
mode = mkOption { mode = lib.mkOption {
default = "standalone"; default = "standalone";
type = types.enum [ "none" "standalone" "netserver" "netclient" ]; type = lib.types.enum [ "none" "standalone" "netserver" "netclient" ];
description = '' description = ''
The MODE determines which part of the NUT is to be started, and The MODE determines which part of the NUT is to be started, and
which configuration files must be modified. which configuration files must be modified.
@ -416,25 +413,25 @@ in
''; '';
}; };
schedulerRules = mkOption { schedulerRules = lib.mkOption {
example = "/etc/nixos/upssched.conf"; example = "/etc/nixos/upssched.conf";
type = types.str; type = lib.types.str;
description = '' description = ''
File which contains the rules to handle UPS events. File which contains the rules to handle UPS events.
''; '';
}; };
openFirewall = mkOption { openFirewall = lib.mkOption {
type = types.bool; type = lib.types.bool;
default = false; default = false;
description = '' description = ''
Open ports in the firewall for `upsd`. Open ports in the firewall for `upsd`.
''; '';
}; };
maxStartDelay = mkOption { maxStartDelay = lib.mkOption {
default = 45; default = 45;
type = types.int; type = lib.types.int;
description = '' description = ''
This can be set as a global variable above your first UPS This can be set as a global variable above your first UPS
definition and it can also be set in a UPS section. This value definition and it can also be set in a UPS section. This value
@ -444,23 +441,23 @@ in
''; '';
}; };
upsmon = mkOption { upsmon = lib.mkOption {
default = {}; default = {};
description = '' description = ''
Options for the `upsmon.conf` configuration file. Options for the `upsmon.conf` configuration file.
''; '';
type = types.submodule upsmonOptions; type = lib.types.submodule upsmonOptions;
}; };
upsd = mkOption { upsd = lib.mkOption {
default = {}; default = {};
description = '' description = ''
Options for the `upsd.conf` configuration file. Options for the `upsd.conf` configuration file.
''; '';
type = types.submodule upsdOptions; type = lib.types.submodule upsdOptions;
}; };
ups = mkOption { ups = lib.mkOption {
default = {}; default = {};
# see nut/etc/ups.conf.sample # see nut/etc/ups.conf.sample
description = '' description = ''
@ -468,27 +465,27 @@ in
monitoring directly. These are usually attached to serial ports, monitoring directly. These are usually attached to serial ports,
but USB devices are also supported. but USB devices are also supported.
''; '';
type = with types; attrsOf (submodule upsOptions); type = with lib.types; attrsOf (submodule upsOptions);
}; };
users = mkOption { users = lib.mkOption {
default = {}; default = {};
description = '' description = ''
Users that can access upsd. See `man upsd.users`. Users that can access upsd. See `man upsd.users`.
''; '';
type = with types; attrsOf (submodule userOptions); type = with lib.types; attrsOf (submodule userOptions);
}; };
}; };
}; };
config = mkIf cfg.enable { config = lib.mkIf cfg.enable {
assertions = [ assertions = [
(let (let
totalPowerValue = foldl' add 0 (map (monitor: monitor.powerValue) (attrValues cfg.upsmon.monitor)); totalPowerValue = lib.foldl' lib.add 0 (map (monitor: monitor.powerValue) (lib.attrValues cfg.upsmon.monitor));
minSupplies = cfg.upsmon.settings.MINSUPPLIES; minSupplies = cfg.upsmon.settings.MINSUPPLIES;
in mkIf cfg.upsmon.enable { in lib.mkIf cfg.upsmon.enable {
assertion = totalPowerValue >= minSupplies; assertion = totalPowerValue >= minSupplies;
message = '' message = ''
`power.ups.upsmon`: Total configured power value (${toString totalPowerValue}) must be at least MINSUPPLIES (${toString minSupplies}). `power.ups.upsmon`: Total configured power value (${toString totalPowerValue}) must be at least MINSUPPLIES (${toString minSupplies}).
@ -498,15 +495,15 @@ in
environment.systemPackages = [ pkgs.nut ]; environment.systemPackages = [ pkgs.nut ];
networking.firewall = mkIf cfg.openFirewall { networking.firewall = lib.mkIf cfg.openFirewall {
allowedTCPPorts = allowedTCPPorts =
if cfg.upsd.listen == [] if cfg.upsd.listen == []
then [ defaultPort ] then [ defaultPort ]
else unique (forEach cfg.upsd.listen (listen: listen.port)); else lib.unique (lib.forEach cfg.upsd.listen (listen: listen.port));
}; };
systemd.services.upsmon = let systemd.services.upsmon = let
secrets = mapAttrsToList (name: monitor: "upsmon_password_${name}") cfg.upsmon.monitor; secrets = lib.mapAttrsToList (name: monitor: "upsmon_password_${name}") cfg.upsmon.monitor;
createUpsmonConf = installSecrets upsmonConf "/run/nut/upsmon.conf" secrets; createUpsmonConf = installSecrets upsmonConf "/run/nut/upsmon.conf" secrets;
in { in {
enable = cfg.upsmon.enable; enable = cfg.upsmon.enable;
@ -518,14 +515,14 @@ in
ExecStartPre = "${createUpsmonConf}"; ExecStartPre = "${createUpsmonConf}";
ExecStart = "${pkgs.nut}/sbin/upsmon"; ExecStart = "${pkgs.nut}/sbin/upsmon";
ExecReload = "${pkgs.nut}/sbin/upsmon -c reload"; ExecReload = "${pkgs.nut}/sbin/upsmon -c reload";
LoadCredential = mapAttrsToList (name: monitor: "upsmon_password_${name}:${monitor.passwordFile}") cfg.upsmon.monitor; LoadCredential = lib.mapAttrsToList (name: monitor: "upsmon_password_${name}:${monitor.passwordFile}") cfg.upsmon.monitor;
}; };
environment.NUT_CONFPATH = "/etc/nut"; environment.NUT_CONFPATH = "/etc/nut";
environment.NUT_STATEPATH = "/var/lib/nut"; environment.NUT_STATEPATH = "/var/lib/nut";
}; };
systemd.services.upsd = let systemd.services.upsd = let
secrets = mapAttrsToList (name: user: "upsdusers_password_${name}") cfg.users; secrets = lib.mapAttrsToList (name: user: "upsdusers_password_${name}") cfg.users;
createUpsdUsers = installSecrets upsdUsers "/run/nut/upsd.users" secrets; createUpsdUsers = installSecrets upsdUsers "/run/nut/upsd.users" secrets;
in { in {
enable = cfg.upsd.enable; enable = cfg.upsd.enable;
@ -538,7 +535,7 @@ in
# TODO: replace 'root' by another username. # TODO: replace 'root' by another username.
ExecStart = "${pkgs.nut}/sbin/upsd -u root"; ExecStart = "${pkgs.nut}/sbin/upsd -u root";
ExecReload = "${pkgs.nut}/sbin/upsd -c reload"; ExecReload = "${pkgs.nut}/sbin/upsd -c reload";
LoadCredential = mapAttrsToList (name: user: "upsdusers_password_${name}:${user.passwordFile}") cfg.users; LoadCredential = lib.mapAttrsToList (name: user: "upsdusers_password_${name}:${user.passwordFile}") cfg.users;
}; };
environment.NUT_CONFPATH = "/etc/nut"; environment.NUT_CONFPATH = "/etc/nut";
environment.NUT_STATEPATH = "/var/lib/nut"; environment.NUT_STATEPATH = "/var/lib/nut";
@ -574,11 +571,11 @@ in
'' ''
maxstartdelay = ${toString cfg.maxStartDelay} maxstartdelay = ${toString cfg.maxStartDelay}
${concatStringsSep "\n\n" (forEach (attrValues cfg.ups) (ups: ups.summary))} ${lib.concatStringsSep "\n\n" (lib.forEach (lib.attrValues cfg.ups) (ups: ups.summary))}
''; '';
"nut/upsd.conf".source = pkgs.writeText "upsd.conf" "nut/upsd.conf".source = pkgs.writeText "upsd.conf"
'' ''
${concatStringsSep "\n" (forEach cfg.upsd.listen (listen: "LISTEN ${listen.address} ${toString listen.port}"))} ${lib.concatStringsSep "\n" (lib.forEach cfg.upsd.listen (listen: "LISTEN ${listen.address} ${toString listen.port}"))}
${cfg.upsd.extraConfig} ${cfg.upsd.extraConfig}
''; '';
"nut/upssched.conf".source = cfg.schedulerRules; "nut/upssched.conf".source = cfg.schedulerRules;
@ -586,7 +583,7 @@ in
"nut/upsmon.conf".source = "/run/nut/upsmon.conf"; "nut/upsmon.conf".source = "/run/nut/upsmon.conf";
}; };
power.ups.schedulerRules = mkDefault "${pkgs.nut}/etc/upssched.conf.sample"; power.ups.schedulerRules = lib.mkDefault "${pkgs.nut}/etc/upssched.conf.sample";
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d /var/state/ups -" "d /var/state/ups -"