docs: Durchsicht -- die Sicherung liegt neben dem Original, und niemand merkt ihren Ausfall
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s

Gesucht wurde nach denselben Fehlermustern, die beim Bau von d4rk_media
aufgefallen sind. 24105 Zeilen liest niemand am Stueck; gesucht wurde gezielt
nach nie aufgerufenen Aufraeumfunktionen, Wachen an '*', SQL mit eingesetzten
Zeichenketten, fetch auf fremde Adressen, ungelesenen Antwortkoerpern,
Schleifen mit Zeitlimit und Sicherungen ohne Gegenprobe.

AN DIESEM BOT WURDE NICHTS GEAENDERT. Die Entscheidungen gehoeren dem
Betreiber.

DER WICHTIGSTE BEFUND: Datenbank (5,0 MB) und alle vierzehn Sicherungen
(5,7 MB) liegen im SELBEN Volume ecobot_ecobot_data. Geht es verloren, geht
beides zusammen verloren -- das ist keine Sicherung, das ist eine Kopie. Der
eingebaute Ausweg (Upload in einen privaten Discord-Kanal) laeuft nie, weil
backup_channel_id nicht gesetzt ist.

DAZU: niemand prueft last_backup. Es wird geschrieben und im Panel ANGEZEIGT,
aber nichts vergleicht es mit heute. Und weil der Zeitplan auf getHours() === 3
steht, faellt der Tag still aus, wenn der Bot waehrend dieser Stunde unten ist
-- bei einem Bot, der nach jedem Update neu startet, kein Sonderfall. Die
Maschinerie dafuer ist vollstaendig da (dmAdmin, brandEmbed, Incidents) und
wird nicht benutzt.

WEITER: "zu gross fuer Discord" endet in einem console.warn, das niemand liest;
die Archive wachsen um rund 70 KB je Tag auf eine Grenze von 9 MB zu. Das
Archiv wird nie ausgepackt und gegengeprueft. Und pruneMessageCache wird
nirgends aufgerufen -- exakt dasselbe Muster wie pruneEvents in d4rk_media,
heute mit 18 Zeilen harmlos.

WAS GEPRUEFT WURDE UND IN ORDNUNG IST, damit es niemand ein zweites Mal prueft:
db.backup() statt Dateikopie ist richtig und WAL-sicher; das gefaehrlich
aussehende ORDER BY ${ord} ist eine feste Weissliste; die oeffentliche
Serverliste zaehlt ihre Felder einzeln auf; die zwei bekannten Geheimnisse
gehen nur als Ja/Nein hinaus; der Spielserver-Monitor nutzt bereits
Promise.all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 01:26:36 +02:00
co-authored by Claude Opus 5
parent 5ac079699c
commit 1808b7c40b
+191
View File
@@ -0,0 +1,191 @@
# Durchsicht am 12.08.2026
Gesucht wurde nach **denselben Fehlermustern**, die beim Bau von `d4rk_media`
aufgefallen sind — nicht nach allem Denkbaren. 24 105 Zeilen in 108 Dateien
liest niemand am Stück; gesucht wurde gezielt nach:
- Aufräumfunktionen, die nie aufgerufen werden
- Wachen, die an `'*'` hängen statt an den eigenen Pfaden
- SQL, in das Zeichenketten eingesetzt werden
- `fetch` auf Adressen, die von außen kommen
- Antwortkörper, die niemand liest
- Schleifen mit Zeitlimit, die nacheinander laufen
- Sicherungen, die nie zurückgespielt wurden
**An diesem Bot wurde nichts geändert.** Was hier steht, ist ein Befund und
kein Eingriff — die Entscheidungen darüber gehören dir.
---
## 1 · Die Sicherung hat keinen sicheren Ort
**Das ist der wichtigste Befund.** Gemessen:
```
docker inspect d4rkbot
volume ecobot_ecobot_data -> /app/data
/app/data/ecobot.db 5,0 MB
/app/data/backups/ 5,7 MB (14 Stück, täglich 03:0x)
```
**Datenbank und alle vierzehn Sicherungen liegen im selben Volume.** Geht das
Volume verloren, geht beides zusammen verloren. Das ist keine Sicherung, das
ist eine Kopie.
Vorgesehen ist ein Ausweg — der Upload in einen privaten Discord-Kanal:
```js
const channelId = getSetting('backup_channel_id');
if (channelId && client) { }
```
**Der Kanal ist nicht gesetzt.** Der Zweig läuft also nie, und der einzige
vorgesehene Weg aus dem Volume heraus ist unbenutzt.
Was der Code *richtig* macht, damit das nicht untergeht: `db.backup(rawFile)`
die Online-Sicherungsschnittstelle von SQLite, WAL-sicher. Eine Dateikopie wäre
still unvollständig gewesen. Das ist derselbe Griff wie in `d4rk_media` und er
ist korrekt.
**Zwei Wege:**
1. `backup_channel_id` setzen — der eingebaute Weg, kostet einen Klick.
2. Wie bei `d4rk_media`: ein Skript auf dem Wirt, das das Archiv abholt und in
die Nextcloud legt. Dort liegt schon eines (`tools/sichern.ps1`), das Muster
ließe sich übernehmen.
---
## 2 · Niemand merkt, wenn die Sicherung ausbleibt
`last_backup` wird geschrieben und im Panel **angezeigt** — aber nirgends
**geprüft**:
```
src/backup.js:57 setSetting('last_backup', …) geschrieben
src/backup.js:70 … === today ? return als Tagesbremse gelesen
src/web/api.js:1003 lastBackup: getSetting(…) angezeigt
```
Niemand vergleicht ihn mit heute und beschwert sich. Dazu kommt das Zeitfenster:
```js
if (now.getHours() !== 3) return;
```
Ist der Bot während der Stunde 03 unten, fällt der Tag aus — still. Bei einem
Bot, der nach jedem Update neu startet, ist das kein Sonderfall.
**Die Maschinerie dafür ist vollständig da und wird nicht benutzt**: `dmAdmin`,
`brandEmbed`, `startIncident`/`endIncident`, der Wächter mit seiner
Fehlerschwelle. Es fehlt der eine Aufruf, der `last_backup` liest.
`d4rk_media` hat für genau das `pruefeSicherung()` — 26 Stunden Schwelle,
meldet einmal je Zustand.
---
## 3 · Zu groß für Discord endet in der Konsole
```js
} else {
console.warn(`[backup] ${gzFile} zu groß für Discord-Upload (${sizeBytes} B)`);
}
```
Die Grenze steht bei 9 MB. Die Archive wachsen sichtbar:
```
08.08. 689 553 B
09.08. 762 371 B
10.08. 836 677 B
11.08. 906 916 B
```
Rund 70 KB je Tag. Bis 9 MB ist Luft für etwa drei Monate — und wenn sie reißt,
hört die Kopie **still** auf. Eine Konsolenzeile in einem Container liest
niemand.
Das ist derselbe Fehler wie ein Knopf ohne Rückmeldung, nur langsamer.
---
## 4 · Das Archiv wird nie gegengeprüft
Es wird geschrieben, gezippt, hochgeladen — und nie ausgepackt, nie geöffnet,
nie gezählt. Ein defektes gzip oder eine halbe Datei fiele erst an dem Tag auf,
an dem man sie braucht.
`tools/sichern.ps1` in `d4rk_media` packt sein Archiv aus, öffnet die Datenbank
darin und zählt die Zeilen — und `tools/zurueckspielen.ps1` fährt daraus einen
zweiten Dienst hoch und lässt ihn drei zufällige Dateien ausliefern. **Genau
diese Übung hat dort gestern einen echten Fehler gefunden** (ein stehen
gebliebenes `media.db-wal`, das die wiederhergestellte Datenbank leer aussehen
ließ).
Ein Backup, das man nie zurückgespielt hat, ist keins.
---
## 5 · `pruneMessageCache` wird nie aufgerufen
```
prunePlayerHistory 1 Aufruf ausserhalb db.js
pruneWatchdogHistory 1
pruneHeartbeat 1
pruneIncidents 1
pruneMessageCache 0 <-
```
Exakt dasselbe Muster, das in `d4rk_media` bei `pruneEvents` gefunden wurde: die
Funktion ist da, die Aufbewahrungsfrist ist dokumentiert, der Aufruf fehlt.
**Heute harmlos** — die Tabelle hat 18 Zeilen. Aber die Frist greift nicht, und
wenn der Nachrichten-Zwischenspeicher je stärker benutzt wird, wächst er ohne
Ende.
---
## Geprüft und in Ordnung
Das gehört genauso hierher, damit es niemand ein zweites Mal prüft.
| Was | Ergebnis |
|---|---|
| `db.backup()` statt Dateikopie | **richtig**, WAL-sicher — und im Kommentar begründet |
| `ORDER BY ${ord}` in `db.js:414` | **sicher**: `SORTIERUNG` ist ein festes Objekt mit drei Werten, die Anweisungen werden einmalig daraus vorbereitet. Der Aufrufer wählt nur einen Schlüssel. |
| Öffentliche Serverliste `/api/servers` | **sauber**: die Felder sind einzeln aufgezählt, „bewusst ohne query_url/host/port" |
| Geheimnisse in `/api/settings` | die zwei bekannten gehen als **Ja/Nein** hinaus (`gitea_api_token_set`, `twitch_creds_set`) |
| Spielserver-Monitor | nutzt schon `Promise.all` — die Schleife läuft nebeneinander |
| Zugangscodes in der Datenbank | genau einer (LS25, 16 Zeichen), und er geht nur an Panel-Nutzer mit `server`-Recht |
---
## Beobachtungen ohne Handlungsbedarf
**Panel-Nutzer können den Bot beliebige Adressen abrufen lassen** — Watchdog,
Spielserver-Abfrage, LS-Feed nehmen alle eine URL aus der Datenbank. Bei
`d4rk_media` war das eine echte Lücke, weil dort die *Antwort* als Datei
zurückkommt. Hier kommt nur „erreichbar ja/nein" heraus, und wer das Panel hat,
kann ohnehin mehr. Kein Handlungsbedarf — nur zu wissen, falls je ein Weg
dazukommt, der den Antwortkörper weitergibt.
**`currentSettings()` gibt jeden von einem Modul angemeldeten Textschlüssel im
Klartext aus** (`src/web/api.js:901`). Heute steht dort nichts Heikles. Aber ein
künftiges Modul mit einem Feld `api_token` ginge ungefragt mit hinaus, ohne dass
jemand es bemerkt. Eine Namensregel („endet auf `_secret`/`_token` → nur als
Ja/Nein") wäre eine Zeile und schlösse das für immer.
---
## Wenn ich eins zuerst täte
**Punkt 1 und 2 zusammen**, denn sie sind dasselbe Problem von zwei Seiten: die
Sicherung hat keinen zweiten Ort, und niemand würde merken, wenn sie ganz
ausbliebe. Beides zusammen heißt: der Bot hat faktisch keine Sicherung, sondern
eine Kopie neben dem Original, deren Ausfall unbemerkt bliebe.
Der billigste Schritt ist `backup_channel_id` zu setzen — ein Klick, und der
vorgesehene Weg funktioniert. Der solide Schritt ist derselbe wie bei
`d4rk_media`: abholen, prüfen, in die Nextcloud, und ein Wachhund darauf.