mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-30 17:41:12 +00:00
191 lines
6.1 KiB
Nix
191 lines
6.1 KiB
Nix
{ config, pkgs, ... }:
|
|
|
|
with pkgs.lib;
|
|
|
|
let
|
|
cfg = config.services.apcupsd;
|
|
|
|
configFile = pkgs.writeText "apcupsd.conf" ''
|
|
## apcupsd.conf v1.1 ##
|
|
# apcupsd complains if the first line is not like above.
|
|
${cfg.configText}
|
|
SCRIPTDIR ${toString scriptDir}
|
|
'';
|
|
|
|
# List of events from "man apccontrol"
|
|
eventList = [
|
|
"annoyme"
|
|
"battattach"
|
|
"battdetach"
|
|
"changeme"
|
|
"commfailure"
|
|
"commok"
|
|
"doreboot"
|
|
"doshutdown"
|
|
"emergency"
|
|
"failing"
|
|
"killpower"
|
|
"loadlimit"
|
|
"mainsback"
|
|
"onbattery"
|
|
"offbattery"
|
|
"powerout"
|
|
"remotedown"
|
|
"runlimit"
|
|
"timeout"
|
|
"startselftest"
|
|
"endselftest"
|
|
];
|
|
|
|
shellCmdsForEventScript = eventname: commands: ''
|
|
echo "#!${pkgs.stdenv.shell}" > "$out/${eventname}"
|
|
echo "${commands}" >> "$out/${eventname}"
|
|
chmod a+x "$out/${eventname}"
|
|
'';
|
|
|
|
eventToShellCmds = event: if builtins.hasAttr event cfg.hooks then (shellCmdsForEventScript event (builtins.getAttr event cfg.hooks)) else "";
|
|
|
|
scriptDir = pkgs.runCommand "apcupsd-scriptdir" {} (''
|
|
mkdir "$out"
|
|
# Copy SCRIPTDIR from apcupsd package
|
|
cp -r ${pkgs.apcupsd}/etc/apcupsd/* "$out"/
|
|
# Make the files writeable (nix will unset the write bits afterwards)
|
|
chmod u+w "$out"/*
|
|
# Remove the sample event notification scripts, because they don't work
|
|
# anyways (they try to send mail to "root" with the "mail" command)
|
|
(cd "$out" && rm changeme commok commfailure onbattery offbattery)
|
|
# Remove the sample apcupsd.conf file (we're generating our own)
|
|
rm "$out/apcupsd.conf"
|
|
# Set the SCRIPTDIR= line in apccontrol to the dir we're creating now
|
|
sed -i -e "s|^SCRIPTDIR=.*|SCRIPTDIR=$out|" "$out/apccontrol"
|
|
'' + concatStringsSep "\n" (map eventToShellCmds eventList)
|
|
|
|
);
|
|
|
|
in
|
|
|
|
{
|
|
|
|
###### interface
|
|
|
|
options = {
|
|
|
|
services.apcupsd = {
|
|
|
|
enable = mkOption {
|
|
default = false;
|
|
type = types.uniq types.bool;
|
|
description = ''
|
|
Whether to enable the APC UPS daemon. apcupsd monitors your UPS and
|
|
permits orderly shutdown of your computer in the event of a power
|
|
failure. User manual: http://www.apcupsd.com/manual/manual.html.
|
|
Note that apcupsd runs as root (to allow shutdown of computer).
|
|
You can check the status of your UPS with the "apcaccess" command.
|
|
'';
|
|
};
|
|
|
|
configText = mkOption {
|
|
default = ''
|
|
UPSTYPE usb
|
|
NISIP 127.0.0.1
|
|
BATTERYLEVEL 50
|
|
MINUTES 5
|
|
'';
|
|
type = types.string;
|
|
description = ''
|
|
Contents of the runtime configuration file, apcupsd.conf. The default
|
|
settings makes apcupsd autodetect USB UPSes, limit network access to
|
|
localhost and shutdown the system when the battery level is below 50
|
|
percent, or when the UPS has calculated that it has 5 minutes or less
|
|
of remaining power-on time. See man apcupsd.conf for details.
|
|
'';
|
|
};
|
|
|
|
hooks = mkOption {
|
|
default = {};
|
|
example = {
|
|
doshutdown = ''# shell commands to notify that the computer is shutting down'';
|
|
};
|
|
type = types.attrsOf types.string;
|
|
description = ''
|
|
Each attribute in this option names an apcupsd event and the string
|
|
value it contains will be executed in a shell, in response to that
|
|
event (prior to the default action). See "man apccontrol" for the
|
|
list of events and what they represent.
|
|
|
|
A hook script can stop apccontrol from doing its default action by
|
|
exiting with value 99. Do not do this unless you know what you're
|
|
doing.
|
|
'';
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
###### implementation
|
|
|
|
config = mkIf cfg.enable {
|
|
|
|
assertions = [ {
|
|
assertion = let hooknames = builtins.attrNames cfg.hooks; in all (x: elem x eventList) hooknames;
|
|
message = ''
|
|
One (or more) attribute names in services.apcupsd.hooks are invalid.
|
|
Current attribute names: ${toString (builtins.attrNames cfg.hooks)}
|
|
Valid attribute names : ${toString eventList}
|
|
'';
|
|
} ];
|
|
|
|
# Give users access to the "apcaccess" tool
|
|
environment.systemPackages = [ pkgs.apcupsd ];
|
|
|
|
# NOTE 1: apcupsd runs as root because it needs permission to run
|
|
# "shutdown"
|
|
#
|
|
# NOTE 2: When apcupsd calls "wall", it prints an error because stdout is
|
|
# not connected to a tty (it is connected to the journal):
|
|
# wall: cannot get tty name: Inappropriate ioctl for device
|
|
# The message still gets through.
|
|
systemd.services.apcupsd = {
|
|
description = "APC UPS daemon";
|
|
wantedBy = [ "multi-user.target" ];
|
|
preStart = "mkdir -p /run/apcupsd/";
|
|
serviceConfig = {
|
|
ExecStart = "${pkgs.apcupsd}/bin/apcupsd -b -f ${configFile} -d1";
|
|
# TODO: When apcupsd has initiated a shutdown, systemd always ends up
|
|
# waiting for it to stop ("A stop job is running for UPS daemon"). This
|
|
# is weird, because in the journal one can clearly see that apcupsd has
|
|
# received the SIGTERM signal and has already quit (or so it seems).
|
|
# This reduces the wait time from 90 seconds (default) to just 5. Then
|
|
# systemd kills it with SIGKILL.
|
|
TimeoutStopSec = 5;
|
|
};
|
|
};
|
|
|
|
# A special service to tell the UPS to power down/hibernate just before the
|
|
# computer shuts down. (The UPS has a built in delay before it actually
|
|
# shuts off power.) Copied from here:
|
|
# http://forums.opensuse.org/english/get-technical-help-here/applications/479499-apcupsd-systemd-killpower-issues.html
|
|
systemd.services.apcupsd-killpower = {
|
|
after = [ "shutdown.target" ]; # append umount.target?
|
|
before = [ "final.target" ];
|
|
wantedBy = [ "shutdown.target" ];
|
|
unitConfig = {
|
|
Description = "APC UPS killpower";
|
|
ConditionPathExists = "/run/apcupsd/powerfail";
|
|
DefaultDependencies = "no";
|
|
};
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
ExecStart = "${pkgs.apcupsd}/bin/apcupsd --killpower -f ${configFile}";
|
|
TimeoutSec = 0;
|
|
StandardOutput = "tty";
|
|
RemainAfterExit = "yes";
|
|
};
|
|
};
|
|
|
|
};
|
|
|
|
}
|