Files
mission-control-v2/deploy/notify.sh
T
HitonabiandClaude Opus 5.5 c31ef827fc meldungen: Lucy spricht – jede Telegram-Meldung in ihrem Ton
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>
2026-09-25 13:07:42 +02:00

203 lines
8.4 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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