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 jobs.upsmon = {
184 description = "Uninterruptible Power Supplies (Monitor)";
185 startOn = "ip-up";
186 daemonType = "fork";
187 exec = ''${pkgs.nut}/sbin/upsmon'';
188 environment.NUT_CONFPATH = "/etc/nut/";
189 environment.NUT_STATEPATH = "/var/lib/nut/";
190 };
191
192 jobs.upsd = {
193 description = "Uninterruptible Power Supplies (Daemon)";
194 startOn = "started network-interfaces and started upsmon";
195 daemonType = "fork";
196 # TODO: replace 'root' by another username.
197 exec = ''${pkgs.nut}/sbin/upsd -u root'';
198 environment.NUT_CONFPATH = "/etc/nut/";
199 environment.NUT_STATEPATH = "/var/lib/nut/";
200 };
201
202 jobs.upsdrv = {
203 description = "Uninterruptible Power Supplies (Register all UPS)";
204 startOn = "started upsd";
205 # TODO: replace 'root' by another username.
206 exec = ''${pkgs.nut}/bin/upsdrvctl -u root start'';
207 task = true;
208 environment.NUT_CONFPATH = "/etc/nut/";
209 environment.NUT_STATEPATH = "/var/lib/nut/";
210 };
211
212 environment.etc = [
213 { source = pkgs.writeText "nut.conf"
214 ''
215 MODE = ${cfg.mode}
216 '';
217 target = "nut/nut.conf";
218 }
219 { source = pkgs.writeText "ups.conf"
220 ''
221 maxstartdelay = ${toString cfg.maxStartDelay}
222
223 ${flip concatStringsSep (flip map (attrValues cfg.ups) (ups: ups.summary)) "
224
225 "}
226 '';
227 target = "nut/ups.conf";
228 }
229 { source = cfg.schedulerRules;
230 target = "nut/upssched.conf";
231 }
232 # These file are containing private informations and thus should not
233 # be stored inside the Nix store.
234 /*
235 { source = ;
236 target = "nut/upsd.conf";
237 }
238 { source = ;
239 target = "nut/upsd.users";
240 }
241 { source = ;
242 target = "nut/upsmon.conf;
243 }
244 */
245 ];
246
247 power.ups.schedulerRules = mkDefault "${pkgs.nut}/etc/upssched.conf.sample";
248
249 system.activationScripts.upsSetup = stringAfter [ "users" "groups" ]
250 ''
251 # Used to store pid files of drivers.
252 mkdir -p /var/state/ups
253 '';
254
255
256/*
257 users.extraUsers = [
258 { name = "nut";
259 uid = 84;
260 home = "/var/lib/nut";
261 createHome = true;
262 group = "nut";
263 description = "UPnP A/V Media Server user";
264 }
265 ];
266
267 users.extraGroups = [
268 { name = "nut";
269 gid = 84;
270 }
271 ];
272*/
273
274 };
275}