docs: Durchsicht -- die Sicherung liegt neben dem Original, und niemand merkt ihren Ausfall
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:
@@ -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.
|
||||
Reference in New Issue
Block a user