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}