From c20eb2c20cdd30db334b496cbf815bcddbdd3496 Mon Sep 17 00:00:00 2001 From: Jan Wiebe Klijnsma Date: Mon, 2 Nov 2026 23:56:25 +0100 Subject: [PATCH 1/5] ClamAV: new script (Ubuntu 26.04, local scanning) - ClamAV 1.5.x from Ubuntu 26.04 main (security-maintained), signatures fetched with freshclam at install, clamav-freshclam keeps them current - clamd on its local socket only (packaged default, no TCPSocket) - daily clamav-scan.timer runs clamdscan --multiscan --fdpass on /mnt/scan, results in /var/log/clamav/scan.log (logrotated) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01SZrFpm4auP7MM23XW7SwQJ --- ct/clamav.sh | 51 +++++++++++++++++++++++++++ install/clamav-install.sh | 72 +++++++++++++++++++++++++++++++++++++++ json/clamav.json | 49 ++++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 ct/clamav.sh create mode 100644 install/clamav-install.sh create mode 100644 json/clamav.json diff --git a/ct/clamav.sh b/ct/clamav.sh new file mode 100644 index 0000000000..0bdc17b290 --- /dev/null +++ b/ct/clamav.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/DevScripts/main" +_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func" +source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func") +# Copyright (c) 2021-2026 community-scripts ORG +# Author: Jan Wiebe Klijnsma (jwklijnsma) +# License: MIT | https://github.com/community-scripts/DevScripts/raw/main/LICENSE +# Source: https://www.clamav.net + +APP="ClamAV" +var_tags="${var_tags:-security;antivirus}" +var_cpu="${var_cpu:-2}" +var_ram="${var_ram:-4096}" +var_disk="${var_disk:-8}" +var_os="${var_os:-ubuntu}" +var_version="${var_version:-26.04}" +#var_arm64="${var_arm64:-no}" # unset = ask the user; set yes/no only when verified +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + + if [[ ! -f /etc/clamav/clamd.conf ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + msg_info "Updating ClamAV" + $STD apt update + $STD apt install -y --only-upgrade clamav clamav-daemon clamav-freshclam + systemctl restart clamav-daemon + msg_ok "Updated ClamAV" + exit +} + +start +build_container +description + +msg_ok "Completed Successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW}Bind-mount the folders to scan to /mnt/scan, e.g. on the host:${CL}" +echo -e "${TAB}${BGN}pct set -mp0 /path/on/host,mp=/mnt/scan,ro=1${CL}" +echo -e "${INFO}${YW}Daily scan results: /var/log/clamav/scan.log${CL}" diff --git a/install/clamav-install.sh b/install/clamav-install.sh new file mode 100644 index 0000000000..89d082e6df --- /dev/null +++ b/install/clamav-install.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 community-scripts ORG +# Author: Jan Wiebe Klijnsma (jwklijnsma) +# License: MIT | https://github.com/community-scripts/DevScripts/raw/main/LICENSE +# Source: https://www.clamav.net + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +msg_info "Installing ClamAV" +$STD apt install -y \ + clamav \ + clamav-daemon \ + clamav-freshclam +msg_ok "Installed ClamAV" + +# clamd refuses to start without signatures, so fetch them once before enabling it. +# The packaged freshclam service holds the database lock, so stop it for the first run. +msg_info "Downloading ClamAV Signatures" +systemctl stop clamav-freshclam +$STD freshclam +systemctl enable -q --now clamav-freshclam +systemctl enable -q --now clamav-daemon +msg_ok "Downloaded ClamAV Signatures" + +msg_info "Creating Scan Timer" +mkdir -p /mnt/scan +cat </etc/systemd/system/clamav-scan.service +[Unit] +Description=ClamAV scan of /mnt/scan +After=clamav-daemon.service +Requires=clamav-daemon.service + +[Service] +Type=oneshot +Nice=19 +IOSchedulingClass=idle +ExecStart=/usr/bin/clamdscan --multiscan --fdpass --infected --log=/var/log/clamav/scan.log /mnt/scan +EOF +cat </etc/systemd/system/clamav-scan.timer +[Unit] +Description=Daily ClamAV scan of /mnt/scan + +[Timer] +OnCalendar=daily +RandomizedDelaySec=1h +Persistent=true + +[Install] +WantedBy=timers.target +EOF +cat </etc/logrotate.d/clamav-scan +/var/log/clamav/scan.log { + weekly + rotate 8 + compress + missingok + notifempty +} +EOF +systemctl enable -q --now clamav-scan.timer +msg_ok "Created Scan Timer" + +motd_ssh +customize +cleanup_lxc diff --git a/json/clamav.json b/json/clamav.json new file mode 100644 index 0000000000..cd55205be2 --- /dev/null +++ b/json/clamav.json @@ -0,0 +1,49 @@ +{ + "name": "ClamAV", + "slug": "clamav", + "categories": [ + 6 + ], + "date_created": "2026-11-02", + "type": "ct", + "updateable": true, + "privileged": false, + "interface_port": null, + "documentation": "https://docs.clamav.net/", + "website": "https://www.clamav.net/", + "repository": "https://github.com/Cisco-Talos/clamav", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/clamav.webp", + "description": "ClamAV is an open-source antivirus engine for detecting trojans, viruses, malware and other malicious threats. It runs as a local scanning daemon with automatic signature updates and can scan files, archives and mailboxes.", + "install_methods": [ + { + "type": "default", + "script": "ct/clamav.sh", + "config_path": "/etc/clamav/clamd.conf", + "resources": { + "cpu": 2, + "ram": 4096, + "hdd": 8, + "os": "Ubuntu", + "version": "26.04" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [ + { + "text": "Bind-mount the folders to scan to /mnt/scan (pct set -mp0 /path,mp=/mnt/scan,ro=1). A daily timer scans it and logs to /var/log/clamav/scan.log.", + "type": "info" + }, + { + "text": "In an unprivileged container, only files readable by others on the host can be scanned. Use a privileged container to scan everything.", + "type": "warning" + }, + { + "text": "Infected files are only reported, not moved or deleted. On-access scanning is not available inside an LXC.", + "type": "info" + } + ] +} From 3850396d04f25a3d9ab9fca941fdbe8e84c16265 Mon Sep 17 00:00:00 2001 From: Jan Wiebe Klijnsma Date: Tue, 3 Nov 2026 00:00:19 +0100 Subject: [PATCH 2/5] ClamAV: make the scan folder configurable (var_scan_dir) - var_scan_dir exported from ct/clamav.sh and declared in json app_vars - install reads it first and prompts only when unset (default /mnt/scan), rejects relative paths, quotes it in the scan unit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01SZrFpm4auP7MM23XW7SwQJ --- ct/clamav.sh | 6 ++++-- install/clamav-install.sh | 17 +++++++++++++---- json/clamav.json | 11 ++++++++++- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/ct/clamav.sh b/ct/clamav.sh index 0bdc17b290..5867596b2d 100644 --- a/ct/clamav.sh +++ b/ct/clamav.sh @@ -17,6 +17,8 @@ var_version="${var_version:-26.04}" #var_arm64="${var_arm64:-no}" # unset = ask the user; set yes/no only when verified var_unprivileged="${var_unprivileged:-1}" +export var_scan_dir="${var_scan_dir:-}" + header_info "$APP" variables color @@ -46,6 +48,6 @@ description msg_ok "Completed Successfully!\n" echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" -echo -e "${INFO}${YW}Bind-mount the folders to scan to /mnt/scan, e.g. on the host:${CL}" -echo -e "${TAB}${BGN}pct set -mp0 /path/on/host,mp=/mnt/scan,ro=1${CL}" +echo -e "${INFO}${YW}Bind-mount the folders to scan to the scan folder you chose (default /mnt/scan), e.g. on the host:${CL}" +echo -e "${TAB}${BGN}pct set -mp0 /path/on/host,mp=${var_scan_dir:-/mnt/scan},ro=1${CL}" echo -e "${INFO}${YW}Daily scan results: /var/log/clamav/scan.log${CL}" diff --git a/install/clamav-install.sh b/install/clamav-install.sh index 89d082e6df..b7657f9ee8 100644 --- a/install/clamav-install.sh +++ b/install/clamav-install.sh @@ -13,6 +13,15 @@ setting_up_container network_check update_os +if [[ -z "${var_scan_dir:-}" ]]; then + var_scan_dir=$(prompt_input "ClamAV folder to scan (inside the container):" "/mnt/scan" 60) +fi +var_scan_dir="${var_scan_dir:-/mnt/scan}" +if [[ "$var_scan_dir" != /* ]]; then + msg_error "var_scan_dir must be an absolute path, got: ${var_scan_dir}" + exit 1 +fi + msg_info "Installing ClamAV" $STD apt install -y \ clamav \ @@ -30,10 +39,10 @@ systemctl enable -q --now clamav-daemon msg_ok "Downloaded ClamAV Signatures" msg_info "Creating Scan Timer" -mkdir -p /mnt/scan +mkdir -p "$var_scan_dir" cat </etc/systemd/system/clamav-scan.service [Unit] -Description=ClamAV scan of /mnt/scan +Description=ClamAV scan of ${var_scan_dir} After=clamav-daemon.service Requires=clamav-daemon.service @@ -41,11 +50,11 @@ Requires=clamav-daemon.service Type=oneshot Nice=19 IOSchedulingClass=idle -ExecStart=/usr/bin/clamdscan --multiscan --fdpass --infected --log=/var/log/clamav/scan.log /mnt/scan +ExecStart=/usr/bin/clamdscan --multiscan --fdpass --infected --log=/var/log/clamav/scan.log "${var_scan_dir}" EOF cat </etc/systemd/system/clamav-scan.timer [Unit] -Description=Daily ClamAV scan of /mnt/scan +Description=Daily ClamAV scan of ${var_scan_dir} [Timer] OnCalendar=daily diff --git a/json/clamav.json b/json/clamav.json index cd55205be2..38e7ad088c 100644 --- a/json/clamav.json +++ b/json/clamav.json @@ -32,9 +32,18 @@ "username": null, "password": null }, + "app_vars": [ + { + "name": "var_scan_dir", + "label": "Scan Folder", + "type": "text", + "default": "/mnt/scan", + "help": "Absolute path inside the container that the daily scan checks; bind-mount host folders here" + } + ], "notes": [ { - "text": "Bind-mount the folders to scan to /mnt/scan (pct set -mp0 /path,mp=/mnt/scan,ro=1). A daily timer scans it and logs to /var/log/clamav/scan.log.", + "text": "Bind-mount the folders to scan to the scan folder (default /mnt/scan): pct set -mp0 /path,mp=/mnt/scan,ro=1. A daily timer scans it and logs to /var/log/clamav/scan.log.", "type": "info" }, { From c2da97ef392246345971db080cb2d4f6272de251 Mon Sep 17 00:00:00 2001 From: Jan Wiebe Klijnsma Date: Tue, 3 Nov 2026 11:05:11 +0100 Subject: [PATCH 3/5] ClamAV: optional clamd TCP listener (var_clamd_tcp) - var_clamd_tcp (yes/no) exported from ct/clamav.sh, declared in json app_vars, read first in the install script and prompted only when unset - TCP 3310 is added as a second ListenStream on clamav-daemon.socket via a drop-in; clamd.conf is left alone because the package regenerates it from debconf with either TCPSocket or LocalSocket, so the local socket used by the scan timer survives upgrades - tested with socket activation on clamd 1.5.4: --fdpass scan on the local socket and INSTREAM over TCP both work without TCPSocket in clamd.conf - notes: no-auth warning for port 3310, description covers the shared-scanner use case Co-Authored-By: Claude Opus 5.5 --- ct/clamav.sh | 4 ++++ install/clamav-install.sh | 22 ++++++++++++++++++++++ json/clamav.json | 17 ++++++++++++----- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/ct/clamav.sh b/ct/clamav.sh index 5867596b2d..93371f1dc7 100644 --- a/ct/clamav.sh +++ b/ct/clamav.sh @@ -18,6 +18,7 @@ var_version="${var_version:-26.04}" var_unprivileged="${var_unprivileged:-1}" export var_scan_dir="${var_scan_dir:-}" +export var_clamd_tcp="${var_clamd_tcp:-}" header_info "$APP" variables @@ -51,3 +52,6 @@ echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" echo -e "${INFO}${YW}Bind-mount the folders to scan to the scan folder you chose (default /mnt/scan), e.g. on the host:${CL}" echo -e "${TAB}${BGN}pct set -mp0 /path/on/host,mp=${var_scan_dir:-/mnt/scan},ro=1${CL}" echo -e "${INFO}${YW}Daily scan results: /var/log/clamav/scan.log${CL}" +echo -e "${INFO}${YW}If TCP mode is enabled, point clients (Nextcloud, rspamd, clamav-milter) at:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}${IP}:3310${CL}" +echo -e "${INFO}${YW}clamd has no authentication: allow port 3310 only from those clients (e.g. the Proxmox firewall).${CL}" diff --git a/install/clamav-install.sh b/install/clamav-install.sh index b7657f9ee8..4ccd8f4505 100644 --- a/install/clamav-install.sh +++ b/install/clamav-install.sh @@ -22,6 +22,14 @@ if [[ "$var_scan_dir" != /* ]]; then exit 1 fi +if [[ -z "${var_clamd_tcp:-}" ]]; then + if prompt_confirm "Let other hosts scan files with clamd over TCP port 3310?" "n" 60; then + var_clamd_tcp="yes" + else + var_clamd_tcp="no" + fi +fi + msg_info "Installing ClamAV" $STD apt install -y \ clamav \ @@ -29,12 +37,26 @@ $STD apt install -y \ clamav-freshclam msg_ok "Installed ClamAV" +# The package rewrites clamd.conf from debconf on upgrade with either TCPSocket or +# LocalSocket, never both, so TCP is added as a second listener on the socket unit. +if [[ "${var_clamd_tcp,,}" == "yes" ]]; then + msg_info "Configuring clamd TCP Listener" + mkdir -p /etc/systemd/system/clamav-daemon.socket.d + cat </etc/systemd/system/clamav-daemon.socket.d/tcp.conf +[Socket] +ListenStream=3310 +EOF + systemctl daemon-reload + msg_ok "Configured clamd TCP Listener" +fi + # clamd refuses to start without signatures, so fetch them once before enabling it. # The packaged freshclam service holds the database lock, so stop it for the first run. msg_info "Downloading ClamAV Signatures" systemctl stop clamav-freshclam $STD freshclam systemctl enable -q --now clamav-freshclam +systemctl restart clamav-daemon.socket systemctl enable -q --now clamav-daemon msg_ok "Downloaded ClamAV Signatures" diff --git a/json/clamav.json b/json/clamav.json index 38e7ad088c..3a1d5f3d2f 100644 --- a/json/clamav.json +++ b/json/clamav.json @@ -13,7 +13,7 @@ "website": "https://www.clamav.net/", "repository": "https://github.com/Cisco-Talos/clamav", "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/clamav.webp", - "description": "ClamAV is an open-source antivirus engine for detecting trojans, viruses, malware and other malicious threats. It runs as a local scanning daemon with automatic signature updates and can scan files, archives and mailboxes.", + "description": "ClamAV is an open-source antivirus engine for detecting trojans, viruses and other malware. It scans bind-mounted folders such as download or share directories on a daily schedule, and can optionally serve clamd over TCP so Nextcloud, rspamd or mail servers share one scanner. Infected files are reported, never moved or deleted.", "install_methods": [ { "type": "default", @@ -39,20 +39,27 @@ "type": "text", "default": "/mnt/scan", "help": "Absolute path inside the container that the daily scan checks; bind-mount host folders here" + }, + { + "name": "var_clamd_tcp", + "label": "clamd over TCP (port 3310)", + "type": "boolean", + "default": false, + "help": "Let other hosts send files to clamd on port 3310, e.g. Nextcloud files_antivirus or rspamd" } ], "notes": [ { - "text": "Bind-mount the folders to scan to the scan folder (default /mnt/scan): pct set -mp0 /path,mp=/mnt/scan,ro=1. A daily timer scans it and logs to /var/log/clamav/scan.log.", + "text": "Bind-mount the folders to scan read-only to the scan folder: pct set -mp0 /path,mp=/mnt/scan,ro=1. Results go to /var/log/clamav/scan.log.", "type": "info" }, { - "text": "In an unprivileged container, only files readable by others on the host can be scanned. Use a privileged container to scan everything.", + "text": "clamd on TCP 3310 has no authentication. Allow the port only from the hosts that use it, e.g. with the Proxmox firewall.", "type": "warning" }, { - "text": "Infected files are only reported, not moved or deleted. On-access scanning is not available inside an LXC.", - "type": "info" + "text": "In an unprivileged container, only files readable by others on the host can be scanned.", + "type": "warning" } ] } From 8d207ec02262005df18e708c34f973dfc943ff26 Mon Sep 17 00:00:00 2001 From: Jan Wiebe Klijnsma Date: Tue, 3 Nov 2026 23:27:02 +0100 Subject: [PATCH 4/5] ClamAV: watch mode for the scan folder (var_scan_mode) - var_scan_mode (both|watch|daily, default both), exported from ct/clamav.sh, declared in json app_vars, prompted only when unset - clamav-watch.service scans files as they are written or moved in (inotifywait + clamdscan --fdpass); clamonacc is not an option because fanotify is not available in an unprivileged LXC - daily timer kept for full rescans with fresh signatures - scan folder rejected if it contains characters that break systemd units - tested: inotify pipeline as systemd unescapes it (new file in a subfolder with a space, file moved in, clean file ignored) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01SZrFpm4auP7MM23XW7SwQJ --- ct/clamav.sh | 3 ++- install/clamav-install.sh | 45 +++++++++++++++++++++++++++++++++------ json/clamav.json | 16 ++++++++++---- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/ct/clamav.sh b/ct/clamav.sh index 93371f1dc7..8550cd1b18 100644 --- a/ct/clamav.sh +++ b/ct/clamav.sh @@ -18,6 +18,7 @@ var_version="${var_version:-26.04}" var_unprivileged="${var_unprivileged:-1}" export var_scan_dir="${var_scan_dir:-}" +export var_scan_mode="${var_scan_mode:-}" export var_clamd_tcp="${var_clamd_tcp:-}" header_info "$APP" @@ -51,7 +52,7 @@ msg_ok "Completed Successfully!\n" echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" echo -e "${INFO}${YW}Bind-mount the folders to scan to the scan folder you chose (default /mnt/scan), e.g. on the host:${CL}" echo -e "${TAB}${BGN}pct set -mp0 /path/on/host,mp=${var_scan_dir:-/mnt/scan},ro=1${CL}" -echo -e "${INFO}${YW}Daily scan results: /var/log/clamav/scan.log${CL}" +echo -e "${INFO}${YW}Scan results (watch and daily): /var/log/clamav/scan.log${CL}" echo -e "${INFO}${YW}If TCP mode is enabled, point clients (Nextcloud, rspamd, clamav-milter) at:${CL}" echo -e "${TAB}${GATEWAY}${BGN}${IP}:3310${CL}" echo -e "${INFO}${YW}clamd has no authentication: allow port 3310 only from those clients (e.g. the Proxmox firewall).${CL}" diff --git a/install/clamav-install.sh b/install/clamav-install.sh index 4ccd8f4505..a791276642 100644 --- a/install/clamav-install.sh +++ b/install/clamav-install.sh @@ -17,8 +17,18 @@ if [[ -z "${var_scan_dir:-}" ]]; then var_scan_dir=$(prompt_input "ClamAV folder to scan (inside the container):" "/mnt/scan" 60) fi var_scan_dir="${var_scan_dir:-/mnt/scan}" -if [[ "$var_scan_dir" != /* ]]; then - msg_error "var_scan_dir must be an absolute path, got: ${var_scan_dir}" +# The path is written into systemd units, where quotes, % and $ have special meaning +if [[ "$var_scan_dir" != /* || "$var_scan_dir" =~ [\'\"%\$\\] ]]; then + msg_error "var_scan_dir must be an absolute path without quotes, %, \$ or backslashes, got: ${var_scan_dir}" + exit 1 +fi + +if [[ -z "${var_scan_mode:-}" ]]; then + var_scan_mode=$(prompt_select "ClamAV scan mode for ${var_scan_dir}:" 1 60 "both" "watch" "daily") +fi +var_scan_mode="${var_scan_mode:-both}" +if [[ ! "$var_scan_mode" =~ ^(both|watch|daily)$ ]]; then + msg_error "var_scan_mode must be both, watch or daily, got: ${var_scan_mode}" exit 1 fi @@ -34,7 +44,8 @@ msg_info "Installing ClamAV" $STD apt install -y \ clamav \ clamav-daemon \ - clamav-freshclam + clamav-freshclam \ + inotify-tools msg_ok "Installed ClamAV" # The package rewrites clamd.conf from debconf on upgrade with either TCPSocket or @@ -60,7 +71,7 @@ systemctl restart clamav-daemon.socket systemctl enable -q --now clamav-daemon msg_ok "Downloaded ClamAV Signatures" -msg_info "Creating Scan Timer" +msg_info "Creating Scan Services" mkdir -p "$var_scan_dir" cat </etc/systemd/system/clamav-scan.service [Unit] @@ -86,6 +97,23 @@ Persistent=true [Install] WantedBy=timers.target EOF +# inotify instead of clamonacc: fanotify is not available in an unprivileged LXC. +# %% and $$ are systemd escapes for % and $. +cat </etc/systemd/system/clamav-watch.service +[Unit] +Description=ClamAV scan of new and changed files in ${var_scan_dir} +After=clamav-daemon.service +Requires=clamav-daemon.service + +[Service] +Type=simple +ExecStart=/bin/bash -c 'inotifywait -m -r -q -e close_write,moved_to --format "%%w%%f" -- "${var_scan_dir}" | while IFS= read -r f; do clamdscan --fdpass --infected --no-summary --log=/var/log/clamav/scan.log -- "\$\$f"; done' +Restart=on-failure +RestartSec=10 + +[Install] +WantedBy=multi-user.target +EOF cat </etc/logrotate.d/clamav-scan /var/log/clamav/scan.log { weekly @@ -95,8 +123,13 @@ cat </etc/logrotate.d/clamav-scan notifempty } EOF -systemctl enable -q --now clamav-scan.timer -msg_ok "Created Scan Timer" +if [[ "$var_scan_mode" != "watch" ]]; then + systemctl enable -q --now clamav-scan.timer +fi +if [[ "$var_scan_mode" != "daily" ]]; then + systemctl enable -q --now clamav-watch +fi +msg_ok "Created Scan Services" motd_ssh customize diff --git a/json/clamav.json b/json/clamav.json index 3a1d5f3d2f..7d62118b6f 100644 --- a/json/clamav.json +++ b/json/clamav.json @@ -13,7 +13,7 @@ "website": "https://www.clamav.net/", "repository": "https://github.com/Cisco-Talos/clamav", "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/clamav.webp", - "description": "ClamAV is an open-source antivirus engine for detecting trojans, viruses and other malware. It scans bind-mounted folders such as download or share directories on a daily schedule, and can optionally serve clamd over TCP so Nextcloud, rspamd or mail servers share one scanner. Infected files are reported, never moved or deleted.", + "description": "ClamAV is an open-source antivirus engine for detecting trojans, viruses and other malware. It scans bind-mounted folders such as download or share directories as files arrive and/or daily, and can optionally serve clamd over TCP so Nextcloud, rspamd or mail servers share one scanner. Infected files are reported, never moved or deleted.", "install_methods": [ { "type": "default", @@ -38,7 +38,15 @@ "label": "Scan Folder", "type": "text", "default": "/mnt/scan", - "help": "Absolute path inside the container that the daily scan checks; bind-mount host folders here" + "help": "Absolute path inside the container that is watched and/or scanned daily; bind-mount host folders here" + }, + { + "name": "var_scan_mode", + "label": "Scan Mode", + "type": "select", + "options": ["both", "watch", "daily"], + "default": "both", + "help": "watch: scan files as they are written or moved in; daily: full scan once a day; both: watch plus a daily full rescan" }, { "name": "var_clamd_tcp", @@ -50,7 +58,7 @@ ], "notes": [ { - "text": "Bind-mount the folders to scan read-only to the scan folder: pct set -mp0 /path,mp=/mnt/scan,ro=1. Results go to /var/log/clamav/scan.log.", + "text": "Bind-mount the folders to scan read-only to the scan folder: pct set -mp0 /path,mp=/mnt/scan,ro=1. Results go to /var/log/clamav/scan.log; signatures update hourly.", "type": "info" }, { @@ -58,7 +66,7 @@ "type": "warning" }, { - "text": "In an unprivileged container, only files readable by others on the host can be scanned.", + "text": "In an unprivileged container, only files readable by others on the host can be scanned. Watch mode does not see changes made on NFS or SMB mounts.", "type": "warning" } ] From 23764fe258d3c9206fabc2447fd10182437cd818 Mon Sep 17 00:00:00 2001 From: Jan Wiebe Klijnsma Date: Wed, 4 Nov 2026 11:20:22 +0100 Subject: [PATCH 5/5] ClamAV: Debian 13 base and host bind mount from the CT script (review feedback) - var_os debian / var_version 13 like the other scripts: Debian 13 ships ClamAV 1.4 (upstream LTS, security support until 2027-08), 1.4.6 already in trixie-proposed-updates; the clamav-daemon.socket the TCP drop-in relies on is the same upstream unit in 1.4.3 - var_scan_host_dir: optional host folder, validated before the build, asked at the end when unset; the CT script reads the scan folder back from the container, adds it as the first free mpN read-only and reboots the container so the watcher starts on the mounted folder - final message prints the exact pct command when no folder was mounted Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01SZrFpm4auP7MM23XW7SwQJ --- ct/clamav.sh | 41 +++++++++++++++++++++++++++++++++++++---- json/clamav.json | 13 ++++++++++--- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/ct/clamav.sh b/ct/clamav.sh index 8550cd1b18..b777f0e29f 100644 --- a/ct/clamav.sh +++ b/ct/clamav.sh @@ -12,20 +12,27 @@ var_tags="${var_tags:-security;antivirus}" var_cpu="${var_cpu:-2}" var_ram="${var_ram:-4096}" var_disk="${var_disk:-8}" -var_os="${var_os:-ubuntu}" -var_version="${var_version:-26.04}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" #var_arm64="${var_arm64:-no}" # unset = ask the user; set yes/no only when verified var_unprivileged="${var_unprivileged:-1}" export var_scan_dir="${var_scan_dir:-}" export var_scan_mode="${var_scan_mode:-}" export var_clamd_tcp="${var_clamd_tcp:-}" +var_scan_host_dir="${var_scan_host_dir:-}" header_info "$APP" variables color catch_errors +# Checked before the build: a bad host path should fail in seconds, not after the install +if [[ -n "$var_scan_host_dir" ]] && [[ ! -d "$var_scan_host_dir" || "$var_scan_host_dir" == *[,=]* ]]; then + msg_error "var_scan_host_dir must be an existing host folder without ',' or '=', got: ${var_scan_host_dir}" + exit 1 +fi + function update_script() { header_info check_container_storage @@ -48,10 +55,36 @@ start build_container description +scan_dir="${var_scan_dir:-/mnt/scan}" +if command -v pct &>/dev/null; then + # The scan folder may have been chosen at the prompt inside the container, so read it back + scan_dir=$(pct exec "$CTID" -- sed -n 's/^Description=ClamAV scan of //p' /etc/systemd/system/clamav-scan.service) + if [[ -z "$var_scan_host_dir" ]]; then + var_scan_host_dir=$(prompt_input "Host folder to bind-mount read-only into ${scan_dir} (empty to skip):" "" 60) + fi + if [[ -n "$var_scan_host_dir" ]] && [[ ! -d "$var_scan_host_dir" || "$var_scan_host_dir" == *[,=]* ]]; then + msg_warn "Skipped the bind mount: ${var_scan_host_dir} is not a host folder (or contains ',' or '=')" + var_scan_host_dir="" + fi + if [[ -n "$var_scan_host_dir" ]]; then + msg_info "Mounting ${var_scan_host_dir} into ClamAV" + mp=0 + while pct config "$CTID" | grep -q "^mp${mp}:"; do + mp=$((mp + 1)) + done + pct set "$CTID" "-mp${mp}" "${var_scan_host_dir},mp=${scan_dir},ro=1" + # The watcher must start on the mounted folder, not the empty one underneath it + pct reboot "$CTID" + msg_ok "Mounted ${var_scan_host_dir} into ClamAV (mp${mp}, read-only)" + fi +fi + msg_ok "Completed Successfully!\n" echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" -echo -e "${INFO}${YW}Bind-mount the folders to scan to the scan folder you chose (default /mnt/scan), e.g. on the host:${CL}" -echo -e "${TAB}${BGN}pct set -mp0 /path/on/host,mp=${var_scan_dir:-/mnt/scan},ro=1${CL}" +if [[ -z "$var_scan_host_dir" ]]; then + echo -e "${INFO}${YW}Bind-mount the folders to scan into ${scan_dir}, e.g. on the host:${CL}" + echo -e "${TAB}${BGN}pct set ${CTID} -mp0 /path/on/host,mp=${scan_dir},ro=1 && pct reboot ${CTID}${CL}" +fi echo -e "${INFO}${YW}Scan results (watch and daily): /var/log/clamav/scan.log${CL}" echo -e "${INFO}${YW}If TCP mode is enabled, point clients (Nextcloud, rspamd, clamav-milter) at:${CL}" echo -e "${TAB}${GATEWAY}${BGN}${IP}:3310${CL}" diff --git a/json/clamav.json b/json/clamav.json index 7d62118b6f..913e19de98 100644 --- a/json/clamav.json +++ b/json/clamav.json @@ -23,8 +23,8 @@ "cpu": 2, "ram": 4096, "hdd": 8, - "os": "Ubuntu", - "version": "26.04" + "os": "Debian", + "version": "13" } } ], @@ -40,6 +40,13 @@ "default": "/mnt/scan", "help": "Absolute path inside the container that is watched and/or scanned daily; bind-mount host folders here" }, + { + "name": "var_scan_host_dir", + "label": "Host Folder to Scan", + "type": "text", + "default": "", + "help": "Optional folder on the Proxmox host; mounted read-only into the scan folder after the install (asked at the end when empty)" + }, { "name": "var_scan_mode", "label": "Scan Mode", @@ -58,7 +65,7 @@ ], "notes": [ { - "text": "Bind-mount the folders to scan read-only to the scan folder: pct set -mp0 /path,mp=/mnt/scan,ro=1. Results go to /var/log/clamav/scan.log; signatures update hourly.", + "text": "Pick a host folder at install (var_scan_host_dir) and it is bind-mounted read-only into the scan folder. Results go to /var/log/clamav/scan.log; signatures update hourly.", "type": "info" }, {