Drei Befunde aus der Durchsicht: /health, Herunterfahren, ephemeral
1. /health konnte nicht fehlschlagen
app.get('/health', async () => ({ status: 'ok' }));
Ein fester Wert, ohne irgendetwas anzusehen. Das meldet "sauber", solange
der HTTP-Faden laeuft -- und der laeuft auch dann noch, wenn die Verbindung
zu Discord weg ist. Ein Bot ohne Gateway ist fuer alle draussen genauso weg
wie ein abgestuerzter, nur merkt es niemand. NPM fragt die Adresse an, zwei
Hosts, alle paar Minuten; sie hat immer 200 gesagt.
Das Bittere: die Auskunft lag laengst vor. heartbeat.js schreibt alle zwei
Minuten client.isReady() mit (gemessen an der laufenden Datenbank: 9557
Zeilen, letzter Schlag ok=1, 104 ms). Der Herzschlag wusste es, /health hat
nicht gefragt.
Jetzt geprueft: steht die Verbindung, und antwortet die Datenbank. Sonst 503
mit Grund -- 503 und nicht 500, das ist kein Programmfehler, sondern ein
Zustand, der vorbeigeht. Der Grund steht dabei, sonst faengt das Raten wieder
von vorn an.
Dazu ein HEALTHCHECK im Dockerfile, denn sonst reagiert niemand darauf.
Gemessen: d4rkbot hatte keinen (<nil>), d4rk-media hat einen und steht auf
"healthy". curl und wget fehlen im slim-Abbild, also Node selbst; die
Exitcodes sind gegengeprueft (erreichbar -> 0, tote Adresse -> 1).
Fuer die Datenbankprobe eine echte Abfrage und nicht existsSync auf die
Datei: die Datei ist auch dann noch da, wenn das Dateisystem nur noch lesbar
ist.
2. Kein Handler fuers Beenden
Kein SIGTERM, kein SIGINT im ganzen Quelltext. Bei `docker stop` wird der
Prozess nach der Schonfrist abgeraeumt, und zwei Dinge geben danach falsche
Auskunft: der Bot steht in Discord noch eine Weile als online, und die
Statuszeile am Sprachkanal behauptet weiter, es liefe Musik -- die ueberlebt
den Container, der sie geschrieben hat.
Reihenfolge ist wichtig: erst das Radio, denn zum Abraeumen der Statuszeile
braucht es die Verbindung zu Discord noch. Dann Webserver, dann Discord.
Jeder Schritt einzeln abgesichert, sonst verhindert ein Fehler beim
Aufraeumen das restliche Aufraeumen. Nach acht Sekunden bricht es selbst ab,
weil Docker nach zehn schiesst.
radio_state bleibt dabei absichtlich stehen -- der Merker ist genau dafuer
da, dass der Bot nach einem Deploy von selbst zurueckkommt.
3. ephemeral: true
Einzige Stelle im ganzen Bot, ueberall sonst steht flags:
MessageFlags.Ephemeral. discord.js 14.27 wertet es noch aus
(InteractionResponses.js:118), warnt aber, und in v15 faellt es weg -- dann
waere ausgerechnet die Fehlermeldung ploetzlich oeffentlich.
Was die Durchsicht sonst ergab, gehoert genauso hierher: 125 Routen
durchgezaehlt, keine einzige aendernde ohne Wache. Keine Nutzereingabe in
SQL. Der Mod-Download prueft gegen die Modliste statt gegen ein Namensmuster.
Die Sicherung packt ihr Archiv wieder aus und laesst integrity_check laufen.
Keine Geheimnisse im Log. Daran war nichts zu verbessern.
Ungeprueft geblieben: 76 Stellen mit stillschweigend verschlucktem Fehler
(catch {}), das Frontend, und ob NPM schon drosselt -- eine
Ratenbegrenzung hat die API naemlich nicht. Entschaerft dadurch, dass alle
aendernden Routen hinter einer Wache liegen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+53
-3
@@ -6,7 +6,7 @@ import { startWatchdog } from './bot/watchdog.js';
|
||||
import { scheduleBackups } from './backup.js';
|
||||
import { startServerMonitor } from './bot/server-monitor.js';
|
||||
import { startLsFarm } from './bot/ls-farm.js';
|
||||
import { startRadio } from './bot/radio.js';
|
||||
import { startRadio, radioBeenden } from './bot/radio.js';
|
||||
import { startGiveaways } from './bot/giveaways.js';
|
||||
import { startScheduledPosts } from './web/scheduled-posts.js';
|
||||
import { startSocialNotify } from './bot/social-notify.js';
|
||||
@@ -17,9 +17,59 @@ process.on('unhandledRejection', (error) => {
|
||||
console.error('[main] Unhandled Rejection:', error);
|
||||
});
|
||||
|
||||
let client = null;
|
||||
let app = null;
|
||||
|
||||
/**
|
||||
* Geordnet herunterfahren.
|
||||
*
|
||||
* Ohne das räumt `docker stop` den Prozess nach der Schonfrist einfach ab.
|
||||
* Zwei Dinge geben dann falsche Auskunft: der Bot steht in Discord noch eine
|
||||
* Weile als online, und die Statuszeile am Sprachkanal behauptet weiter, es
|
||||
* liefe Musik. Beides überlebt den Container, der es geschrieben hat.
|
||||
*
|
||||
* Reihenfolge ist wichtig: erst das Radio, denn zum Abräumen der Statuszeile
|
||||
* braucht es die Verbindung zu Discord noch.
|
||||
*/
|
||||
let laeuftAus = false;
|
||||
async function beenden(signal) {
|
||||
if (laeuftAus) return; // zweites SIGTERM nicht doppelt abarbeiten
|
||||
laeuftAus = true;
|
||||
console.log(`[main] ${signal} — fahre herunter`);
|
||||
|
||||
// Docker wartet vorgabemässig zehn Sekunden und schiesst dann. Lieber
|
||||
// vorher selbst aufhören, als mitten im Aufräumen abgeschossen zu werden.
|
||||
const frist = setTimeout(() => {
|
||||
console.error('[main] Herunterfahren dauert zu lange — breche ab');
|
||||
process.exit(1);
|
||||
}, 8000);
|
||||
frist.unref?.();
|
||||
|
||||
for (const [was, tun] of [
|
||||
['Radio', () => radioBeenden()],
|
||||
['Webserver', () => app?.close()],
|
||||
['Discord', () => client?.destroy()],
|
||||
]) {
|
||||
try {
|
||||
await tun();
|
||||
} catch (error) {
|
||||
// Ein Fehler beim Aufräumen darf das restliche Aufräumen nicht
|
||||
// verhindern — sonst bleibt wegen der Statuszeile der Bot online.
|
||||
console.error(`[main] ${was} beim Herunterfahren:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
clearTimeout(frist);
|
||||
console.log('[main] beendet');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
process.on('SIGTERM', () => beenden('SIGTERM'));
|
||||
process.on('SIGINT', () => beenden('SIGINT'));
|
||||
|
||||
try {
|
||||
const client = await startBot();
|
||||
await startWebServer(client);
|
||||
client = await startBot();
|
||||
app = await startWebServer(client);
|
||||
scheduleWeeklyRecap(client);
|
||||
startWatchdog(client);
|
||||
scheduleBackups(client);
|
||||
|
||||
Reference in New Issue
Block a user