Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<event>/` run on every
power event with the rendered message as `$1`
- `MSG_LANG` built-in language presets for default messages (en, tr)
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
Expand Down
56 changes: 55 additions & 1 deletion bin/upsentry
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
5 changes: 4 additions & 1 deletion bin/upsentry-notify.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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}" \
Expand Down
7 changes: 7 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions upsentry.conf.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading