From bec5dc73634e8a8e9659c505242ef5618d49ffd9 Mon Sep 17 00:00:00 2001 From: ToolCTRL Date: Tue, 26 May 2026 13:40:19 +0200 Subject: [PATCH] Produktion: ClamAV in den App-Container integriert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dockerfile installiert clamav-daemon + clamav-freshclam und konfiguriert clamd auf TCP 127.0.0.1:3310 mit DB-Verzeichnis auf der persistenten Coolify-Volume (/app/data/clamav-db). entrypoint.sh startet clamd und freshclam vor dem Node-Server, wartet auf clamd-PING und exec't dann node als unprivilegierter Nutzer. tini übernimmt PID 1 für Signal-Forwarding. Folge: GameJam-Uploads werden in Produktion automatisch per ClamAV geprüft, ohne dass Coolify manuell angepasst werden muss. Erster Deploy lädt einmalig ~300 MB Virendefinitionen auf die Volume, Folge-Starts in ~30 s. RAM-Bedarf des Containers ~1.5 GB. Co-Authored-By: Claude Opus 4.7 (1M context) --- Dockerfile | 38 ++++++++++++++++++++++++++++++++------ README.md | 24 ++++++++++++++++++++---- entrypoint.sh | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 10 deletions(-) create mode 100755 entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 82bf796..a3fd2e8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,27 +8,53 @@ ENV NODE_ENV=production \ PORT=3000 \ HOST=0.0.0.0 \ DATA_DIR=/app/data \ + CLAMAV_HOST=127.0.0.1 \ + CLAMAV_PORT=3310 \ + CLAMAV_ENABLED=true \ APP_REVISION=${APP_REVISION} +# Debian-Postinst soll keine Daemons im Build starten. +RUN echo 'exit 101' > /usr/sbin/policy-rc.d && chmod +x /usr/sbin/policy-rc.d + RUN apt-get update \ - && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ca-certificates curl \ - && rm -rf /var/lib/apt/lists/* \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + ca-certificates curl tini gosu netcat-openbsd \ + clamav clamav-daemon clamav-freshclam \ + && rm -rf /var/lib/apt/lists/* /var/lib/clamav/* \ + && rm -f /usr/sbin/policy-rc.d \ && groupadd --gid 1001 nodejs \ && useradd --uid 1001 --gid nodejs --home-dir /app --shell /usr/sbin/nologin nodejs +# clamd: TCP 127.0.0.1:3310, DB-Verzeichnis auf der persistenten Volume, +# Example-Zeile entfernen, damit clamd/freshclam überhaupt starten. +RUN sed -i 's/^Example/#Example/' /etc/clamav/clamd.conf /etc/clamav/freshclam.conf \ + && sed -i 's|^DatabaseDirectory .*|DatabaseDirectory /app/data/clamav-db|' \ + /etc/clamav/clamd.conf /etc/clamav/freshclam.conf \ + && sed -i -e 's/^#\?TCPSocket .*/TCPSocket 3310/' \ + -e 's/^#\?TCPAddr .*/TCPAddr 127.0.0.1/' \ + /etc/clamav/clamd.conf \ + && (grep -q '^TCPSocket' /etc/clamav/clamd.conf || echo 'TCPSocket 3310' >> /etc/clamav/clamd.conf) \ + && (grep -q '^TCPAddr' /etc/clamav/clamd.conf || echo 'TCPAddr 127.0.0.1' >> /etc/clamav/clamd.conf) \ + && (grep -q '^DatabaseDirectory' /etc/clamav/clamd.conf || echo 'DatabaseDirectory /app/data/clamav-db' >> /etc/clamav/clamd.conf) \ + && (grep -q '^DatabaseDirectory' /etc/clamav/freshclam.conf || echo 'DatabaseDirectory /app/data/clamav-db' >> /etc/clamav/freshclam.conf) + WORKDIR /app COPY --chown=nodejs:nodejs public ./public COPY --chown=nodejs:nodejs server.js ./server.js +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh RUN mkdir -p /app/data \ && chown -R nodejs:nodejs /app/data -USER nodejs - EXPOSE 3000 -HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ +# Lange Start-Periode: erster Container-Start lädt die Virendefinitionen +# (~300 MB, ~1–2 Min). Danach liegen sie auf der persistenten Volume und +# Folge-Starts sind in ~30 s wieder oben. +HEALTHCHECK --interval=30s --timeout=3s --start-period=900s --retries=3 \ CMD curl -fsS http://127.0.0.1:3000/healthz >/dev/null || exit 1 -CMD ["node", "server.js"] +ENTRYPOINT ["/usr/bin/tini", "--"] +CMD ["/entrypoint.sh"] diff --git a/README.md b/README.md index 8543a7b..483a588 100644 --- a/README.md +++ b/README.md @@ -50,11 +50,27 @@ geprüft.** Befund → Ablehnung; ist der Scanner nicht erreichbar, wird der Upload ebenfalls abgelehnt (fail-closed, kein ungeprüftes Speichern). Der Server spricht den ClamAV-Daemon `clamd` direkt über sein Socket-Protokoll -an — keine zusätzliche Abhängigkeit. Voraussetzung: ClamAV ist installiert und -`clamd` läuft. Beim Start meldet der Server `Virenscan: aktiv` bzw. eine -Warnung, falls `clamd` nicht erreichbar ist. +an — keine zusätzliche Abhängigkeit. Beim Start meldet der Server +`Virenscan: aktiv` bzw. eine Warnung, falls `clamd` nicht erreichbar ist. -### ClamAV einrichten — macOS +### Produktion (Coolify) + +In der Produktion läuft ClamAV **im selben Container** wie der Node-Server. +Das `Dockerfile` installiert `clamav-daemon` und `clamav-freshclam`, das +`entrypoint.sh` startet beide Daemons vor Node: + +- Virendefinitionen liegen auf der persistenten Volume unter + `/app/data/clamav-db` und überleben Restarts/Deploys. +- Erster Start auf einer leeren Volume lädt einmalig ~300 MB Definitionen + (~1–2 Min). Folge-Starts sind in ~30 s wieder oben. +- `freshclam --daemon` hält die Definitionen im Hintergrund aktuell. +- **Speicherbedarf:** ~1–1,5 GB RAM für die geladene DB. + Die Coolify-VM muss entsprechend dimensioniert sein. + +Keine manuelle Coolify-Konfiguration nötig — push auf `main` baut das Image +mit ClamAV, beim nächsten Deploy ist der Scanner aktiv. + +### ClamAV einrichten — macOS (lokale Entwicklung) ```bash brew install clamav diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..eba2d7f --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# +# Container-Entrypoint: startet clamd (Virenscanner) + Node-Server. +# clamd läuft als clamav-User, node läuft als nodejs-User. Beide sind +# Kinder von tini (PID 1), das Signal-Forwarding und Reaping übernimmt. +# + +set -eu + +CLAMAV_DB_DIR=/app/data/clamav-db + +# Persistente Verzeichnisse auf der Coolify-Volume sicherstellen. +mkdir -p "$CLAMAV_DB_DIR" \ + /app/data/leads \ + /app/data/gamejam \ + /app/data/gamejam-games +chown -R clamav:clamav "$CLAMAV_DB_DIR" 2>/dev/null || true +chown -R nodejs:nodejs /app/data/leads \ + /app/data/gamejam \ + /app/data/gamejam-games 2>/dev/null || true + +# Erster Start auf einer leeren Volume: Virendefinitionen ziehen +# (~300 MB; danach persistent auf der Volume). +if ! ls "$CLAMAV_DB_DIR"/daily.{cvd,cld} >/dev/null 2>&1; then + echo "[entrypoint] keine ClamAV-DB im Volume — initialer freshclam-Lauf …" + freshclam --quiet \ + || echo "[entrypoint] initialer freshclam fehlgeschlagen; Container läuft weiter" +fi + +# Hintergrund-Updates der Virendefinitionen (alle paar Stunden). +freshclam --daemon --quiet || true + +# clamd starten (forkt in den Hintergrund). +echo "[entrypoint] clamd starten …" +clamd + +# Bis zu ~120 s auf clamd-Bereitschaft warten. Wenn es länger dauert, +# startet node trotzdem — Uploads werden dann fail-closed abgelehnt, +# bis clamd kommt. +for i in $(seq 1 60); do + if printf 'zPING\0' | nc -w 2 127.0.0.1 3310 2>/dev/null | grep -q PONG; then + echo "[entrypoint] clamd bereit" + break + fi + sleep 2 +done + +echo "[entrypoint] node-Server starten …" +exec gosu nodejs node /app/server.js