Переглянути джерело

[Clamd] use python bootstrapper to start CLAMD container

FreddleSpl0it 3 місяців тому
батько
коміт
669f75182d

+ 2 - 0
data/Dockerfiles/bootstrap/main.py

@@ -21,6 +21,8 @@ def main():
     from modules.BootstrapDovecot import Bootstrap
   elif container_name == "rspamd-mailcow":
     from modules.BootstrapRspamd import Bootstrap
+  elif container_name == "clamd-mailcow":
+    from modules.BootstrapClamd import Bootstrap
   else:
     print(f"No bootstrap handler for container: {container_name}", file=sys.stderr)
     sys.exit(1)

+ 10 - 6
data/Dockerfiles/bootstrap/modules/BootstrapBase.py

@@ -30,16 +30,14 @@ class BootstrapBase:
     self.mysql_conn = None
     self.redis_conn = None
 
-  def render_config(self, template_name, output_path):
+  def render_config(self, template_name, output_path, clean_blank_lines=False):
     """
     Renders a Jinja2 template and writes it to the specified output path.
 
-    The method uses the class's `self.env` Jinja2 environment and `self.env_vars`
-    for rendering template variables.
-
     Args:
-        template_name (str): Name of the template file.
-        output_path (str or Path): Path to write the rendered output file.
+      template_name (str): Name of the template file.
+      output_path (str or Path): Path to write the rendered output file.
+      clean_blank_lines (bool): If True, removes empty/whitespace-only lines from rendered output.
     """
 
     output_path = Path(output_path)
@@ -48,6 +46,12 @@ class BootstrapBase:
     template = self.env.get_template(template_name)
     rendered = template.render(self.env_vars)
 
+    if clean_blank_lines:
+      rendered = "\n".join(line for line in rendered.splitlines() if line.strip())
+
+    # converts output to Unix-style line endings
+    rendered = rendered.replace('\r\n', '\n').replace('\r', '\n')
+
     with open(output_path, "w") as f:
       f.write(rendered)
 

+ 58 - 0
data/Dockerfiles/bootstrap/modules/BootstrapClamd.py

@@ -0,0 +1,58 @@
+from jinja2 import Environment, FileSystemLoader
+from modules.BootstrapBase import BootstrapBase
+from pathlib import Path
+import os
+import sys
+import time
+import platform
+
+class Bootstrap(BootstrapBase):
+  def bootstrap(self):
+    # Skip Clamd if set
+    if self.isYes(os.getenv("SKIP_CLAMD", "")):
+      print("SKIP_CLAMD is set, skipping ClamAV startup...")
+      time.sleep(365 * 24 * 60 * 60)
+      sys.exit(1)
+
+    # Connect to MySQL
+    self.connect_mysql()
+
+    print("Cleaning up tmp files...")
+    tmp_files = Path("/var/lib/clamav").glob("clamav-*.tmp")
+    for tmp_file in tmp_files:
+      try:
+        self.remove(tmp_file)
+        print(f"Removed: {tmp_file}")
+      except Exception as e:
+        print(f"Failed to remove {tmp_file}: {e}")
+
+    self.create_dir("/run/clamav")
+    self.create_dir("/var/lib/clamav")
+
+    # Setup Jinja2 Environment and load vars
+    self.env = Environment(
+      loader=FileSystemLoader('./etc/clamav/config_templates'),
+      keep_trailing_newline=True,
+      lstrip_blocks=True,
+      trim_blocks=True
+    )
+    extra_vars = {
+    }
+    self.env_vars = self.prepare_template_vars('/overwrites.json', extra_vars)
+
+    print("Set Timezone")
+    self.set_timezone()
+
+    print("Render config")
+    self.render_config("whitelist.ign2.j2", "/var/lib/clamav/whitelist.ign2", clean_blank_lines=True)
+
+    # Fix permissions
+    self.set_owner("/var/lib/clamav", "clamav", "clamav", recursive=True)
+    self.set_owner("/run/clamav", "clamav", "clamav", recursive=True)
+    self.set_permissions("/var/lib/clamav", 0o755)
+    for item in Path("/var/lib/clamav").glob("*"):
+      self.set_permissions(item, 0o644)
+    self.set_permissions("/run/clamav", 0o750)
+
+    # Copying to /etc/clamav to expose file as-is to administrator
+    self.copy_file("/var/lib/clamav/whitelist.ign2", "/etc/clamav/whitelist.ign2")

+ 21 - 10
data/Dockerfiles/clamd/Dockerfile

@@ -41,7 +41,7 @@ RUN wget -P /src https://www.clamav.net/downloads/production/clamav-${CLAMD_VERS
   -D ENABLE_MILTER=ON                                                                 \
   -D ENABLE_MAN_PAGES=OFF                                                             \
   -D ENABLE_STATIC_LIB=OFF                                                            \
-  -D ENABLE_JSON_SHARED=ON                                                            \ 
+  -D ENABLE_JSON_SHARED=ON                                                            \
   && cmake --build . \
   && make DESTDIR="/clamav" -j$(($(nproc) - 1)) install \
   && rm -r "/clamav/usr/lib/pkgconfig/" \
@@ -88,23 +88,34 @@ RUN apk upgrade --no-cache \
     pcre2 \
     zlib \
     libgcc \
+    py3-pip \
   && addgroup -S "clamav" && \
     adduser -D -G "clamav" -h "/var/lib/clamav" -s "/bin/false" -S "clamav" && \
     install -d -m 755 -g "clamav" -o "clamav" "/var/log/clamav" && \
     chown -R clamav:clamav /var/lib/clamav
 
+RUN pip install  --break-system-packages \
+  mysql-connector-python \
+  jinja2 \
+  redis \
+  dnspython
+
+
 COPY --from=builder "/clamav" "/"
 
-# init
-COPY clamd.sh /clamd.sh
-RUN chmod +x /sbin/tini
 
-# healthcheck
-COPY healthcheck.sh /healthcheck.sh
-COPY clamdcheck.sh /usr/local/bin
-RUN chmod +x /healthcheck.sh
-RUN chmod +x /usr/local/bin/clamdcheck.sh
+COPY data/Dockerfiles/bootstrap /bootstrap
+COPY data/Dockerfiles/clamd/docker-entrypoint.sh /docker-entrypoint.sh
+COPY data/Dockerfiles/clamd/clamd.sh /clamd.sh
+COPY data/Dockerfiles/clamd/healthcheck.sh /healthcheck.sh
+COPY data/Dockerfiles/clamd/clamdcheck.sh /usr/local/bin
 HEALTHCHECK --start-period=6m CMD "/healthcheck.sh"
 
-ENTRYPOINT []
+RUN chmod +x /docker-entrypoint.sh \
+  /clamd.sh \
+  /healthcheck.sh \
+  /usr/local/bin/clamdcheck.sh \
+  /sbin/tini
+
+ENTRYPOINT ["/docker-entrypoint.sh"]
 CMD ["/sbin/tini", "-g", "--", "/clamd.sh"]

+ 0 - 43
data/Dockerfiles/clamd/clamd.sh

@@ -1,48 +1,5 @@
 #!/bin/bash
 
-if [[ "${SKIP_CLAMD}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
-  echo "SKIP_CLAMD=y, skipping ClamAV..."
-  sleep 365d
-  exit 0
-fi
-
-# Cleaning up garbage
-echo "Cleaning up tmp files..."
-rm -rf /var/lib/clamav/clamav-*.tmp
-
-# Prepare whitelist
-
-mkdir -p /run/clamav /var/lib/clamav
-
-if [[ -s /etc/clamav/whitelist.ign2 ]]; then
-  echo "Copying non-empty whitelist.ign2 to /var/lib/clamav/whitelist.ign2"
-  cp /etc/clamav/whitelist.ign2 /var/lib/clamav/whitelist.ign2
-fi
-
-if [[ ! -f /var/lib/clamav/whitelist.ign2 ]]; then
-  echo "Creating /var/lib/clamav/whitelist.ign2"
-  cat <<EOF > /var/lib/clamav/whitelist.ign2
-# Please restart ClamAV after changing signatures
-Example-Signature.Ignore-1
-PUA.Win.Trojan.EmbeddedPDF-1
-PUA.Pdf.Trojan.EmbeddedJavaScript-1
-PUA.Pdf.Trojan.OpenActionObjectwithJavascript-1
-EOF
-fi
-
-chown clamav:clamav -R /var/lib/clamav /run/clamav
-
-chmod 755 /var/lib/clamav
-chmod 644 -R /var/lib/clamav/*
-chmod 750 /run/clamav
-
-stat /var/lib/clamav/whitelist.ign2
-dos2unix /var/lib/clamav/whitelist.ign2
-sed -i '/^\s*$/d' /var/lib/clamav/whitelist.ign2
-# Copying to /etc/clamav to expose file as-is to administrator
-cp -p /var/lib/clamav/whitelist.ign2 /etc/clamav/whitelist.ign2
-
-
 BACKGROUND_TASKS=()
 
 echo "Running freshclam..."

+ 20 - 0
data/Dockerfiles/clamd/docker-entrypoint.sh

@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# Run hooks
+for file in /hooks/*; do
+  if [ -x "${file}" ]; then
+    echo "Running hook ${file}"
+    "${file}"
+  fi
+done
+
+python3 -u /bootstrap/main.py
+BOOTSTRAP_EXIT_CODE=$?
+
+if [ $BOOTSTRAP_EXIT_CODE -ne 0 ]; then
+  echo "Bootstrap failed with exit code $BOOTSTRAP_EXIT_CODE. Not starting Clamd."
+  exit $BOOTSTRAP_EXIT_CODE
+fi
+
+echo "Bootstrap succeeded. Starting Clamd..."
+exec "$@"

+ 5 - 0
data/conf/clamav/config_templates/whitelist.ign2.j2

@@ -0,0 +1,5 @@
+# Please restart ClamAV after changing signatures
+Example-Signature.Ignore-1
+PUA.Win.Trojan.EmbeddedPDF-1
+PUA.Pdf.Trojan.EmbeddedJavaScript-1
+PUA.Pdf.Trojan.OpenActionObjectwithJavascript-1

+ 0 - 0
data/hooks/clamd/.gitkeep


+ 6 - 1
docker-compose.yml

@@ -65,7 +65,7 @@ services:
             - redis
 
     clamd-mailcow:
-      image: ghcr.io/mailcow/clamd:1.70
+      image: ghcr.io/mailcow/clamd:nightly-19052025
       restart: always
       depends_on:
         unbound-mailcow:
@@ -73,10 +73,15 @@ services:
       dns:
         - ${IPV4_NETWORK:-172.22.1}.254
       environment:
+        - CONTAINER_NAME=clamd-mailcow
+        - DBNAME=${DBNAME}
+        - DBUSER=${DBUSER}
+        - DBPASS=${DBPASS}
         - TZ=${TZ}
         - SKIP_CLAMD=${SKIP_CLAMD:-n}
       volumes:
         - ./data/conf/clamav/:/etc/clamav/:Z
+        - mysql-socket-vol-1:/var/run/mysqld/
         - clamd-db-vol-1:/var/lib/clamav
       networks:
         mailcow-network: