grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / monitoring / apcupsd.nix
blob09cf593f5d5e2aff78946398acaf45a45340421b
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
6   cfg = config.services.apcupsd;
8   configFile = pkgs.writeText "apcupsd.conf" ''
9     ## apcupsd.conf v1.1 ##
10     # apcupsd complains if the first line is not like above.
11     ${cfg.configText}
12     SCRIPTDIR ${toString scriptDir}
13   '';
15   # List of events from "man apccontrol"
16   eventList = [
17     "annoyme"
18     "battattach"
19     "battdetach"
20     "changeme"
21     "commfailure"
22     "commok"
23     "doreboot"
24     "doshutdown"
25     "emergency"
26     "failing"
27     "killpower"
28     "loadlimit"
29     "mainsback"
30     "onbattery"
31     "offbattery"
32     "powerout"
33     "remotedown"
34     "runlimit"
35     "timeout"
36     "startselftest"
37     "endselftest"
38   ];
40   shellCmdsForEventScript = eventname: commands: ''
41     echo "#!${pkgs.runtimeShell}" > "$out/${eventname}"
42     echo '${commands}' >> "$out/${eventname}"
43     chmod a+x "$out/${eventname}"
44   '';
46   eventToShellCmds = event: if builtins.hasAttr event cfg.hooks then (shellCmdsForEventScript event (builtins.getAttr event cfg.hooks)) else "";
48   scriptDir = pkgs.runCommand "apcupsd-scriptdir" { preferLocalBuild = true; } (''
49     mkdir "$out"
50     # Copy SCRIPTDIR from apcupsd package
51     cp -r ${pkgs.apcupsd}/etc/apcupsd/* "$out"/
52     # Make the files writeable (nix will unset the write bits afterwards)
53     chmod u+w "$out"/*
54     # Remove the sample event notification scripts, because they don't work
55     # anyways (they try to send mail to "root" with the "mail" command)
56     (cd "$out" && rm changeme commok commfailure onbattery offbattery)
57     # Remove the sample apcupsd.conf file (we're generating our own)
58     rm "$out/apcupsd.conf"
59     # Set the SCRIPTDIR= line in apccontrol to the dir we're creating now
60     sed -i -e "s|^SCRIPTDIR=.*|SCRIPTDIR=$out|" "$out/apccontrol"
61     '' + concatStringsSep "\n" (map eventToShellCmds eventList)
63   );
65   # Ensure the CLI uses our generated configFile
66   wrappedBinaries = pkgs.runCommandLocal "apcupsd-wrapped-binaries"
67     { nativeBuildInputs = [ pkgs.makeWrapper ]; }
68     ''
69       for p in "${lib.getBin pkgs.apcupsd}/bin/"*; do
70           bname=$(basename "$p")
71           makeWrapper "$p" "$out/bin/$bname" --add-flags "-f ${configFile}"
72       done
73     '';
75   apcupsdWrapped = pkgs.symlinkJoin {
76     name = "apcupsd-wrapped";
77     # Put wrappers first so they "win"
78     paths = [ wrappedBinaries pkgs.apcupsd ];
79   };
84   ###### interface
86   options = {
88     services.apcupsd = {
90       enable = mkOption {
91         default = false;
92         type = types.bool;
93         description = ''
94           Whether to enable the APC UPS daemon. apcupsd monitors your UPS and
95           permits orderly shutdown of your computer in the event of a power
96           failure. User manual: http://www.apcupsd.com/manual/manual.html.
97           Note that apcupsd runs as root (to allow shutdown of computer).
98           You can check the status of your UPS with the "apcaccess" command.
99         '';
100       };
102       configText = mkOption {
103         default = ''
104           UPSTYPE usb
105           NISIP 127.0.0.1
106           BATTERYLEVEL 50
107           MINUTES 5
108         '';
109         type = types.lines;
110         description = ''
111           Contents of the runtime configuration file, apcupsd.conf. The default
112           settings makes apcupsd autodetect USB UPSes, limit network access to
113           localhost and shutdown the system when the battery level is below 50
114           percent, or when the UPS has calculated that it has 5 minutes or less
115           of remaining power-on time. See man apcupsd.conf for details.
116         '';
117       };
119       hooks = mkOption {
120         default = {};
121         example = {
122           doshutdown = "# shell commands to notify that the computer is shutting down";
123         };
124         type = types.attrsOf types.lines;
125         description = ''
126           Each attribute in this option names an apcupsd event and the string
127           value it contains will be executed in a shell, in response to that
128           event (prior to the default action). See "man apccontrol" for the
129           list of events and what they represent.
131           A hook script can stop apccontrol from doing its default action by
132           exiting with value 99. Do not do this unless you know what you're
133           doing.
134         '';
135       };
137     };
139   };
142   ###### implementation
144   config = mkIf cfg.enable {
146     assertions = [ {
147       assertion = let hooknames = builtins.attrNames cfg.hooks; in all (x: elem x eventList) hooknames;
148       message = ''
149         One (or more) attribute names in services.apcupsd.hooks are invalid.
150         Current attribute names: ${toString (builtins.attrNames cfg.hooks)}
151         Valid attribute names  : ${toString eventList}
152       '';
153     } ];
155     # Give users access to the "apcaccess" tool
156     environment.systemPackages = [ apcupsdWrapped ];
158     # NOTE 1: apcupsd runs as root because it needs permission to run
159     # "shutdown"
160     #
161     # NOTE 2: When apcupsd calls "wall", it prints an error because stdout is
162     # not connected to a tty (it is connected to the journal):
163     #   wall: cannot get tty name: Inappropriate ioctl for device
164     # The message still gets through.
165     systemd.services.apcupsd = {
166       description = "APC UPS Daemon";
167       wantedBy = [ "multi-user.target" ];
168       preStart = "mkdir -p /run/apcupsd/";
169       serviceConfig = {
170         ExecStart = "${pkgs.apcupsd}/bin/apcupsd -b -f ${configFile} -d1";
171         # TODO: When apcupsd has initiated a shutdown, systemd always ends up
172         # waiting for it to stop ("A stop job is running for UPS daemon"). This
173         # is weird, because in the journal one can clearly see that apcupsd has
174         # received the SIGTERM signal and has already quit (or so it seems).
175         # This reduces the wait time from 90 seconds (default) to just 5. Then
176         # systemd kills it with SIGKILL.
177         TimeoutStopSec = 5;
178       };
179       unitConfig.Documentation = "man:apcupsd(8)";
180     };
182     # A special service to tell the UPS to power down/hibernate just before the
183     # computer shuts down. (The UPS has a built in delay before it actually
184     # shuts off power.) Copied from here:
185     # http://forums.opensuse.org/english/get-technical-help-here/applications/479499-apcupsd-systemd-killpower-issues.html
186     systemd.services.apcupsd-killpower = {
187       description = "APC UPS Kill Power";
188       after = [ "shutdown.target" ]; # append umount.target?
189       before = [ "final.target" ];
190       wantedBy = [ "shutdown.target" ];
191       unitConfig = {
192         ConditionPathExists = "/run/apcupsd/powerfail";
193         DefaultDependencies = "no";
194       };
195       serviceConfig = {
196         Type = "oneshot";
197         ExecStart = "${pkgs.apcupsd}/bin/apcupsd --killpower -f ${configFile}";
198         TimeoutSec = "infinity";
199         StandardOutput = "tty";
200         RemainAfterExit = "yes";
201       };
202     };
204   };