feat: pre-update maintenance notifications via mail + matrix (Issue #24)
unattended-upgrades was already active on the host, just never documented or closed. Adds a generic, reusable systemd timer + script that fires before the daily update window and notifies via email and a Matrix thread reply if any packages are actually pending - reusing the mas-cli bot account pattern established for Draupnir. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
100b46ec91
commit
376772078a
@@ -0,0 +1,28 @@
|
||||
# Example for /etc/maintenance-notify/config (host-level, NOT deployed via
|
||||
# GitOps/Flux - copy manually to the target host and adjust for your own
|
||||
# instance). Sourced as a plain bash file by maintenance-notify.sh.
|
||||
#
|
||||
# The values below are axion1337.chat's own, real configuration - shown as a
|
||||
# concrete worked example. Replace every value for your own homeserver/room.
|
||||
|
||||
# Your homeserver's base URL (matrix client-server API). This is often a
|
||||
# dedicated subdomain, NOT your apex domain - check your own
|
||||
# .well-known/matrix/client delegation (`m.homeserver.base_url`) to be sure.
|
||||
# For axion1337.chat specifically it's matrix.axion1337.chat, not the apex.
|
||||
MATRIX_HOMESERVER="https://matrix.axion1337.chat"
|
||||
|
||||
# The room the notification gets posted into.
|
||||
MATRIX_ROOM_ID="!lmZaajvVboTPxQxXzv:axion1337.chat"
|
||||
|
||||
# The thread to reply into (matrix.to link's event id after the room id).
|
||||
# IMPORTANT: Matrix event IDs start with "$" - this value MUST be
|
||||
# single-quoted, otherwise bash will try to expand "$T3MQZgf..." as a
|
||||
# variable and silently truncate it to an empty string.
|
||||
MATRIX_THREAD_EVENT_ID='$T3MQZgf-maQwfshCKlCn0bo4DGHn4sZS-8eI9u2V6ZI'
|
||||
|
||||
# Sending identity - must match the "user"/"from" in /etc/msmtprc.
|
||||
MAIL_FROM="wartung@axion1337.chat"
|
||||
|
||||
# Where the pre-update heads-up actually lands (your everyday inbox, not
|
||||
# necessarily the sending mailbox above).
|
||||
MAIL_TO="your-address@example.com"
|
||||
@@ -0,0 +1,8 @@
|
||||
[Unit]
|
||||
Description=Pre-update maintenance notification (Mail + Matrix)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/maintenance-notify.sh
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
#!/bin/bash
|
||||
# Pre-update maintenance notification. Runs as a systemd oneshot service, well
|
||||
# before apt-daily-upgrade.timer's own window, and tells you (Mail + Matrix)
|
||||
# which packages are about to be auto-upgraded - so a post-update problem can
|
||||
# immediately be traced back to "the update ran, that's probably it".
|
||||
#
|
||||
# Generic by design: no instance-specific values are hardcoded here. All of
|
||||
# them live in /etc/maintenance-notify/config - see config.example in this
|
||||
# same directory, and docs/deployment-guides/07-host-maintenance-notifications.md
|
||||
# for the full setup guide.
|
||||
set -euo pipefail
|
||||
|
||||
CONFIG_FILE="/etc/maintenance-notify/config"
|
||||
MATRIX_TOKEN_FILE="/etc/maintenance-notify/matrix-token"
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo "Missing $CONFIG_FILE - see docs/deployment-guides/07-host-maintenance-notifications.md" >&2
|
||||
exit 1
|
||||
fi
|
||||
# shellcheck source=/dev/null
|
||||
. "$CONFIG_FILE"
|
||||
|
||||
: "${MATRIX_HOMESERVER:?MATRIX_HOMESERVER not set in $CONFIG_FILE}"
|
||||
: "${MATRIX_ROOM_ID:?MATRIX_ROOM_ID not set in $CONFIG_FILE}"
|
||||
: "${MATRIX_THREAD_EVENT_ID:?MATRIX_THREAD_EVENT_ID not set in $CONFIG_FILE}"
|
||||
: "${MAIL_FROM:?MAIL_FROM not set in $CONFIG_FILE}"
|
||||
: "${MAIL_TO:?MAIL_TO not set in $CONFIG_FILE}"
|
||||
|
||||
apt-get update -qq
|
||||
|
||||
DRYRUN_OUTPUT="$(unattended-upgrade --dry-run -v 2>&1)"
|
||||
|
||||
# Exact log strings taken from /usr/bin/unattended-upgrade itself (verified
|
||||
# live on the target host), not guessed - this is the one message emitted
|
||||
# when there is nothing to do, and the one line emitted with the package
|
||||
# list otherwise. They're mutually exclusive.
|
||||
if echo "$DRYRUN_OUTPUT" | grep -q "No packages found that can be upgraded unattended"; then
|
||||
echo "No pending upgrades - nothing to notify."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
PENDING_PKGS="$(echo "$DRYRUN_OUTPUT" | sed -n 's/^.*Packages that will be upgraded: //p' | tail -1)"
|
||||
|
||||
if [ -z "$PENDING_PKGS" ]; then
|
||||
echo "No pending upgrade packages parsed - nothing to notify."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
HOST_LABEL="$(hostname -f 2>/dev/null || hostname)"
|
||||
NOW="$(date '+%Y-%m-%d %H:%M %Z')"
|
||||
|
||||
BODY="Host: ${HOST_LABEL}
|
||||
Zeitpunkt: ${NOW}
|
||||
|
||||
Im naechsten apt-daily-upgrade.timer-Fenster werden folgende Pakete automatisch aktualisiert:
|
||||
|
||||
${PENDING_PKGS}
|
||||
|
||||
Automatische Vorab-Benachrichtigung, keine Aktion erforderlich."
|
||||
|
||||
send_mail() {
|
||||
if ! command -v msmtp >/dev/null 2>&1; then
|
||||
echo "msmtp not installed, skipping mail notification" >&2
|
||||
return 1
|
||||
fi
|
||||
{
|
||||
echo "From: ${MAIL_FROM}"
|
||||
echo "To: ${MAIL_TO}"
|
||||
echo "Subject: [${HOST_LABEL}] Anstehendes Update"
|
||||
echo
|
||||
echo "$BODY"
|
||||
} | msmtp -a maintenance-notify -- "${MAIL_TO}"
|
||||
}
|
||||
|
||||
send_matrix() {
|
||||
if [ ! -f "$MATRIX_TOKEN_FILE" ]; then
|
||||
echo "Missing $MATRIX_TOKEN_FILE, skipping Matrix notification" >&2
|
||||
return 1
|
||||
fi
|
||||
local token txn_id encoded_room payload
|
||||
token="$(cat "$MATRIX_TOKEN_FILE")"
|
||||
txn_id="$(uuidgen)"
|
||||
encoded_room="$(jq -rn --arg s "$MATRIX_ROOM_ID" '$s|@uri')"
|
||||
payload="$(jq -n --arg body "$BODY" --arg event_id "$MATRIX_THREAD_EVENT_ID" \
|
||||
'{msgtype: "m.text", body: $body, "m.relates_to": {rel_type: "m.thread", event_id: $event_id}}')"
|
||||
curl -sS -f -X PUT \
|
||||
-H "Authorization: Bearer ${token}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "$payload" \
|
||||
"${MATRIX_HOMESERVER}/_matrix/client/v3/rooms/${encoded_room}/send/m.room.message/${txn_id}" \
|
||||
> /dev/null
|
||||
}
|
||||
|
||||
mail_ok=0
|
||||
matrix_ok=0
|
||||
send_mail && mail_ok=1
|
||||
send_matrix && matrix_ok=1
|
||||
|
||||
if [ "$mail_ok" -eq 0 ] && [ "$matrix_ok" -eq 0 ]; then
|
||||
echo "Both mail and Matrix notification failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=Daily pre-update maintenance notification, fires before apt-daily-upgrade.timer's window
|
||||
|
||||
[Timer]
|
||||
# Fixed, unrandomized time - must fire safely before the earliest possible
|
||||
# start of apt-daily-upgrade.timer. Default apt-daily-upgrade.timer ships as
|
||||
# OnCalendar=*-*-* 6:00 with RandomizedDelaySec=60m (actual run: 06:00-07:00).
|
||||
# If your apt-daily-upgrade.timer differs (check with
|
||||
# `systemctl cat apt-daily-upgrade.timer`), adjust the time below to keep a
|
||||
# comfortable lead.
|
||||
OnCalendar=*-*-* 05:00
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -0,0 +1,24 @@
|
||||
# Template for /etc/msmtprc (host-level, NOT deployed via GitOps/Flux - copy
|
||||
# manually to the target host and fill in the placeholders yourself).
|
||||
#
|
||||
# Copy to /etc/msmtprc, replace the __PLACEHOLDER__ values below with your own
|
||||
# transactional-mail provider's SMTP details, then:
|
||||
# chmod 600 /etc/msmtprc
|
||||
# The password itself is NOT stored here - it's read at send-time from
|
||||
# /etc/maintenance-notify/mail-password (chmod 600, plain text, one line, no
|
||||
# trailing newline needed either way).
|
||||
|
||||
account maintenance-notify
|
||||
host __SMTP_HOST__
|
||||
port __SMTP_PORT__
|
||||
tls on
|
||||
# Port 465 = implicit TLS (tls_starttls off, as below). If your provider uses
|
||||
# port 587/STARTTLS instead, flip this to "tls_starttls on".
|
||||
tls_starttls off
|
||||
auth on
|
||||
user __MAIL_FROM__
|
||||
passwordeval "cat /etc/maintenance-notify/mail-password"
|
||||
from __MAIL_FROM__
|
||||
logfile /var/log/msmtp.log
|
||||
|
||||
account default : maintenance-notify
|
||||
Reference in New Issue
Block a user