feat(notify): outbound alerts for failed background tasks
The missing piece of hands-off updates/backups: when a task fails while nobody has the WebUI open, LibrePortal now says so — email (via the existing Mail settings), ntfy, Gotify, Discord, Slack, Telegram, or Pushover, configured under Settings → Notifications. One hook, everywhere: the task processor reports every terminal task to `libreportal notify task <id>` (detached, never load-bearing — hard curl timeouts, failures ignored). The POLICY lives in the notify command, not the daemon: CFG_NOTIFY_EVENTS = failures (default) | all | off, and cancelled tasks never notify. Failure copy is task-aware — a failed update says the app was already rolled back and won't be retried, so the reader knows the box is safe before opening the WebUI. `libreportal notify test` sends to every enabled channel with per-channel results. Verified against a local mock endpoint: all webhook payloads, JSON escaping (quotes/newlines), the events policy, and fail-fast on dead endpoints (8ms, exit nonzero). The v0.1.0 per-app NOTIFY_* field-mapping scaffolding (never wired to a sender) stays as-is; this global channel is the system it was waiting on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
66c79f997e
commit
f221177b12
24
configs/general/general_notifications
Normal file
24
configs/general/general_notifications
Normal file
@ -0,0 +1,24 @@
|
||||
# ================================================================================
|
||||
# Notifications - Alerts when background work fails (updates, backups, installs)
|
||||
# @icon 🔔
|
||||
# ================================================================================
|
||||
CFG_NOTIFY_EVENTS=failures # What to send - Failures only is the quiet default: you hear about a failed update/backup/install and nothing else. All also announces successful task results. [failures:Failures only|all:All task results|off:Off]
|
||||
CFG_NOTIFY_EMAIL=false # 📧 Email - Send alerts by email. Uses the Mail settings above (CFG_MAIL_*) for the server and sender; enable those first.
|
||||
CFG_NOTIFY_EMAIL_TO= # Email To - Recipient address for alert emails
|
||||
CFG_NOTIFY_NTFY=false # 🔔 Ntfy - Send alerts to an ntfy topic (self-hosted or ntfy.sh)
|
||||
CFG_NOTIFY_NTFY_URL=https://ntfy.sh # Ntfy Server URL - Base URL of the ntfy server
|
||||
CFG_NOTIFY_NTFY_TOPIC= # Ntfy Topic - Topic name to publish alerts to
|
||||
CFG_NOTIFY_NTFY_TOKEN= # Ntfy Access Token - Optional bearer token for protected topics
|
||||
CFG_NOTIFY_GOTIFY=false # 🔔 Gotify - Send alerts to a self-hosted Gotify server
|
||||
CFG_NOTIFY_GOTIFY_URL= # Gotify Server URL - e.g. https://gotify.example.com
|
||||
CFG_NOTIFY_GOTIFY_TOKEN= # Gotify App Token - Application token from the Gotify UI
|
||||
CFG_NOTIFY_DISCORD=false # 💬 Discord - Send alerts to a Discord channel via webhook
|
||||
CFG_NOTIFY_DISCORD_WEBHOOK= # Discord Webhook URL - https://discord.com/api/webhooks/...
|
||||
CFG_NOTIFY_SLACK=false # 💬 Slack - Send alerts to a Slack channel via incoming webhook
|
||||
CFG_NOTIFY_SLACK_WEBHOOK= # Slack Webhook URL - https://hooks.slack.com/services/...
|
||||
CFG_NOTIFY_TELEGRAM=false # 📱 Telegram - Send alerts via a Telegram bot
|
||||
CFG_NOTIFY_TELEGRAM_TOKEN= # Telegram Bot Token - From @BotFather, e.g. 123456:ABC-DEF...
|
||||
CFG_NOTIFY_TELEGRAM_CHAT_ID= # Telegram Chat ID - The chat/channel the bot posts to
|
||||
CFG_NOTIFY_PUSHOVER=false # 🔔 Pushover - Send alerts via Pushover
|
||||
CFG_NOTIFY_PUSHOVER_TOKEN= # Pushover App Token - Application API token
|
||||
CFG_NOTIFY_PUSHOVER_USER= # Pushover User Key - Your user (or group) key
|
||||
227
scripts/cli/commands/notify/cli_notify_commands.sh
Normal file
227
scripts/cli/commands/notify/cli_notify_commands.sh
Normal file
@ -0,0 +1,227 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Notifications — `libreportal notify <sub>`
|
||||
# ---------------------------------------------------------------------------
|
||||
# The outbound half of "hands-off background work": when a task the user is
|
||||
# not watching fails (an automatic update, a scheduled backup), this is what
|
||||
# tells them, on channels configured in configs/general/general_notifications.
|
||||
#
|
||||
# send <severity> <title> <body...> send one message now (severity: info|high)
|
||||
# task <task-id> notify about a finished task, applying the
|
||||
# CFG_NOTIFY_EVENTS policy (the task
|
||||
# processor calls this after every task)
|
||||
# test send a test message to every enabled channel
|
||||
#
|
||||
# Design rules:
|
||||
# * Fire-and-forget, never load-bearing: a dead webhook must not fail a task
|
||||
# or block the processor, so every channel send has hard curl timeouts and
|
||||
# the caller backgrounds the whole invocation.
|
||||
# * The POLICY lives here, not in the processor: the processor reports every
|
||||
# terminal task and this command decides (per CFG_NOTIFY_EVENTS) whether
|
||||
# anyone hears about it. The daemon never needs the notify config.
|
||||
# * Cancelled tasks never notify — the user did that themselves.
|
||||
|
||||
cliHandleNotifyCommands()
|
||||
{
|
||||
local sub="$initial_command2"
|
||||
|
||||
case "$sub" in
|
||||
"send")
|
||||
local sev="$initial_command3"
|
||||
local title="$initial_command4"
|
||||
local body="$initial_command5"
|
||||
if [[ -z "$title" ]]; then isError "Usage: libreportal notify send <info|high> <title> <body>"; return 1; fi
|
||||
lpNotifySend "${sev:-info}" "$title" "$body"
|
||||
;;
|
||||
"task")
|
||||
lpNotifyTaskResult "$initial_command3"
|
||||
;;
|
||||
"test")
|
||||
isHeader "Sending a test notification"
|
||||
if ! lpNotifyAnyChannelEnabled; then
|
||||
isNotice "No notification channels are enabled — turn one on under Settings → Notifications first."
|
||||
return 1
|
||||
fi
|
||||
lpNotifySend "info" "LibrePortal test notification" \
|
||||
"Notifications are working on $(hostname 2>/dev/null || echo this-host). Sent $(date '+%Y-%m-%d %H:%M')."
|
||||
;;
|
||||
*)
|
||||
cliShowNotifyHelp
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# True when at least one channel checkbox is on.
|
||||
lpNotifyAnyChannelEnabled()
|
||||
{
|
||||
[[ "$CFG_NOTIFY_EMAIL" == "true" || "$CFG_NOTIFY_NTFY" == "true" \
|
||||
|| "$CFG_NOTIFY_GOTIFY" == "true" || "$CFG_NOTIFY_DISCORD" == "true" \
|
||||
|| "$CFG_NOTIFY_SLACK" == "true" || "$CFG_NOTIFY_TELEGRAM" == "true" \
|
||||
|| "$CFG_NOTIFY_PUSHOVER" == "true" ]]
|
||||
}
|
||||
|
||||
# JSON string escaper for the webhook payloads. jq is used when present (it
|
||||
# handles every edge); this fallback covers the two characters that break a
|
||||
# JSON string plus newlines, which is sufficient for our own message text.
|
||||
_lpNotifyJson()
|
||||
{
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
jq -rn --arg s "$1" '$s|tojson'
|
||||
else
|
||||
local s="${1//\\/\\\\}"; s="${s//\"/\\\"}"; s="${s//$'\n'/\\n}"
|
||||
printf '"%s"' "$s"
|
||||
fi
|
||||
}
|
||||
|
||||
# Send one message to every enabled channel. $1 severity (info|high), $2 title,
|
||||
# $3 body. Reports per-channel results; returns 0 when every enabled channel
|
||||
# accepted the message (still 0 when none are enabled — nothing to fail).
|
||||
lpNotifySend()
|
||||
{
|
||||
local sev="$1" title="$2" body="$3"
|
||||
local curl_opts=(-sS --connect-timeout 5 --max-time 8 -o /dev/null -w '%{http_code}')
|
||||
local failures=0 sent=0
|
||||
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
isError "notify: curl is required to send notifications."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# One channel = one _try call: runs the curl, classifies the HTTP code,
|
||||
# logs per-channel. 2xx = delivered; anything else (or curl error) = fail.
|
||||
_try() {
|
||||
local name="$1"; shift
|
||||
local code; code="$(curl "${curl_opts[@]}" "$@" 2>/dev/null)"
|
||||
if [[ "$code" =~ ^2[0-9][0-9]$ ]]; then
|
||||
isSuccessful "notify: $name ok"
|
||||
sent=$((sent + 1))
|
||||
else
|
||||
isError "notify: $name failed (HTTP ${code:-none})"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "$CFG_NOTIFY_NTFY" == "true" && -n "$CFG_NOTIFY_NTFY_TOPIC" ]]; then
|
||||
local ntfy_args=(-H "Title: $title" -H "Priority: $([[ "$sev" == "high" ]] && echo high || echo default)")
|
||||
[[ -n "$CFG_NOTIFY_NTFY_TOKEN" ]] && ntfy_args+=(-H "Authorization: Bearer $CFG_NOTIFY_NTFY_TOKEN")
|
||||
_try ntfy "${ntfy_args[@]}" -d "$body" "${CFG_NOTIFY_NTFY_URL%/}/$CFG_NOTIFY_NTFY_TOPIC"
|
||||
fi
|
||||
|
||||
if [[ "$CFG_NOTIFY_GOTIFY" == "true" && -n "$CFG_NOTIFY_GOTIFY_URL" && -n "$CFG_NOTIFY_GOTIFY_TOKEN" ]]; then
|
||||
_try gotify -F "title=$title" -F "message=$body" \
|
||||
-F "priority=$([[ "$sev" == "high" ]] && echo 8 || echo 4)" \
|
||||
"${CFG_NOTIFY_GOTIFY_URL%/}/message?token=$CFG_NOTIFY_GOTIFY_TOKEN"
|
||||
fi
|
||||
|
||||
if [[ "$CFG_NOTIFY_DISCORD" == "true" && -n "$CFG_NOTIFY_DISCORD_WEBHOOK" ]]; then
|
||||
_try discord -H 'Content-Type: application/json' \
|
||||
-d "{\"content\": $(_lpNotifyJson "**${title}**"$'\n'"${body}")}" \
|
||||
"$CFG_NOTIFY_DISCORD_WEBHOOK"
|
||||
fi
|
||||
|
||||
if [[ "$CFG_NOTIFY_SLACK" == "true" && -n "$CFG_NOTIFY_SLACK_WEBHOOK" ]]; then
|
||||
_try slack -H 'Content-Type: application/json' \
|
||||
-d "{\"text\": $(_lpNotifyJson "*${title}*"$'\n'"${body}")}" \
|
||||
"$CFG_NOTIFY_SLACK_WEBHOOK"
|
||||
fi
|
||||
|
||||
if [[ "$CFG_NOTIFY_TELEGRAM" == "true" && -n "$CFG_NOTIFY_TELEGRAM_TOKEN" && -n "$CFG_NOTIFY_TELEGRAM_CHAT_ID" ]]; then
|
||||
_try telegram --data-urlencode "chat_id=$CFG_NOTIFY_TELEGRAM_CHAT_ID" \
|
||||
--data-urlencode "text=${title}"$'\n'"${body}" \
|
||||
"https://api.telegram.org/bot${CFG_NOTIFY_TELEGRAM_TOKEN}/sendMessage"
|
||||
fi
|
||||
|
||||
if [[ "$CFG_NOTIFY_PUSHOVER" == "true" && -n "$CFG_NOTIFY_PUSHOVER_TOKEN" && -n "$CFG_NOTIFY_PUSHOVER_USER" ]]; then
|
||||
_try pushover -F "token=$CFG_NOTIFY_PUSHOVER_TOKEN" -F "user=$CFG_NOTIFY_PUSHOVER_USER" \
|
||||
-F "title=$title" -F "message=$body" \
|
||||
-F "priority=$([[ "$sev" == "high" ]] && echo 1 || echo 0)" \
|
||||
"https://api.pushover.net/1/messages.json"
|
||||
fi
|
||||
|
||||
# Email rides the existing Mail settings (host/creds/from) — the notify
|
||||
# config only adds the on/off switch and the recipient. curl speaks SMTP
|
||||
# natively: smtps:// for implicit TLS (465), --ssl-reqd for STARTTLS (587).
|
||||
if [[ "$CFG_NOTIFY_EMAIL" == "true" && -n "$CFG_NOTIFY_EMAIL_TO" ]]; then
|
||||
if [[ "$CFG_MAIL_ENABLED" != "true" ]]; then
|
||||
isError "notify: email is enabled but Mail settings are off (CFG_MAIL_ENABLED) — skipping."
|
||||
failures=$((failures + 1))
|
||||
else
|
||||
local proto="smtp" mail_args=(--mail-from "$CFG_MAIL_FROM" --mail-rcpt "$CFG_NOTIFY_EMAIL_TO")
|
||||
case "$CFG_MAIL_SECURE" in
|
||||
ssl) proto="smtps" ;;
|
||||
tls) mail_args+=(--ssl-reqd) ;;
|
||||
esac
|
||||
[[ -n "$CFG_MAIL_USERNAME" ]] && mail_args+=(--user "$CFG_MAIL_USERNAME:$CFG_MAIL_PASSWORD")
|
||||
local msg; msg="$(printf 'From: LibrePortal <%s>\r\nTo: %s\r\nSubject: %s\r\n\r\n%s\r\n' \
|
||||
"$CFG_MAIL_FROM" "$CFG_NOTIFY_EMAIL_TO" "$title" "$body")"
|
||||
local ecode; ecode="$(printf '%s' "$msg" | curl -sS --connect-timeout 5 --max-time 15 \
|
||||
-o /dev/null -w '%{http_code}' "${mail_args[@]}" -T - \
|
||||
"${proto}://${CFG_MAIL_HOST}:${CFG_MAIL_PORT}" 2>/dev/null; echo "rc=$?")"
|
||||
# SMTP via curl reports no meaningful http_code; trust the exit code.
|
||||
if [[ "$ecode" == *"rc=0"* ]]; then
|
||||
isSuccessful "notify: email ok"
|
||||
sent=$((sent + 1))
|
||||
else
|
||||
isError "notify: email failed (${ecode##*rc=})"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if (( sent == 0 && failures == 0 )); then
|
||||
isNotice "notify: no channels enabled — nothing sent."
|
||||
return 0
|
||||
fi
|
||||
(( failures == 0 ))
|
||||
}
|
||||
|
||||
# Notify about one finished task, applying the events policy. Called by the
|
||||
# task processor after EVERY terminal task; this is where "should anyone hear
|
||||
# about it" is decided, so the daemon stays policy-free.
|
||||
lpNotifyTaskResult()
|
||||
{
|
||||
local task_id="$1"
|
||||
[[ "$task_id" =~ ^task_[0-9]+_[A-Za-z0-9]+$ ]] || return 0
|
||||
local events="${CFG_NOTIFY_EVENTS:-failures}"
|
||||
[[ "$events" == "off" ]] && return 0
|
||||
lpNotifyAnyChannelEnabled || return 0
|
||||
|
||||
local f="${containers_dir%/}/libreportal/frontend/data/tasks/${task_id}.json"
|
||||
[[ -f "$f" ]] || return 0
|
||||
local status type app exit_code
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
read -r status type app exit_code < <(jq -r '[.status, .type // "task", .app // "", (.exit_code // 0 | tostring)] | join(" ")' "$f" 2>/dev/null)
|
||||
else
|
||||
status=$(grep -oE '"status"[[:space:]]*:[[:space:]]*"[^"]+"' "$f" | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
|
||||
type=$(grep -oE '"type"[[:space:]]*:[[:space:]]*"[^"]+"' "$f" | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
|
||||
app=$(grep -oE '"app"[[:space:]]*:[[:space:]]*"[^"]+"' "$f" | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
|
||||
exit_code=""
|
||||
fi
|
||||
[[ "$status" == "cancelled" ]] && return 0 # the user did that
|
||||
[[ "$status" == "failed" || "$events" == "all" ]] || return 0
|
||||
|
||||
# Human copy per task type — the failure lines say what ALREADY happened
|
||||
# (rollback, retained snapshot), so the reader knows the box is safe before
|
||||
# they even open the WebUI.
|
||||
local host; host="$(hostname 2>/dev/null || echo LibrePortal)"
|
||||
local label="${type:-task}"; local extra=""
|
||||
case "$type" in
|
||||
updater_apply|updater_apply_all)
|
||||
label="Update${app:+ of $app}"
|
||||
extra=" The app was rolled back to its pre-update snapshot and this build will not be retried automatically — retry from the Updates page when ready." ;;
|
||||
updater_rollback) label="Rollback${app:+ of $app}" ;;
|
||||
backup) label="Backup${app:+ of $app}"
|
||||
extra=" Check the Backups page — the previous snapshots are unaffected." ;;
|
||||
restore) label="Restore${app:+ of $app}" ;;
|
||||
install) label="Install${app:+ of $app}" ;;
|
||||
*) label="Task ${type:-?}${app:+ ($app)}" ;;
|
||||
esac
|
||||
|
||||
if [[ "$status" == "failed" ]]; then
|
||||
lpNotifySend "high" "$host: ${label} failed" \
|
||||
"${label} failed (exit ${exit_code:-?}) at $(date '+%H:%M').${extra} Full log: WebUI → Tasks → ${task_id}."
|
||||
else
|
||||
lpNotifySend "info" "$host: ${label} completed" \
|
||||
"${label} finished successfully at $(date '+%H:%M')."
|
||||
fi
|
||||
}
|
||||
20
scripts/cli/commands/notify/cli_notify_header.sh
Normal file
20
scripts/cli/commands/notify/cli_notify_header.sh
Normal file
@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Notification Commands Header
|
||||
# Shows available `libreportal notify` subcommands.
|
||||
|
||||
cliShowNotifyHelp()
|
||||
{
|
||||
echo ""
|
||||
echo "Available Notification Commands:"
|
||||
echo ""
|
||||
echo " libreportal notify test - Send a test message to every enabled channel"
|
||||
echo " libreportal notify send <sev> <title> <body> - Send one message now (sev: info|high)"
|
||||
echo " libreportal notify task <task-id> - Notify about a finished task (used by the task processor)"
|
||||
echo ""
|
||||
echo "Channels (email, ntfy, Gotify, Discord, Slack, Telegram, Pushover) are"
|
||||
echo "configured under Settings → Notifications. The default policy notifies"
|
||||
echo "on FAILED background tasks only — a failed automatic update or backup"
|
||||
echo "reaches you without the WebUI being open. Cancelled tasks never notify."
|
||||
echo ""
|
||||
}
|
||||
@ -33,6 +33,8 @@ cli_scripts=(
|
||||
"cli/commands/instance/cli_instance_header.sh"
|
||||
"cli/commands/ip/cli_ip_commands.sh"
|
||||
"cli/commands/ip/cli_ip_header.sh"
|
||||
"cli/commands/notify/cli_notify_commands.sh"
|
||||
"cli/commands/notify/cli_notify_header.sh"
|
||||
"cli/commands/peer/cli_peer_commands.sh"
|
||||
"cli/commands/peer/cli_peer_header.sh"
|
||||
"cli/commands/regen/cli_regen_commands.sh"
|
||||
|
||||
@ -297,6 +297,7 @@ declare -gA LP_FN_MAP=(
|
||||
[cliHandleInstallCommands]="cli/commands/install/cli_install_commands.sh"
|
||||
[cliHandleInstanceCommands]="cli/commands/instance/cli_instance_commands.sh"
|
||||
[cliHandleIPCommands]="cli/commands/ip/cli_ip_commands.sh"
|
||||
[cliHandleNotifyCommands]="cli/commands/notify/cli_notify_commands.sh"
|
||||
[cliHandlePeerCommands]="cli/commands/peer/cli_peer_commands.sh"
|
||||
[cliHandleRegenCommands]="cli/commands/regen/cli_regen_commands.sh"
|
||||
[cliHandleResetCommands]="cli/commands/reset/cli_reset_commands.sh"
|
||||
@ -322,6 +323,7 @@ declare -gA LP_FN_MAP=(
|
||||
[cliShowInstallHelp]="cli/commands/install/cli_install_header.sh"
|
||||
[cliShowInstanceHelp]="cli/commands/instance/cli_instance_header.sh"
|
||||
[cliShowIPHelp]="cli/commands/ip/cli_ip_header.sh"
|
||||
[cliShowNotifyHelp]="cli/commands/notify/cli_notify_header.sh"
|
||||
[cliShowPeerHelp]="cli/commands/peer/cli_peer_header.sh"
|
||||
[cliShowRegenHelp]="cli/commands/regen/cli_regen_header.sh"
|
||||
[cliShowResetHelp]="cli/commands/reset/cli_reset_header.sh"
|
||||
@ -631,6 +633,10 @@ declare -gA LP_FN_MAP=(
|
||||
[_lpJsonEsc]="source/fetch.sh"
|
||||
[_lpJsonNum]="source/fetch.sh"
|
||||
[_lpJsonStr]="source/fetch.sh"
|
||||
[lpNotifyAnyChannelEnabled]="cli/commands/notify/cli_notify_commands.sh"
|
||||
[_lpNotifyJson]="cli/commands/notify/cli_notify_commands.sh"
|
||||
[lpNotifySend]="cli/commands/notify/cli_notify_commands.sh"
|
||||
[lpNotifyTaskResult]="cli/commands/notify/cli_notify_commands.sh"
|
||||
[lpRegen]="webui/webui_regen.sh"
|
||||
[lpRegenArrays]="webui/webui_regen.sh"
|
||||
[_lpRegenStale]="webui/webui_regen.sh"
|
||||
@ -1311,6 +1317,7 @@ declare -gA LP_FN_ROOT=(
|
||||
[cliHandleInstallCommands]="scripts"
|
||||
[cliHandleInstanceCommands]="scripts"
|
||||
[cliHandleIPCommands]="scripts"
|
||||
[cliHandleNotifyCommands]="scripts"
|
||||
[cliHandlePeerCommands]="scripts"
|
||||
[cliHandleRegenCommands]="scripts"
|
||||
[cliHandleResetCommands]="scripts"
|
||||
@ -1336,6 +1343,7 @@ declare -gA LP_FN_ROOT=(
|
||||
[cliShowInstallHelp]="scripts"
|
||||
[cliShowInstanceHelp]="scripts"
|
||||
[cliShowIPHelp]="scripts"
|
||||
[cliShowNotifyHelp]="scripts"
|
||||
[cliShowPeerHelp]="scripts"
|
||||
[cliShowRegenHelp]="scripts"
|
||||
[cliShowResetHelp]="scripts"
|
||||
@ -1645,6 +1653,10 @@ declare -gA LP_FN_ROOT=(
|
||||
[_lpJsonEsc]="scripts"
|
||||
[_lpJsonNum]="scripts"
|
||||
[_lpJsonStr]="scripts"
|
||||
[lpNotifyAnyChannelEnabled]="scripts"
|
||||
[_lpNotifyJson]="scripts"
|
||||
[lpNotifySend]="scripts"
|
||||
[lpNotifyTaskResult]="scripts"
|
||||
[lpRegen]="scripts"
|
||||
[lpRegenArrays]="scripts"
|
||||
[_lpRegenStale]="scripts"
|
||||
@ -2358,6 +2370,7 @@ cliHandleHelpCommands() { unset -f cliHandleHelpCommands; __lpAutoload "${instal
|
||||
cliHandleInstallCommands() { unset -f cliHandleInstallCommands; __lpAutoload "${install_scripts_dir}cli/commands/install/cli_install_commands.sh"; cliHandleInstallCommands "$@"; }
|
||||
cliHandleInstanceCommands() { unset -f cliHandleInstanceCommands; __lpAutoload "${install_scripts_dir}cli/commands/instance/cli_instance_commands.sh"; cliHandleInstanceCommands "$@"; }
|
||||
cliHandleIPCommands() { unset -f cliHandleIPCommands; __lpAutoload "${install_scripts_dir}cli/commands/ip/cli_ip_commands.sh"; cliHandleIPCommands "$@"; }
|
||||
cliHandleNotifyCommands() { unset -f cliHandleNotifyCommands; __lpAutoload "${install_scripts_dir}cli/commands/notify/cli_notify_commands.sh"; cliHandleNotifyCommands "$@"; }
|
||||
cliHandlePeerCommands() { unset -f cliHandlePeerCommands; __lpAutoload "${install_scripts_dir}cli/commands/peer/cli_peer_commands.sh"; cliHandlePeerCommands "$@"; }
|
||||
cliHandleRegenCommands() { unset -f cliHandleRegenCommands; __lpAutoload "${install_scripts_dir}cli/commands/regen/cli_regen_commands.sh"; cliHandleRegenCommands "$@"; }
|
||||
cliHandleResetCommands() { unset -f cliHandleResetCommands; __lpAutoload "${install_scripts_dir}cli/commands/reset/cli_reset_commands.sh"; cliHandleResetCommands "$@"; }
|
||||
@ -2383,6 +2396,7 @@ cliShowHelpHelp() { unset -f cliShowHelpHelp; __lpAutoload "${install_scripts_di
|
||||
cliShowInstallHelp() { unset -f cliShowInstallHelp; __lpAutoload "${install_scripts_dir}cli/commands/install/cli_install_header.sh"; cliShowInstallHelp "$@"; }
|
||||
cliShowInstanceHelp() { unset -f cliShowInstanceHelp; __lpAutoload "${install_scripts_dir}cli/commands/instance/cli_instance_header.sh"; cliShowInstanceHelp "$@"; }
|
||||
cliShowIPHelp() { unset -f cliShowIPHelp; __lpAutoload "${install_scripts_dir}cli/commands/ip/cli_ip_header.sh"; cliShowIPHelp "$@"; }
|
||||
cliShowNotifyHelp() { unset -f cliShowNotifyHelp; __lpAutoload "${install_scripts_dir}cli/commands/notify/cli_notify_header.sh"; cliShowNotifyHelp "$@"; }
|
||||
cliShowPeerHelp() { unset -f cliShowPeerHelp; __lpAutoload "${install_scripts_dir}cli/commands/peer/cli_peer_header.sh"; cliShowPeerHelp "$@"; }
|
||||
cliShowRegenHelp() { unset -f cliShowRegenHelp; __lpAutoload "${install_scripts_dir}cli/commands/regen/cli_regen_header.sh"; cliShowRegenHelp "$@"; }
|
||||
cliShowResetHelp() { unset -f cliShowResetHelp; __lpAutoload "${install_scripts_dir}cli/commands/reset/cli_reset_header.sh"; cliShowResetHelp "$@"; }
|
||||
@ -2692,6 +2706,10 @@ lpInstalledFootprintVersion() { unset -f lpInstalledFootprintVersion; __lpAutolo
|
||||
_lpJsonEsc() { unset -f _lpJsonEsc; __lpAutoload "${install_scripts_dir}source/fetch.sh"; _lpJsonEsc "$@"; }
|
||||
_lpJsonNum() { unset -f _lpJsonNum; __lpAutoload "${install_scripts_dir}source/fetch.sh"; _lpJsonNum "$@"; }
|
||||
_lpJsonStr() { unset -f _lpJsonStr; __lpAutoload "${install_scripts_dir}source/fetch.sh"; _lpJsonStr "$@"; }
|
||||
lpNotifyAnyChannelEnabled() { unset -f lpNotifyAnyChannelEnabled; __lpAutoload "${install_scripts_dir}cli/commands/notify/cli_notify_commands.sh"; lpNotifyAnyChannelEnabled "$@"; }
|
||||
_lpNotifyJson() { unset -f _lpNotifyJson; __lpAutoload "${install_scripts_dir}cli/commands/notify/cli_notify_commands.sh"; _lpNotifyJson "$@"; }
|
||||
lpNotifySend() { unset -f lpNotifySend; __lpAutoload "${install_scripts_dir}cli/commands/notify/cli_notify_commands.sh"; lpNotifySend "$@"; }
|
||||
lpNotifyTaskResult() { unset -f lpNotifyTaskResult; __lpAutoload "${install_scripts_dir}cli/commands/notify/cli_notify_commands.sh"; lpNotifyTaskResult "$@"; }
|
||||
lpRegen() { unset -f lpRegen; __lpAutoload "${install_scripts_dir}webui/webui_regen.sh"; lpRegen "$@"; }
|
||||
lpRegenArrays() { unset -f lpRegenArrays; __lpAutoload "${install_scripts_dir}webui/webui_regen.sh"; lpRegenArrays "$@"; }
|
||||
_lpRegenStale() { unset -f _lpRegenStale; __lpAutoload "${install_scripts_dir}webui/webui_regen.sh"; _lpRegenStale "$@"; }
|
||||
|
||||
@ -429,6 +429,15 @@ runTask() {
|
||||
cancelled) logInfo "Task $taskId cancelled";;
|
||||
failed) logError "Task $taskId failed (exit $rc)";;
|
||||
esac
|
||||
|
||||
# Outbound notification for the finished task. The POLICY (which events, which
|
||||
# channels, or nothing at all) lives entirely in `notify task` — the daemon
|
||||
# just reports every terminal task and moves on. Detached + backgrounded so a
|
||||
# slow webhook can never block the dispatch loop, and failures of the notify
|
||||
# itself are deliberately ignored: notifications are never load-bearing.
|
||||
if command -v libreportal >/dev/null 2>&1; then
|
||||
( libreportal notify task "$taskId" >/dev/null 2>&1 & ) 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user