at 16.09-beta 6.3 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.apcupsd; 7 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 ''; 14 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 ]; 39 40 shellCmdsForEventScript = eventname: commands: '' 41 echo "#!${pkgs.stdenv.shell}" > "$out/${eventname}" 42 echo '${commands}' >> "$out/${eventname}" 43 chmod a+x "$out/${eventname}" 44 ''; 45 46 eventToShellCmds = event: if builtins.hasAttr event cfg.hooks then (shellCmdsForEventScript event (builtins.getAttr event cfg.hooks)) else ""; 47 48 scriptDir = pkgs.runCommand "apcupsd-scriptdir" {} ('' 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) 62 63 ); 64 65in 66 67{ 68 69 ###### interface 70 71 options = { 72 73 services.apcupsd = { 74 75 enable = mkOption { 76 default = false; 77 type = types.bool; 78 description = '' 79 Whether to enable the APC UPS daemon. apcupsd monitors your UPS and 80 permits orderly shutdown of your computer in the event of a power 81 failure. User manual: http://www.apcupsd.com/manual/manual.html. 82 Note that apcupsd runs as root (to allow shutdown of computer). 83 You can check the status of your UPS with the "apcaccess" command. 84 ''; 85 }; 86 87 configText = mkOption { 88 default = '' 89 UPSTYPE usb 90 NISIP 127.0.0.1 91 BATTERYLEVEL 50 92 MINUTES 5 93 ''; 94 type = types.string; 95 description = '' 96 Contents of the runtime configuration file, apcupsd.conf. The default 97 settings makes apcupsd autodetect USB UPSes, limit network access to 98 localhost and shutdown the system when the battery level is below 50 99 percent, or when the UPS has calculated that it has 5 minutes or less 100 of remaining power-on time. See man apcupsd.conf for details. 101 ''; 102 }; 103 104 hooks = mkOption { 105 default = {}; 106 example = { 107 doshutdown = ''# shell commands to notify that the computer is shutting down''; 108 }; 109 type = types.attrsOf types.string; 110 description = '' 111 Each attribute in this option names an apcupsd event and the string 112 value it contains will be executed in a shell, in response to that 113 event (prior to the default action). See "man apccontrol" for the 114 list of events and what they represent. 115 116 A hook script can stop apccontrol from doing its default action by 117 exiting with value 99. Do not do this unless you know what you're 118 doing. 119 ''; 120 }; 121 122 }; 123 124 }; 125 126 127 ###### implementation 128 129 config = mkIf cfg.enable { 130 131 assertions = [ { 132 assertion = let hooknames = builtins.attrNames cfg.hooks; in all (x: elem x eventList) hooknames; 133 message = '' 134 One (or more) attribute names in services.apcupsd.hooks are invalid. 135 Current attribute names: ${toString (builtins.attrNames cfg.hooks)} 136 Valid attribute names : ${toString eventList} 137 ''; 138 } ]; 139 140 # Give users access to the "apcaccess" tool 141 environment.systemPackages = [ pkgs.apcupsd ]; 142 143 # NOTE 1: apcupsd runs as root because it needs permission to run 144 # "shutdown" 145 # 146 # NOTE 2: When apcupsd calls "wall", it prints an error because stdout is 147 # not connected to a tty (it is connected to the journal): 148 # wall: cannot get tty name: Inappropriate ioctl for device 149 # The message still gets through. 150 systemd.services.apcupsd = { 151 description = "APC UPS Daemon"; 152 wantedBy = [ "multi-user.target" ]; 153 preStart = "mkdir -p /run/apcupsd/"; 154 serviceConfig = { 155 ExecStart = "${pkgs.apcupsd}/bin/apcupsd -b -f ${configFile} -d1"; 156 # TODO: When apcupsd has initiated a shutdown, systemd always ends up 157 # waiting for it to stop ("A stop job is running for UPS daemon"). This 158 # is weird, because in the journal one can clearly see that apcupsd has 159 # received the SIGTERM signal and has already quit (or so it seems). 160 # This reduces the wait time from 90 seconds (default) to just 5. Then 161 # systemd kills it with SIGKILL. 162 TimeoutStopSec = 5; 163 }; 164 unitConfig.Documentation = "man:apcupsd(8)"; 165 }; 166 167 # A special service to tell the UPS to power down/hibernate just before the 168 # computer shuts down. (The UPS has a built in delay before it actually 169 # shuts off power.) Copied from here: 170 # http://forums.opensuse.org/english/get-technical-help-here/applications/479499-apcupsd-systemd-killpower-issues.html 171 systemd.services.apcupsd-killpower = { 172 description = "APC UPS Kill Power"; 173 after = [ "shutdown.target" ]; # append umount.target? 174 before = [ "final.target" ]; 175 wantedBy = [ "shutdown.target" ]; 176 unitConfig = { 177 ConditionPathExists = "/run/apcupsd/powerfail"; 178 DefaultDependencies = "no"; 179 }; 180 serviceConfig = { 181 Type = "oneshot"; 182 ExecStart = "${pkgs.apcupsd}/bin/apcupsd --killpower -f ${configFile}"; 183 TimeoutSec = 0; 184 StandardOutput = "tty"; 185 RemainAfterExit = "yes"; 186 }; 187 }; 188 189 }; 190 191}