# 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.