{ 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 ]; }; }