User 25.09.: „Hermes' Persona ist Lucy – das muss sich in all ihren Meldungen widerspiegeln“; das angehängte „(Diese Meldung kam auch an Lucy.)“ tat so, als wäre sie jemand anderes als der Absender. - notify.sh: Kopfzeile aus dem Betreff in Lucys Ton (SOUL.md: „Commander“, knapp, sachlich), bei Dringendem „Commander, das ist dringend.“; keine zweite Anrede, wenn der Text schon mit ihr beginnt. Morgenmeldung mit kurzen Marken statt Betreffen in Klammern; die des Homelabs sagt, woher sie kommt. - Wächter: Zusatz „kam auch an Lucy“ entfernt (der Briefkasten bekommt die Meldung weiter). - Melde-Log und Auswertungen unverändert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
203 lines
8.4 KiB
Bash
203 lines
8.4 KiB
Bash
#!/usr/bin/env bash
|
||
# Meldeweg der Box (Autonomie E1): eine Nachricht an den User schicken.
|
||
# Primär: hermes send → Telegram (nutzt Gateway-Credentials, kein LLM nötig).
|
||
# Zweitweg (seit 24.09.2026): direkt an die Telegram-Bot-API mit den Zugangsdaten aus Hermes' .env —
|
||
# greift, wenn Hermes steht oder fehlt (die Homelab-Instanz hat gar kein Hermes).
|
||
# Letzter Ausweg: Logfile + wall.
|
||
#
|
||
# Nutzung: notify.sh "Nachricht" (oder via stdin: echo msg | notify.sh)
|
||
# notify.sh -s "[Update]" "Text" (Betreffzeile voranstellen)
|
||
# notify.sh -d -s "[Update]" "…" (dringend: geht auch nachts sofort raus)
|
||
#
|
||
# Nachtruhe (User-Entscheid 24.09.2026): Zwischen 00:00 und 06:59 wird alles, was nicht dringend
|
||
# ist, in der Nacht-Warteschlange gesammelt und um 07:00 von morgenmeldung.sh als EINE Meldung
|
||
# verschickt. Dringend ist: -d, ein Betreff mit „Alarm“/„Notfall“ oder ein Text, der mit
|
||
# „KRITISCH“ beginnt. (Bis 24.09. lieferte niemand die Warteschlange aus — seit dem 21.08.
|
||
# gingen so alle Nachtmeldungen verloren, auch die Sonntags-Updates.)
|
||
#
|
||
# Exit 0 = zugestellt (Telegram, Fallback-Log oder Nacht-Warteschlange).
|
||
# MC_NOTIFY_STRICT=1: Exit 1, wenn Telegram scheitert (für morgenmeldung.sh, damit nichts verloren geht).
|
||
# Test-Schalter: MC_NOTIFY_STUNDE (Stunde vortäuschen), MC_NIGHT_QUEUE, MC_NOTIFY_HERMES (hermes-Befehl),
|
||
# MC_TELEGRAM_ENV (Datei mit TELEGRAM_BOT_TOKEN/TELEGRAM_HOME_CHANNEL), MC_TELEGRAM_API (Bot-API-Adresse),
|
||
# MC_NOTIFY_OHNE_WALL=1 (kein wall im letzten Ausweg).
|
||
set -uo pipefail
|
||
|
||
LOG="${MC_NOTIFY_LOG:-$HOME/mc2-notify.log}"
|
||
NIGHT_QUEUE="${MC_NIGHT_QUEUE:-$HOME/.hermes/night-queue.txt}"
|
||
HERMES_BIN="${MC_NOTIFY_HERMES:-hermes}"
|
||
TG_ENV="${MC_TELEGRAM_ENV:-$HOME/.hermes/.env}"
|
||
TG_API="${MC_TELEGRAM_API:-https://api.telegram.org}"
|
||
SUBJECT=""
|
||
DRINGEND=0
|
||
|
||
while getopts ":s:d" opt; do
|
||
case "$opt" in
|
||
s) SUBJECT="$OPTARG" ;;
|
||
d) DRINGEND=1 ;;
|
||
*) echo "usage: notify.sh [-d] [-s subject] <nachricht>" >&2; exit 2 ;;
|
||
esac
|
||
done
|
||
shift $((OPTIND - 1))
|
||
|
||
MSG="${1:-}"
|
||
[ -z "$MSG" ] && [ ! -t 0 ] && MSG="$(cat)"
|
||
if [ -z "$MSG" ]; then
|
||
# Bei leerer Pipe-Eingabe lautlos beenden (wichtig für Cron-Skripte ohne Ausgabe)
|
||
exit 0
|
||
fi
|
||
|
||
TS="$(date '+%Y-%m-%d %H:%M:%S')"
|
||
|
||
# Lucy-Briefkasten (Proaktivität A3): Meldung zusätzlich an MC2 spiegeln — die Desktop-Lucy
|
||
# spricht sie dann von sich aus. Best-effort (darf den Telegram-Weg nie aufhalten).
|
||
# MC_NOTIFY_NO_ANNOUNCE=1 unterdrückt das (der Health-Wächter legt seine Einträge selbst ab).
|
||
if [ "${MC_NOTIFY_NO_ANNOUNCE:-0}" != "1" ]; then
|
||
curl -sf -m 3 -X POST "${MC_ANNOUNCE_URL:-http://127.0.0.1:9001/api/voice/announce}" \
|
||
-H 'Content-Type: application/json' \
|
||
--data "$(python3 - "$SUBJECT" "$MSG" <<'PY'
|
||
import json, sys
|
||
print(json.dumps({"text": sys.argv[2], "subject": sys.argv[1], "source": "notify"}))
|
||
PY
|
||
)" >/dev/null 2>&1 || true
|
||
fi
|
||
|
||
# Dringend ist, was nicht bis zum Morgen warten darf.
|
||
case "${SUBJECT,,}" in
|
||
*alarm*|*notfall*) DRINGEND=1 ;;
|
||
esac
|
||
case "$MSG" in
|
||
KRITISCH*) DRINGEND=1 ;;
|
||
esac
|
||
|
||
# Lucys Stimme (User-Wunsch 25.09.2026: „Hermes' Persona ist Lucy — das muss sich in all ihren Meldungen
|
||
# widerspiegeln“). Was über Telegram rausgeht, kommt von ihrem Bot, also spricht sie: Anrede „Commander“, knapp,
|
||
# sachlich, ohne Emojis und Ausrufe (~/.hermes/SOUL.md). Die Kopfzeile folgt aus dem Betreff; beginnt die Nachricht
|
||
# schon mit ihrer Anrede (Morgenmeldung, von Lucy selbst formulierte Berichte), bleibt sie weg. Melde-Log und
|
||
# Auswertungen (update_verlauf.py, protokoll.py) behalten Betreff und Wortlaut wie bisher.
|
||
lucy_kopf() {
|
||
case "$MSG" in
|
||
Commander*|"Guten Morgen, Commander"*) return 0 ;;
|
||
esac
|
||
case "${SUBJECT,,}" in
|
||
*test*) echo "Commander, ein Test des Meldewegs." ; return 0 ;;
|
||
*morgenmeldung*) echo "Guten Morgen, Commander." ; return 0 ;;
|
||
esac
|
||
if [ "$DRINGEND" = "1" ]; then
|
||
echo "Commander, das ist dringend."
|
||
return 0
|
||
fi
|
||
case "${SUBJECT,,}" in
|
||
*homelab-problem*) echo "Commander, im Homelab stimmt etwas nicht." ;;
|
||
*"homelab wieder ok"*) echo "Commander, Entwarnung im Homelab." ;;
|
||
*homelab-update*) echo "Commander, Bericht aus dem Homelab." ;;
|
||
*box-problem*) echo "Commander, auf der Box stimmt etwas nicht." ;;
|
||
*"box wieder ok"*) echo "Commander, Entwarnung auf der Box." ;;
|
||
*update*) echo "Commander, Bericht von der Wartung." ;;
|
||
*deploy*) echo "Commander, Bericht vom Deploy." ;;
|
||
*erinnerung*) echo "Commander, eine Erinnerung." ;;
|
||
*radar*) echo "Commander, Bericht vom Modell-Radar." ;;
|
||
"") echo "Commander, kurz gesagt:" ;;
|
||
*) echo "Commander, $(printf '%s' "$SUBJECT" | tr -d '[]')." ;;
|
||
esac
|
||
}
|
||
|
||
# Dasselbe als kurze Marke für eine Zeile der Morgenmeldung („Homelab, Störung: …“ statt „[Homelab-Problem]: …“).
|
||
lucy_marke() {
|
||
case "${SUBJECT,,}" in
|
||
"") return 0 ;;
|
||
*alarm*|*notfall*) echo "Dringend" ;;
|
||
*homelab-problem*) echo "Homelab, Störung" ;;
|
||
*"homelab wieder ok"*) echo "Homelab, Entwarnung" ;;
|
||
*homelab-update*) echo "Homelab, Update" ;;
|
||
*box-problem*) echo "Box, Störung" ;;
|
||
*"box wieder ok"*) echo "Box, Entwarnung" ;;
|
||
*box-update*) echo "Box, Wartung" ;;
|
||
*) printf '%s' "$SUBJECT" | tr -d '[]' ;;
|
||
esac
|
||
}
|
||
KOPF="$(lucy_kopf)"
|
||
|
||
# Nachtruhe: 00:00–06:59 sammeln, außer es ist dringend. 10#… verhindert, dass „08“/„09“ als
|
||
# Oktalzahl gelesen werden.
|
||
HOUR="${MC_NOTIFY_STUNDE:-$(date +%H)}"
|
||
if [ "$DRINGEND" != "1" ] && [ "$((10#$HOUR))" -lt 7 ]; then
|
||
mkdir -p "$(dirname "$NIGHT_QUEUE")"
|
||
MARKE="$(lucy_marke)"
|
||
printf -- '- %s%s\n' "${MARKE:+$MARKE: }" "$MSG" >> "$NIGHT_QUEUE"
|
||
# Wortlaut „QUEUED für Morgen-Digest“ nicht ändern: services/update_verlauf.py liest ihn.
|
||
echo "$TS QUEUED für Morgen-Digest: $MSG" >> "$LOG"
|
||
exit 0
|
||
fi
|
||
|
||
# Primärweg: Telegram via hermes send (Login-Shell-PATH, falls aus Timer/cron aufgerufen). Die Kopfzeile ist Lucys.
|
||
if [ -n "$KOPF" ]; then
|
||
SEND_OUT="$(bash -lc '"$1" send --to telegram --subject "$2" -- "$3"' _ "$HERMES_BIN" "$KOPF" "$MSG" 2>&1)"
|
||
else
|
||
SEND_OUT="$(bash -lc '"$1" send --to telegram -- "$2"' _ "$HERMES_BIN" "$MSG" 2>&1)"
|
||
fi
|
||
if [ $? -eq 0 ]; then
|
||
echo "$TS OK telegram: $MSG" >> "$LOG"
|
||
exit 0
|
||
fi
|
||
|
||
# Zweitweg: direkt an die Bot-API. Das Token geht über --config auf stdin an curl, damit es nicht
|
||
# in der Prozessliste steht.
|
||
env_wert() {
|
||
local wert
|
||
wert="$(grep -E "^(export +)?$1=" "$TG_ENV" 2>/dev/null | tail -n 1)"
|
||
wert="${wert#*=}"
|
||
wert="${wert%$'\r'}"
|
||
wert="${wert%\"}"; wert="${wert#\"}"
|
||
wert="${wert%\'}"; wert="${wert#\'}"
|
||
printf '%s' "$wert"
|
||
}
|
||
# Warum der Zweitweg scheiterte, steht seit 24.09.2026 mit im FALLBACK-Eintrag (der Telegram-Test der Oberfläche
|
||
# zeigt es an). Ohne Klammern im Text: services/update_verlauf.py trennt den Eintrag an „): “.
|
||
DIREKT_GRUND=""
|
||
telegram_direkt() {
|
||
local token chat thread text rc
|
||
if [ ! -r "$TG_ENV" ]; then
|
||
DIREKT_GRUND="Zugangsdatei $TG_ENV fehlt oder ist nicht lesbar"
|
||
return 1
|
||
fi
|
||
token="$(env_wert TELEGRAM_BOT_TOKEN)"
|
||
chat="$(env_wert TELEGRAM_HOME_CHANNEL)"
|
||
thread="$(env_wert TELEGRAM_HOME_CHANNEL_THREAD_ID)"
|
||
if [ -z "$token" ] || [ -z "$chat" ]; then
|
||
DIREKT_GRUND="TELEGRAM_BOT_TOKEN oder TELEGRAM_HOME_CHANNEL fehlt in $TG_ENV"
|
||
return 1
|
||
fi
|
||
text="$MSG"
|
||
[ -n "$KOPF" ] && text="$KOPF
|
||
$MSG"
|
||
printf 'url = "%s/bot%s/sendMessage"\n' "$TG_API" "$token" \
|
||
| curl -sf -m 15 -o /dev/null --config - \
|
||
--data-urlencode "chat_id=$chat" --data-urlencode "text=$text" \
|
||
${thread:+--data-urlencode "message_thread_id=$thread"}
|
||
rc=$?
|
||
case "$rc" in
|
||
0) return 0 ;;
|
||
22) DIREKT_GRUND="Telegram lehnt ab, HTTP-Fehler – Token oder Chat falsch?" ;;
|
||
6|7) DIREKT_GRUND="Telegram nicht erreichbar, keine Verbindung" ;;
|
||
28) DIREKT_GRUND="Telegram antwortet nicht rechtzeitig" ;;
|
||
*) DIREKT_GRUND="curl endete mit Code $rc" ;;
|
||
esac
|
||
return 1
|
||
}
|
||
if telegram_direkt; then
|
||
# „OK telegram direkt“: services/update_verlauf.py erkennt beide Wege.
|
||
echo "$TS OK telegram direkt: $MSG" >> "$LOG"
|
||
exit 0
|
||
fi
|
||
|
||
# Letzter Ausweg: Logfile + wall — Meldung darf nie verloren gehen.
|
||
GRUND="$SEND_OUT"
|
||
[ -n "$DIREKT_GRUND" ] && GRUND="${GRUND:+$GRUND; }Zweitweg: $DIREKT_GRUND"
|
||
echo "$TS FALLBACK (telegram fehlgeschlagen: $GRUND): $MSG" >> "$LOG"
|
||
if [ "${MC_NOTIFY_OHNE_WALL:-0}" != "1" ]; then
|
||
printf '%s\n' "MC2-Meldung: ${SUBJECT:+$SUBJECT }$MSG" | wall 2>/dev/null || true
|
||
fi
|
||
echo "WARN: Telegram fehlgeschlagen, in $LOG protokolliert" >&2
|
||
[ "${MC_NOTIFY_STRICT:-0}" = "1" ] && exit 1
|
||
exit 0
|