Files
nix-config/modules/nixos/power/default.nix
mjallen18 84eb2e3734 ntfy
2026-03-24 14:41:22 -05:00

213 lines
5.6 KiB
Nix

{
config,
lib,
pkgs,
namespace,
...
}:
with lib;
let
inherit (lib.${namespace}) mkOpt mkBoolOpt;
cfg = config.${namespace}.power.ups;
# Script called by upsmon for every UPS event. Reads NTFY_USER and
# NTFY_PASSWORD from the environment (injected via EnvironmentFile on the
# upsmon systemd service). upsmon passes the event type as the first
# argument (e.g. ONBATT, ONLINE, LOWBATT, FSD, COMMOK, COMMBAD, etc).
upsNotifyScript = pkgs.writeShellScript "ups-ntfy-notify" ''
EVENT="$1"
HOST="$(${pkgs.hostname}/bin/hostname)"
SERVER="https://ntfy.mjallen.dev"
TOPIC="ups"
case "$EVENT" in
ONBATT)
TITLE="UPS on battery: $HOST"
PRIORITY="high"
TAGS="battery,rotating_light"
MESSAGE="Power failure detected. UPS is now running on battery."
;;
ONLINE)
TITLE="UPS back on mains: $HOST"
PRIORITY="low"
TAGS="electric_plug,white_check_mark"
MESSAGE="Power restored. UPS is back on mains power."
;;
LOWBATT)
TITLE="UPS battery LOW: $HOST"
PRIORITY="urgent"
TAGS="battery,sos"
MESSAGE="UPS battery is critically low. Shutdown imminent."
;;
FSD)
TITLE="UPS forced shutdown: $HOST"
PRIORITY="urgent"
TAGS="warning,sos"
MESSAGE="Forced shutdown initiated by UPS."
;;
COMMOK)
TITLE="UPS comms restored: $HOST"
PRIORITY="low"
TAGS="electric_plug,white_check_mark"
MESSAGE="Communication with UPS restored."
;;
COMMBAD)
TITLE="UPS comms lost: $HOST"
PRIORITY="high"
TAGS="warning,rotating_light"
MESSAGE="Lost communication with UPS."
;;
SHUTDOWN)
TITLE="UPS shutdown in progress: $HOST"
PRIORITY="urgent"
TAGS="warning,sos"
MESSAGE="System is shutting down due to UPS condition."
;;
REPLBATT)
TITLE="UPS battery needs replacement: $HOST"
PRIORITY="default"
TAGS="battery,warning"
MESSAGE="UPS reports battery needs replacement."
;;
NOCOMM)
TITLE="UPS unreachable: $HOST"
PRIORITY="high"
TAGS="warning,rotating_light"
MESSAGE="UPS is not reachable."
;;
*)
TITLE="UPS event on $HOST: $EVENT"
PRIORITY="default"
TAGS="electric_plug"
MESSAGE="UPS event: $EVENT"
;;
esac
${pkgs.curl}/bin/curl -sf \
--user "$NTFY_USER:$NTFY_PASSWORD" \
-H "Title: $TITLE" \
-H "Priority: $PRIORITY" \
-H "Tags: $TAGS" \
-d "$MESSAGE" \
"$SERVER/$TOPIC" || true
'';
in
{
options.${namespace}.power.ups = {
enable = mkEnableOption "Enable UPS support";
upsName = mkOpt types.str "nas-ups" "Name of the ups";
upsUser = mkOpt types.str "nas-admin" "Name of the ups user";
upsdPort = mkOpt types.int 3493 "Port for upsd";
ntfy = {
enable = mkBoolOpt false "Send ntfy notifications on UPS events";
envFile = mkOpt types.str "" "Path to env file containing NTFY_USER and NTFY_PASSWORD";
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = cfg.upsName != "";
message = "mjallen.power.ups.upsName must be a non-empty string.";
}
{
assertion = cfg.upsUser != "";
message = "mjallen.power.ups.upsUser must be a non-empty string.";
}
{
assertion = builtins.hasAttr "jallen-nas/ups_password" config.sops.secrets;
message = "mjallen.power.ups requires a sops secret \"jallen-nas/ups_password\" to be declared.";
}
];
power.ups = {
enable = true;
openFirewall = true;
mode = "netserver";
ups = {
"${cfg.upsName}" = {
description = "NAS UPS";
driver = "usbhid-ups";
port = "auto";
};
};
users."${cfg.upsUser}" = {
passwordFile = config.sops.secrets."jallen-nas/ups_password".path;
actions = [ "ALL" ];
instcmds = [ "ALL" ];
upsmon = "primary";
};
upsmon = {
enable = true;
monitor."${cfg.upsName}" = {
passwordFile = config.sops.secrets."jallen-nas/ups_password".path;
user = cfg.upsUser;
};
# Call the notify script for all event types we care about.
settings = mkIf cfg.ntfy.enable {
NOTIFYCMD = "${upsNotifyScript}";
NOTIFYFLAG = [
[
"ONLINE"
"SYSLOG+WALL+EXEC"
]
[
"ONBATT"
"SYSLOG+WALL+EXEC"
]
[
"LOWBATT"
"SYSLOG+WALL+EXEC"
]
[
"FSD"
"SYSLOG+WALL+EXEC"
]
[
"COMMOK"
"SYSLOG+WALL+EXEC"
]
[
"COMMBAD"
"SYSLOG+WALL+EXEC"
]
[
"SHUTDOWN"
"SYSLOG+WALL+EXEC"
]
[
"REPLBATT"
"SYSLOG+WALL+EXEC"
]
[
"NOCOMM"
"SYSLOG+WALL+EXEC"
]
];
};
};
upsd = {
enable = true;
listen = [
{
address = "0.0.0.0";
port = 3493;
}
];
};
};
# Inject ntfy credentials into the upsmon service so the notify script
# can read NTFY_USER and NTFY_PASSWORD from the environment.
systemd.services.upsmon.serviceConfig.EnvironmentFile = mkIf cfg.ntfy.enable [ cfg.ntfy.envFile ];
};
}