mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2025-12-19 21:01:31 +00:00
[Clamd] use python bootstrapper to start CLAMD container
This commit is contained in:
@@ -21,6 +21,8 @@ def main():
|
|||||||
from modules.BootstrapDovecot import Bootstrap
|
from modules.BootstrapDovecot import Bootstrap
|
||||||
elif container_name == "rspamd-mailcow":
|
elif container_name == "rspamd-mailcow":
|
||||||
from modules.BootstrapRspamd import Bootstrap
|
from modules.BootstrapRspamd import Bootstrap
|
||||||
|
elif container_name == "clamd-mailcow":
|
||||||
|
from modules.BootstrapClamd import Bootstrap
|
||||||
else:
|
else:
|
||||||
print(f"No bootstrap handler for container: {container_name}", file=sys.stderr)
|
print(f"No bootstrap handler for container: {container_name}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
@@ -30,16 +30,14 @@ class BootstrapBase:
|
|||||||
self.mysql_conn = None
|
self.mysql_conn = None
|
||||||
self.redis_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.
|
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:
|
Args:
|
||||||
template_name (str): Name of the template file.
|
template_name (str): Name of the template file.
|
||||||
output_path (str or Path): Path to write the rendered output 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)
|
output_path = Path(output_path)
|
||||||
@@ -48,6 +46,12 @@ class BootstrapBase:
|
|||||||
template = self.env.get_template(template_name)
|
template = self.env.get_template(template_name)
|
||||||
rendered = template.render(self.env_vars)
|
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:
|
with open(output_path, "w") as f:
|
||||||
f.write(rendered)
|
f.write(rendered)
|
||||||
|
|
||||||
|
|||||||
58
data/Dockerfiles/bootstrap/modules/BootstrapClamd.py
Normal file
58
data/Dockerfiles/bootstrap/modules/BootstrapClamd.py
Normal file
@@ -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")
|
||||||
@@ -41,7 +41,7 @@ RUN wget -P /src https://www.clamav.net/downloads/production/clamav-${CLAMD_VERS
|
|||||||
-D ENABLE_MILTER=ON \
|
-D ENABLE_MILTER=ON \
|
||||||
-D ENABLE_MAN_PAGES=OFF \
|
-D ENABLE_MAN_PAGES=OFF \
|
||||||
-D ENABLE_STATIC_LIB=OFF \
|
-D ENABLE_STATIC_LIB=OFF \
|
||||||
-D ENABLE_JSON_SHARED=ON \
|
-D ENABLE_JSON_SHARED=ON \
|
||||||
&& cmake --build . \
|
&& cmake --build . \
|
||||||
&& make DESTDIR="/clamav" -j$(($(nproc) - 1)) install \
|
&& make DESTDIR="/clamav" -j$(($(nproc) - 1)) install \
|
||||||
&& rm -r "/clamav/usr/lib/pkgconfig/" \
|
&& rm -r "/clamav/usr/lib/pkgconfig/" \
|
||||||
@@ -88,23 +88,34 @@ RUN apk upgrade --no-cache \
|
|||||||
pcre2 \
|
pcre2 \
|
||||||
zlib \
|
zlib \
|
||||||
libgcc \
|
libgcc \
|
||||||
|
py3-pip \
|
||||||
&& addgroup -S "clamav" && \
|
&& addgroup -S "clamav" && \
|
||||||
adduser -D -G "clamav" -h "/var/lib/clamav" -s "/bin/false" -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" && \
|
install -d -m 755 -g "clamav" -o "clamav" "/var/log/clamav" && \
|
||||||
chown -R clamav:clamav /var/lib/clamav
|
chown -R clamav:clamav /var/lib/clamav
|
||||||
|
|
||||||
|
RUN pip install --break-system-packages \
|
||||||
|
mysql-connector-python \
|
||||||
|
jinja2 \
|
||||||
|
redis \
|
||||||
|
dnspython
|
||||||
|
|
||||||
|
|
||||||
COPY --from=builder "/clamav" "/"
|
COPY --from=builder "/clamav" "/"
|
||||||
|
|
||||||
# init
|
|
||||||
COPY clamd.sh /clamd.sh
|
|
||||||
RUN chmod +x /sbin/tini
|
|
||||||
|
|
||||||
# healthcheck
|
COPY data/Dockerfiles/bootstrap /bootstrap
|
||||||
COPY healthcheck.sh /healthcheck.sh
|
COPY data/Dockerfiles/clamd/docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
COPY clamdcheck.sh /usr/local/bin
|
COPY data/Dockerfiles/clamd/clamd.sh /clamd.sh
|
||||||
RUN chmod +x /healthcheck.sh
|
COPY data/Dockerfiles/clamd/healthcheck.sh /healthcheck.sh
|
||||||
RUN chmod +x /usr/local/bin/clamdcheck.sh
|
COPY data/Dockerfiles/clamd/clamdcheck.sh /usr/local/bin
|
||||||
HEALTHCHECK --start-period=6m CMD "/healthcheck.sh"
|
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"]
|
CMD ["/sbin/tini", "-g", "--", "/clamd.sh"]
|
||||||
@@ -1,48 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/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=()
|
BACKGROUND_TASKS=()
|
||||||
|
|
||||||
echo "Running freshclam..."
|
echo "Running freshclam..."
|
||||||
|
|||||||
20
data/Dockerfiles/clamd/docker-entrypoint.sh
Normal file
20
data/Dockerfiles/clamd/docker-entrypoint.sh
Normal file
@@ -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
data/conf/clamav/config_templates/whitelist.ign2.j2
Normal file
5
data/conf/clamav/config_templates/whitelist.ign2.j2
Normal file
@@ -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
data/hooks/clamd/.gitkeep
Normal file
0
data/hooks/clamd/.gitkeep
Normal file
@@ -65,7 +65,7 @@ services:
|
|||||||
- redis
|
- redis
|
||||||
|
|
||||||
clamd-mailcow:
|
clamd-mailcow:
|
||||||
image: ghcr.io/mailcow/clamd:1.70
|
image: ghcr.io/mailcow/clamd:nightly-19052025
|
||||||
restart: always
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
unbound-mailcow:
|
unbound-mailcow:
|
||||||
@@ -73,10 +73,15 @@ services:
|
|||||||
dns:
|
dns:
|
||||||
- ${IPV4_NETWORK:-172.22.1}.254
|
- ${IPV4_NETWORK:-172.22.1}.254
|
||||||
environment:
|
environment:
|
||||||
|
- CONTAINER_NAME=clamd-mailcow
|
||||||
|
- DBNAME=${DBNAME}
|
||||||
|
- DBUSER=${DBUSER}
|
||||||
|
- DBPASS=${DBPASS}
|
||||||
- TZ=${TZ}
|
- TZ=${TZ}
|
||||||
- SKIP_CLAMD=${SKIP_CLAMD:-n}
|
- SKIP_CLAMD=${SKIP_CLAMD:-n}
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/conf/clamav/:/etc/clamav/:Z
|
- ./data/conf/clamav/:/etc/clamav/:Z
|
||||||
|
- mysql-socket-vol-1:/var/run/mysqld/
|
||||||
- clamd-db-vol-1:/var/lib/clamav
|
- clamd-db-vol-1:/var/lib/clamav
|
||||||
networks:
|
networks:
|
||||||
mailcow-network:
|
mailcow-network:
|
||||||
|
|||||||
Reference in New Issue
Block a user