1{ config, lib, pkgs, ... }: 2 3# TODO: This is not secure, have a look at the file docs/security.txt inside 4# the project sources. 5with lib; 6 7let 8 cfg = config.power.ups; 9in 10 11let 12 upsOptions = {name, config, ...}: 13 { 14 options = { 15 # This can be infered from the UPS model by looking at 16 # /nix/store/nut/share/driver.list 17 driver = mkOption { 18 type = types.str; 19 description = '' 20 Specify the program to run to talk to this UPS. apcsmart, 21 bestups, and sec are some examples. 22 ''; 23 }; 24 25 port = mkOption { 26 type = types.str; 27 description = '' 28 The serial port to which your UPS is connected. /dev/ttyS0 is 29 usually the first port on Linux boxes, for example. 30 ''; 31 }; 32 33 shutdownOrder = mkOption { 34 default = 0; 35 type = types.int; 36 description = '' 37 When you have multiple UPSes on your system, you usually need to 38 turn them off in a certain order. upsdrvctl shuts down all the 39 0s, then the 1s, 2s, and so on. To exclude a UPS from the 40 shutdown sequence, set this to -1. 41 ''; 42 }; 43 44 maxStartDelay = mkOption { 45 default = null; 46 type = types.uniq (types.nullOr types.int); 47 description = '' 48 This can be set as a global variable above your first UPS 49 definition and it can also be set in a UPS section. This value 50 controls how long upsdrvctl will wait for the driver to finish 51 starting. This keeps your system from getting stuck due to a 52 broken driver or UPS. 53 ''; 54 }; 55 56 description = mkOption { 57 default = ""; 58 type = types.string; 59 description = '' 60 Description of the UPS. 61 ''; 62 }; 63 64 directives = mkOption { 65 default = []; 66 type = types.listOf types.str; 67 description = '' 68 List of configuration directives for this UPS. 69 ''; 70 }; 71 72 summary = mkOption { 73 default = ""; 74 type = types.string; 75 description = '' 76 Lines which would be added inside ups.conf for handling this UPS. 77 ''; 78 }; 79 80 }; 81 82 config = { 83 directives = mkHeader ([ 84 "driver = ${config.driver}" 85 "port = ${config.port}" 86 ''desc = "${config.description}"'' 87 "sdorder = ${toString config.shutdownOrder}" 88 ] ++ (optional (config.maxStartDelay != null) 89 "maxstartdelay = ${toString config.maxStartDelay}") 90 ); 91 92 summary = 93 concatStringsSep "\n " 94 (["[${name}]"] ++ config.directives); 95 }; 96 }; 97 98in 99 100 101{ 102 options = { 103 # powerManagement.powerDownCommands 104 105 power.ups = { 106 enable = mkOption { 107 default = false; 108 type = with types; bool; 109 description = '' 110 Enables support for Power Devices, such as Uninterruptible Power 111 Supplies, Power Distribution Units and Solar Controllers. 112 ''; 113 }; 114 115 # This option is not used yet. 116 mode = mkOption { 117 default = "standalone"; 118 type = types.str; 119 description = '' 120 The MODE determines which part of the NUT is to be started, and 121 which configuration files must be modified. 122 123 The values of MODE can be: 124 125 - none: NUT is not configured, or use the Integrated Power 126 Management, or use some external system to startup NUT 127 components. So nothing is to be started. 128 129 - standalone: This mode address a local only configuration, with 1 130 UPS protecting the local system. This implies to start the 3 NUT 131 layers (driver, upsd and upsmon) and the matching configuration 132 files. This mode can also address UPS redundancy. 133 134 - netserver: same as for the standalone configuration, but also 135 need some more ACLs and possibly a specific LISTEN directive in 136 upsd.conf. Since this MODE is opened to the network, a special 137 care should be applied to security concerns. 138 139 - netclient: this mode only requires upsmon. 140 ''; 141 }; 142 143 schedulerRules = mkOption { 144 example = "/etc/nixos/upssched.conf"; 145 type = types.str; 146 description = '' 147 File which contains the rules to handle UPS events. 148 ''; 149 }; 150 151 152 maxStartDelay = mkOption { 153 default = 45; 154 type = types.int; 155 description = '' 156 This can be set as a global variable above your first UPS 157 definition and it can also be set in a UPS section. This value 158 controls how long upsdrvctl will wait for the driver to finish 159 starting. This keeps your system from getting stuck due to a 160 broken driver or UPS. 161 ''; 162 }; 163 164 ups = mkOption { 165 default = {}; 166 # see nut/etc/ups.conf.sample 167 description = '' 168 This is where you configure all the UPSes that this system will be 169 monitoring directly. These are usually attached to serial ports, 170 but USB devices are also supported. 171 ''; 172 type = types.attrsOf types.optionSet; 173 options = [ upsOptions ]; 174 }; 175 176 }; 177 }; 178 179 config = mkIf cfg.enable { 180 181 environment.systemPackages = [ pkgs.nut ]; 182 183 systemd.services.upsmon = { 184 description = "Uninterruptible Power Supplies (Monitor)"; 185 wantedBy = [ "ip-up.target" ]; 186 serviceConfig.Type = "forking"; 187 script = "${pkgs.nut}/sbin/upsmon"; 188 environment.NUT_CONFPATH = "/etc/nut/"; 189 environment.NUT_STATEPATH = "/var/lib/nut/"; 190 }; 191 192 systemd.services.upsd = { 193 description = "Uninterruptible Power Supplies (Daemon)"; 194 wantedBy = [ "multi-user.target" ]; 195 after = [ "network-interfaces.target" "upsmon.service" ]; 196 serviceConfig.Type = "forking"; 197 # TODO: replace 'root' by another username. 198 script = "${pkgs.nut}/sbin/upsd -u root"; 199 environment.NUT_CONFPATH = "/etc/nut/"; 200 environment.NUT_STATEPATH = "/var/lib/nut/"; 201 }; 202 203 systemd.services.upsdrv = { 204 description = "Uninterruptible Power Supplies (Register all UPS)"; 205 wantedBy = [ "multi-user.target" ]; 206 after = [ "upsd.service" ]; 207 # TODO: replace 'root' by another username. 208 script = ''${pkgs.nut}/bin/upsdrvctl -u root start''; 209 serviceConfig = { 210 Type = "oneshot"; 211 RemainAfterExit = true; 212 }; 213 environment.NUT_CONFPATH = "/etc/nut/"; 214 environment.NUT_STATEPATH = "/var/lib/nut/"; 215 }; 216 217 environment.etc = [ 218 { source = pkgs.writeText "nut.conf" 219 '' 220 MODE = ${cfg.mode} 221 ''; 222 target = "nut/nut.conf"; 223 } 224 { source = pkgs.writeText "ups.conf" 225 '' 226 maxstartdelay = ${toString cfg.maxStartDelay} 227 228 ${flip concatStringsSep (flip map (attrValues cfg.ups) (ups: ups.summary)) " 229 230 "} 231 ''; 232 target = "nut/ups.conf"; 233 } 234 { source = cfg.schedulerRules; 235 target = "nut/upssched.conf"; 236 } 237 # These file are containing private informations and thus should not 238 # be stored inside the Nix store. 239 /* 240 { source = ; 241 target = "nut/upsd.conf"; 242 } 243 { source = ; 244 target = "nut/upsd.users"; 245 } 246 { source = ; 247 target = "nut/upsmon.conf; 248 } 249 */ 250 ]; 251 252 power.ups.schedulerRules = mkDefault "${pkgs.nut}/etc/upssched.conf.sample"; 253 254 system.activationScripts.upsSetup = stringAfter [ "users" "groups" ] 255 '' 256 # Used to store pid files of drivers. 257 mkdir -p /var/state/ups 258 ''; 259 260 261/* 262 users.extraUsers = [ 263 { name = "nut"; 264 uid = 84; 265 home = "/var/lib/nut"; 266 createHome = true; 267 group = "nut"; 268 description = "UPnP A/V Media Server user"; 269 } 270 ]; 271 272 users.extraGroups = [ 273 { name = "nut"; 274 gid = 84; 275 } 276 ]; 277*/ 278 279 }; 280}