From f74d5e1de6b14f06affd949300d5ea7d0ca1521b Mon Sep 17 00:00:00 2001 From: Can Date: Sun, 21 Jun 2026 15:59:09 +0300 Subject: [PATCH] feat: add `upsentry broadcast on|off|status` to mute on-screen messages Mute or restore on-screen outage messages with one command: NUT's terminal WALL broadcasts plus the optional tmux "save your work" session warning. Notifiers (SMS / Telegram / webhook) are never affected. Also fixes a latent bug: the installer left NOCOMM/NOPARENT/REPLBATT/ NOTCONFIGURED on upsmon's SYSLOG+WALL default, so an unreachable NUT master broadcast "UPS ... is unavailable" to every terminal on every poll. These are now SYSLOG by default and managed by `upsentry broadcast`. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 5 ++++ README.md | 18 ++++++++++++++ bin/upsentry | 56 +++++++++++++++++++++++++++++++++++++++++- bin/upsentry-notify.sh | 5 +++- install.sh | 7 ++++++ upsentry.conf.example | 6 +++++ 6 files changed, 95 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 122612c..a39e131 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +- `upsentry broadcast on|off|status`: mute/unmute on-screen outage messages + (NUT terminal `WALL` broadcasts + the tmux session warning) in one command; + notifiers are never affected +- Fix: installer now sets `NOCOMM`/`NOPARENT`/`REPLBATT`/`NOTCONFIGURED` to + `SYSLOG` so an unreachable NUT master no longer spams `WALL` to every terminal - Event hooks: executable scripts in `hooks.d//` run on every power event with the rendered message as `$1` - `MSG_LANG` built-in language presets for default messages (en, tr) diff --git a/README.md b/README.md index efe3446..e3357fa 100644 --- a/README.md +++ b/README.md @@ -105,11 +105,29 @@ sudo upsentry test online # ... and a "power restored" one sudo upsentry simulate-shutdown # dry-run the shutdown hook (does NOT halt) sudo upsentry stats # outage history & totals sudo upsentry log 50 # last 50 log lines +sudo upsentry broadcast off # mute on-screen outage messages (see below) ``` > `sudo` is needed because the config file holding your credentials is > readable by root only — by design. +### 🔕 Mute on-screen messages + +UPSentry can put two kinds of message **on your terminals**: NUT's own +`WALL` broadcasts (e.g. `UPS … is unavailable`, which repeats while the NUT +master is unreachable) and the optional tmux "save your work" warning. Silence +or restore **both** at once — no config edit, no reinstall: + +```bash +sudo upsentry broadcast off # mute: strip WALL from upsmon + mute tmux warning +sudo upsentry broadcast on # unmute: restore WALL + tmux warning +sudo upsentry broadcast status # show the current state +``` + +Notifiers (SMS, Telegram, webhook) are **never** affected — they keep firing +on every event. `off` reloads upsmon immediately and writes a flag at +`$STATE_DIR/broadcast.disabled`, so it survives reboots until you turn it back on. + ## ⚙️ Configuration Everything lives in **`/etc/upsentry/upsentry.conf`** (root-only, `chmod 600`). diff --git a/bin/upsentry b/bin/upsentry index 1f16dd1..3d17029 100755 --- a/bin/upsentry +++ b/bin/upsentry @@ -8,6 +8,9 @@ # upsentry log [N] show last N log lines (default 20) # upsentry stats outage history & totals # upsentry simulate-shutdown dry-run the shutdown hook (no halt) +# upsentry broadcast on|off|status +# mute/unmute on-screen outage messages +# (terminal WALL + tmux session warning) # upsentry version # # Part of UPSentry — https://github.com/cankirca/upsentry @@ -21,6 +24,25 @@ die() { echo "upsentry: $*" >&2; exit 1; } # shellcheck source=/dev/null need_config() { [ -r "$CONFIG" ] || die "config not readable: $CONFIG (run as root?)"; . "$CONFIG"; } +# Rewrite the NOTIFYFLAG block in upsmon.conf and reload upsmon, preserving the +# file's owner/mode. $1 = upsmon.conf path, $2 = '|'-separated "EVENT FLAGS". +bcast_apply_flags() { + local conf="$1" list="$2" tmp line + [ -w "$conf" ] || { echo "note: $conf not writable — terminal WALL left unchanged"; return 0; } + tmp="$(mktemp)" || return 1 + grep -vE '^[[:space:]]*NOTIFYFLAG[[:space:]]|upsentry-broadcast-managed' "$conf" > "$tmp" + { + printf '\n# >>> upsentry-broadcast-managed (edited by '\''upsentry broadcast'\'') >>>\n' + printf '%s\n' "$list" | tr '|' '\n' | while IFS= read -r line; do + [ -n "$line" ] && printf 'NOTIFYFLAG %s\n' "$line" + done + printf '# <<< upsentry-broadcast-managed <<<\n' + } >> "$tmp" + cat "$tmp" > "$conf" # cat (not mv) keeps the original owner/mode + rm -f "$tmp" + upsmon -c reload >/dev/null 2>&1 || systemctl restart nut-monitor.service >/dev/null 2>&1 || true +} + cmd="${1:-help}" case "$cmd" in status) @@ -75,10 +97,42 @@ case "$cmd" in [ "$(id -u)" -eq 0 ] || die "run as root: sudo upsentry simulate-shutdown" UPSENTRY_DRY_RUN=1 "$LIB_DIR/bin/upsentry-shutdown.sh" ;; + broadcast) + [ "$(id -u)" -eq 0 ] || die "run as root: sudo upsentry broadcast {on|off|status}" + need_config + bcast_conf="${UPSENTRY_UPSMON_CONF:-/etc/nut/upsmon.conf}" + bcast_flag="${STATE_DIR:-/var/lib/upsentry}/broadcast.disabled" + # EXEC stays on the power events so notifiers (SMS, Telegram, …) always fire. + bcast_off="ONLINE SYSLOG+EXEC|ONBATT SYSLOG+EXEC|LOWBATT SYSLOG+EXEC|FSD SYSLOG+EXEC|COMMBAD SYSLOG|COMMOK SYSLOG|SHUTDOWN SYSLOG|NOCOMM SYSLOG|NOPARENT SYSLOG|REPLBATT SYSLOG|NOTCONFIGURED SYSLOG" + bcast_on="ONLINE SYSLOG+WALL+EXEC|ONBATT SYSLOG+WALL+EXEC|LOWBATT SYSLOG+WALL+EXEC|FSD SYSLOG+WALL+EXEC|COMMBAD SYSLOG+WALL|COMMOK SYSLOG+WALL|SHUTDOWN SYSLOG+WALL|NOCOMM SYSLOG+WALL|NOPARENT SYSLOG+WALL|REPLBATT SYSLOG+WALL|NOTCONFIGURED SYSLOG+WALL" + case "${2:-status}" in + off|disable|mute) + mkdir -p "$(dirname "$bcast_flag")" 2>/dev/null + : > "$bcast_flag" + bcast_apply_flags "$bcast_conf" "$bcast_off" + echo "on-screen broadcast: OFF (terminal WALL stripped + tmux warning muted; notifiers still active)" + ;; + on|enable|unmute) + rm -f "$bcast_flag" + bcast_apply_flags "$bcast_conf" "$bcast_on" + echo "on-screen broadcast: ON (terminal WALL restored + tmux warning enabled)" + ;; + status) + grep -qE '^[[:space:]]*NOTIFYFLAG.*WALL' "$bcast_conf" 2>/dev/null \ + && echo "terminal WALL : ON" || echo "terminal WALL : OFF" + [ -f "$bcast_flag" ] \ + && echo "tmux warning : muted" \ + || echo "tmux warning : enabled (follows SESSION_NOTIFY_ENABLED)" + ;; + *) + die "usage: upsentry broadcast {on|off|status}" + ;; + esac + ;; version) echo "UPSentry $VERSION" ;; help|--help|-h|*) - sed -n '3,12p' "$0" | sed 's/^# \{0,1\}//' + sed -n '3,14p' "$0" | sed 's/^# \{0,1\}//' ;; esac diff --git a/bin/upsentry-notify.sh b/bin/upsentry-notify.sh index c8c59a1..7a06513 100755 --- a/bin/upsentry-notify.sh +++ b/bin/upsentry-notify.sh @@ -130,7 +130,10 @@ fi # ONBATT only: best-effort warning to matching tmux sessions. # Runs as SESSION_USER; config values are passed via env so the # credential-holding config never needs to be readable by that user. -if [ "$EVENT" = "ONBATT" ] && [ "${SESSION_NOTIFY_ENABLED:-no}" = "yes" ] && [ -n "$SESSION_USER" ]; then +# `upsentry broadcast off` creates $STATE_DIR/broadcast.disabled to mute this. +if [ "$EVENT" = "ONBATT" ] && [ -f "$STATE_DIR/broadcast.disabled" ]; then + log "on-screen session warning muted by 'upsentry broadcast off'" +elif [ "$EVENT" = "ONBATT" ] && [ "${SESSION_NOTIFY_ENABLED:-no}" = "yes" ] && [ -n "$SESSION_USER" ]; then if [ -x "$LIB_DIR/bin/upsentry-sessions.sh" ]; then sudo -u "$SESSION_USER" env \ UPSENTRY_PATTERN="${SESSION_PROCESS_PATTERN:-claude}" \ diff --git a/install.sh b/install.sh index 71eb54b..93a14b9 100755 --- a/install.sh +++ b/install.sh @@ -231,6 +231,13 @@ NOTIFYFLAG FSD SYSLOG+EXEC NOTIFYFLAG COMMBAD SYSLOG NOTIFYFLAG COMMOK SYSLOG NOTIFYFLAG SHUTDOWN SYSLOG +# Explicitly silence the comm-loss / misc events: upsmon defaults these to +# SYSLOG+WALL, which broadcasts "UPS ... is unavailable" to every terminal +# whenever the NUT master is unreachable. Manage them via 'upsentry broadcast'. +NOTIFYFLAG NOCOMM SYSLOG +NOTIFYFLAG NOPARENT SYSLOG +NOTIFYFLAG REPLBATT SYSLOG +NOTIFYFLAG NOTCONFIGURED SYSLOG POLLFREQ 5 POLLFREQALERT 5 diff --git a/upsentry.conf.example b/upsentry.conf.example index 5d5dffa..402244c 100644 --- a/upsentry.conf.example +++ b/upsentry.conf.example @@ -59,6 +59,12 @@ DATE_FORMAT="%d.%m.%Y %H:%M:%S" # panes that run an AI coding agent (Claude Code, aider, …) so # the agent saves its state before a possible shutdown. # Panes that do NOT match the pattern are never touched. +# +# Tip: temporarily mute/unmute ALL on-screen messages (this warning +# plus NUT's terminal WALL broadcasts) without editing this file: +# sudo upsentry broadcast off # mute +# sudo upsentry broadcast on # unmute +# sudo upsentry broadcast status # ------------------------------------------------------------ SESSION_NOTIFY_ENABLED="no" SESSION_USER="" # user owning the tmux server, e.g. devuser