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>
48 lines
1.8 KiB
Docker
48 lines
1.8 KiB
Docker
# EcoBot — Discord-Bot + Webinterface
|
|
# Stage 1: React-Frontend bauen
|
|
FROM node:22-slim AS frontend
|
|
WORKDIR /build
|
|
COPY frontend/package.json frontend/package-lock.json ./
|
|
RUN npm ci
|
|
COPY frontend ./
|
|
RUN npm run build
|
|
|
|
# Stage 2: Runtime (slim statt alpine: better-sqlite3 liefert glibc-Prebuilds)
|
|
FROM node:22-slim
|
|
|
|
# Fonts für die Willkommens-Karten (sharp/librsvg braucht installierte Schriften),
|
|
# git für die Repo-Backups (git clone --mirror + bundle),
|
|
# ffmpeg fürs Radio: es holt den MP3-Strom und gibt direkt Ogg/Opus aus — damit
|
|
# braucht es keine Opus-Bibliothek in Node, die erst gebaut werden müsste
|
|
RUN apt-get update && apt-get install -y --no-install-recommends fonts-dejavu-core git ffmpeg \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
WORKDIR /app
|
|
|
|
# Erst nur Manifest kopieren → Docker-Layer-Cache für npm ci
|
|
COPY package.json package-lock.json ./
|
|
RUN npm ci --omit=dev
|
|
|
|
COPY src ./src
|
|
# Die Prüfwerkzeuge müssen dort laufen, wo der Fehler auftritt — im Container.
|
|
# Ohne diese Zeile lief `docker exec … node tools/udp-pruefen.mjs` ins Leere,
|
|
# obwohl genau das im Kopf des Skripts steht.
|
|
COPY tools ./tools
|
|
COPY --from=frontend /build/dist ./frontend/dist
|
|
|
|
# Laufzeitdaten (SQLite) landen in /app/data → Volume
|
|
RUN mkdir -p /app/data
|
|
|
|
ENV NODE_ENV=production
|
|
|
|
# Ohne diese Zeile weiss Docker nur, DASS der Prozess laeuft — nicht, ob der
|
|
# Bot noch mit Discord verbunden ist. Genau das beantwortet /health.
|
|
#
|
|
# `curl` und `wget` fehlen im slim-Abbild (nachgemessen), also Node selbst.
|
|
# Die Schonfrist ist grosszuegig: der Anmeldevorgang bei Discord dauert, und
|
|
# ein Neustart soll nicht als Ausfall gelten.
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=45s --retries=3 \
|
|
CMD node -e "fetch('http://127.0.0.1:'+(process.env.HTTP_PORT||3080)+'/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
|
|
CMD ["node", "src/index.js"]
|