Tell the operator when a finding needs them

A finding nobody is told about is a finding nobody has, and the card added earlier only shows
them to someone who opens the tab. Only needs_operator is announced — an open finding may still
be repaired by the next pass — one notification for all of them, and each is announced once and
stays quiet until the fault changes or gets worse.

vv_notify() hands the message to common.sh's notify() rather than reimplementing the channels,
and calls detect_hosts() explicitly because load_config.sh deliberately does not: without it the
Unraid notification arrives and Discord silently never does. It also reports false when no
channel is switched on at all, since notify() exits 0 either way and a caller believing that
would mark a finding as told and never mention it again.

Notification text is folded to ASCII. Unraid's notifier dropped an em dash outright and left the
double space behind, which was found by sending one and reading what arrived.
This commit is contained in:
Gmer4Lfe
2026-08-09 21:59:50 -04:00
parent e4423200cc
commit 91357a2d03
4 changed files with 194 additions and 0 deletions
+61
View File
@@ -933,3 +933,64 @@ function vv_transcode_sessions(): array {
'last_ssd_freed' => $lastSsdFreed,
];
}
// ── Notification ─────────────────────────────────────────────────────────────────────────────
// Hands a message to common.sh's notify(), rather than reimplementing it here.
//
// notify() is the one place that knows this installation's channels — Unraid's native notifier
// via the adapter, and the per-host Discord webhook — and which of them are switched on. A PHP
// copy would be a second answer to "how does this machine reach its operator", and the two would
// drift the first time a channel is added.
//
// detect_hosts() is called explicitly and that is not optional. load_config.sh deliberately does
// not call it — its header says so — and MY_DISCORD_WEBHOOK is set by detect_hosts() from
// HOST<n>_DISCORD_WEBHOOK. Skipping it yields a notification that reaches the Unraid GUI and
// silently never reaches Discord, which is the failure that looks like success.
//
// Single-line messages only. notify() builds its Discord payload with printf into a JSON string
// literal, so a raw newline in the message produces invalid JSON and the webhook rejects it.
// Callers separate with ' · '.
// Is there anywhere for a notification to go?
//
// notify() exits 0 whether or not it did anything — with NOTIFY_UNRAID false and no webhook it
// logs a line and returns success, because from its point of view nothing went wrong. A caller
// that treats that as delivered will mark its work as told and go quiet about it forever, so the
// question "is any channel switched on" is answered here instead, from the same two settings
// notify() itself consults.
function vv_notify_available(): bool {
$conf = vv_conf_vars();
if (strtolower(trim((string)($conf['NOTIFY_UNRAID'] ?? 'false'))) === 'true') return true;
$hook = strtoupper(vv_detect_host()) . '_DISCORD_WEBHOOK';
return trim((string)($conf[$hook] ?? '')) !== '';
}
function vv_notify(string $message, string $subject, string $severity = 'normal'): bool {
$loader = SCRIPTS_DIR . '/load_config.sh';
if (!is_file($loader)) return false;
if (!vv_notify_available()) return false;
if (!in_array($severity, ['normal', 'warning', 'alert'], true)) $severity = 'normal';
// Newlines are stripped rather than refused: a caller that accidentally includes one should
// still reach the operator, just on one line.
$message = trim(preg_replace('/\s*[\r\n]+\s*/', ' | ', $message));
if ($message === '') return false;
// Typographic characters do not survive Unraid's notifier — it dropped an em dash outright
// and left the double space behind it, which was found by sending one and reading what
// arrived. Every string in this codebase is full of them, so they are folded to ASCII here
// rather than asked of each caller. Anything else non-ASCII is left alone: a share name with
// an accent should arrive imperfectly rather than not at all.
$message = strtr($message, ['—' => '-', '' => '-', '·' => '|', '→' => '->',
'“' => '"', '”' => '"', '' => "'", '' => "'", '…' => '...']);
$subject = strtr($subject, ['—' => '-', '' => '-', '·' => '|', '→' => '->',
'“' => '"', '”' => '"', '' => "'", '' => "'", '…' => '...']);
$script = 'source ' . escapeshellarg($loader) . ' >/dev/null 2>&1 || exit 91; '
. 'detect_hosts >/dev/null 2>&1; '
. 'notify ' . escapeshellarg($message) . ' ' . escapeshellarg($subject) . ' '
. escapeshellarg($severity) . ' >/dev/null 2>&1';
$out = []; $rc = 0;
exec('bash -c ' . escapeshellarg($script), $out, $rc);
return $rc === 0;
}