In der Statusdatei des Gegenbuchs stand fuer JEDEN Exit-2 derselbe Satz:
"BEFUND - Widerspruch zwischen CRM und Gegenbuch". Ein echter
Widerspruch sah damit genauso aus wie eine quittierpflichtige
Erstsiegelung.
Aufgefallen im Betrieb: Der Betreiber las "BEFUND" auf Staging und
konnte nicht entscheiden, ob er handeln muss - obwohl die Kette dort
valid:true meldet und der Alarm nur den erwarteten Siegelwechsel betraf.
Wer ausschliesslich die Statusdatei liest, und genau dafuer ist sie da
("damit eine Ueberwachung sie abgreifen kann, ohne Logs zu
durchsuchen"), bekam ein Etikett ohne Inhalt.
Der Lauf wird jetzt mitgeschnitten; die erste ALARM-Zeile landet als
"Grund:" in status.txt, ohne Alarm die erste Ausgabezeile.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
105 lines
4.4 KiB
Bash
Executable File
105 lines
4.4 KiB
Bash
Executable File
#!/bin/bash
|
||
# Gegenbuch-Schleife: einrichten, dann in festem Takt beglaubigen.
|
||
set -uo pipefail
|
||
|
||
: "${INSTANZ:?INSTANZ fehlt (z. B. prod oder staging)}"
|
||
: "${CRM_URL:?CRM_URL fehlt – von welcher OpenCRM-Instanz soll geholt werden?}"
|
||
: "${CRM_EMAIL:?CRM_EMAIL fehlt – Dienstkonto im CRM mit dem Recht audit:read}"
|
||
: "${CRM_PASSWORD:?CRM_PASSWORD fehlt – Passwort dieses Dienstkontos}"
|
||
|
||
INTERVALL="${NOTARY_INTERVAL:-3600}"
|
||
BUCH=/gegenbuch/buch
|
||
SCHLUESSEL=/gegenbuch/schluessel
|
||
export NOTARY_STATE_FILE=/gegenbuch/beobachtungen.jsonl
|
||
export NOTARY_ALLOW_LOCAL=true # Buch liegt auf dieser Maschine
|
||
export NOTARY_PUSH=false # es gibt kein Ziel zum Pushen
|
||
|
||
# --- Rechte geraderuecken, dann Privilegien abgeben ------------------------
|
||
# Laeuft der Container als root, gehoert das Bind-Mount-Verzeichnis vermutlich
|
||
# ebenfalls root (typisch, wenn das Projekt als root geklont wurde). Wir setzen
|
||
# es auf den Arbeitsbenutzer um und starten uns selbst als dieser neu.
|
||
BENUTZER_UID="${PUID:-1000}"
|
||
BENUTZER_GID="${PGID:-1000}"
|
||
if [ "$(id -u)" = "0" ]; then
|
||
if ! chown -R "$BENUTZER_UID:$BENUTZER_GID" /gegenbuch 2>/dev/null; then
|
||
echo "[$INSTANZ] HINWEIS: Rechte auf /gegenbuch liessen sich nicht setzen." >&2
|
||
echo "[$INSTANZ] Falls es gleich an fehlenden Schreibrechten scheitert, auf dem Host:" >&2
|
||
echo "[$INSTANZ] chown -R $BENUTZER_UID:$BENUTZER_GID <datenverzeichnis>" >&2
|
||
fi
|
||
exec setpriv --reuid="$BENUTZER_UID" --regid="$BENUTZER_GID" --init-groups \
|
||
--inh-caps=-all "$0" "$@"
|
||
fi
|
||
|
||
echo "[$INSTANZ] Gegenbuch startet – Quelle: $CRM_URL, Takt: ${INTERVALL}s"
|
||
|
||
# --- Signaturschlüssel: einmalig auf DIESER Maschine erzeugen ---------------
|
||
# Er verlässt den Rechner nie. OpenCRM kennt ihn nicht.
|
||
mkdir -p "$SCHLUESSEL"
|
||
if [ ! -f "$SCHLUESSEL/id_ed25519" ]; then
|
||
echo "[$INSTANZ] Erzeuge Signaturschlüssel..."
|
||
ssh-keygen -t ed25519 -f "$SCHLUESSEL/id_ed25519" -N "" -C "gegenbuch-$INSTANZ" -q
|
||
fi
|
||
chmod 600 "$SCHLUESSEL/id_ed25519"
|
||
if [ ! -f "$SCHLUESSEL/allowed_signers" ]; then
|
||
echo "gegenbuch-$INSTANZ namespaces=\"git\" $(cat "$SCHLUESSEL/id_ed25519.pub")" \
|
||
> "$SCHLUESSEL/allowed_signers"
|
||
fi
|
||
|
||
# --- Buch anlegen, falls noch nicht vorhanden ------------------------------
|
||
if [ ! -d "$BUCH/.git" ]; then
|
||
echo "[$INSTANZ] Lege Gegenbuch an..."
|
||
mkdir -p "$BUCH"
|
||
git -C "$BUCH" init -q -b main
|
||
fi
|
||
cd "$BUCH" || exit 1
|
||
|
||
git config user.email "${NOTAR_EMAIL:-gegenbuch@localhost}"
|
||
git config user.name "Gegenbuch $INSTANZ"
|
||
git config gpg.format ssh
|
||
git config user.signingkey "$SCHLUESSEL/id_ed25519.pub"
|
||
git config gpg.ssh.allowedSignersFile "$SCHLUESSEL/allowed_signers"
|
||
|
||
# Ein erster Commit, damit es eine Historie gibt, an die angehängt werden kann.
|
||
if ! git rev-parse HEAD >/dev/null 2>&1; then
|
||
git commit -q -S --allow-empty -m "Gegenbuch $INSTANZ angelegt"
|
||
fi
|
||
|
||
# --- Schleife ---------------------------------------------------------------
|
||
# Jeder Rückgabecode außer 0 gehört angesehen. Der letzte Stand steht
|
||
# zusätzlich in status.txt, damit eine Überwachung ihn abgreifen kann, ohne
|
||
# Logs zu durchsuchen.
|
||
while true; do
|
||
ZEIT=$(date -Iseconds)
|
||
# Ausgabe mitschneiden, um den GRUND in die Statusdatei zu bekommen.
|
||
#
|
||
# Vorher stand dort nur das Etikett ("BEFUND"), und das ist fuer jeden
|
||
# Exit-2 dasselbe - ein echter Widerspruch sieht darin aus wie eine
|
||
# quittierpflichtige Erstsiegelung. Wer nur die Statusdatei liest, kann
|
||
# nicht entscheiden, ob er handeln muss. Genau die Sorte Signal, gegen die
|
||
# dieses Werkzeug gebaut ist.
|
||
node /opt/notary/notary.mjs > /tmp/lauf.log 2>&1
|
||
CODE=$?
|
||
cat /tmp/lauf.log
|
||
case $CODE in
|
||
0) LAGE="in Ordnung" ;;
|
||
1) LAGE="Betriebsfehler (Konfiguration, CRM nicht erreichbar, Commit)" ;;
|
||
2) LAGE="BEFUND – Widerspruch zwischen CRM und Gegenbuch" ;;
|
||
3) LAGE="nicht abschliessend feststellbar (u. a. erster Lauf)" ;;
|
||
4) LAGE="Gedaechtnis fehlt – Bestaetigung noetig, siehe README" ;;
|
||
5) LAGE="Anker unvollstaendig" ;;
|
||
*) LAGE="unbekannter Code" ;;
|
||
esac
|
||
echo "[$INSTANZ] $ZEIT exit=$CODE ($LAGE)"
|
||
# Erste erklaerende Zeile mitnehmen: bei Alarm die ALARM-Zeile, sonst die
|
||
# erste Ausgabezeile ueberhaupt.
|
||
GRUND=$(grep -m1 '^ALARM: ' /tmp/lauf.log | sed 's/^ALARM: //')
|
||
if [ -z "$GRUND" ]; then
|
||
GRUND=$(grep -m1 -v '^[[:space:]]*$' /tmp/lauf.log)
|
||
fi
|
||
{
|
||
printf '%s exit=%s %s\n' "$ZEIT" "$CODE" "$LAGE"
|
||
[ -n "$GRUND" ] && printf 'Grund: %s\n' "$GRUND"
|
||
} > /gegenbuch/status.txt
|
||
sleep "$INTERVALL"
|
||
done
|