diff --git a/ct/clamav.sh b/ct/clamav.sh new file mode 100644 index 0000000000..8550cd1b18 --- /dev/null +++ b/ct/clamav.sh @@ -0,0 +1,58 @@ +#!/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}" + +export var_scan_dir="${var_scan_dir:-}" +export var_scan_mode="${var_scan_mode:-}" +export var_clamd_tcp="${var_clamd_tcp:-}" + +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 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}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 new file mode 100644 index 0000000000..a791276642 --- /dev/null +++ b/install/clamav-install.sh @@ -0,0 +1,136 @@ +#!/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 + +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}" +# 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 + +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 \ + clamav-daemon \ + clamav-freshclam \ + inotify-tools +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" + +msg_info "Creating Scan Services" +mkdir -p "$var_scan_dir" +cat </etc/systemd/system/clamav-scan.service +[Unit] +Description=ClamAV scan of ${var_scan_dir} +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 "${var_scan_dir}" +EOF +cat </etc/systemd/system/clamav-scan.timer +[Unit] +Description=Daily ClamAV scan of ${var_scan_dir} + +[Timer] +OnCalendar=daily +RandomizedDelaySec=1h +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 + rotate 8 + compress + missingok + notifempty +} +EOF +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 +cleanup_lxc diff --git a/json/clamav.json b/json/clamav.json new file mode 100644 index 0000000000..7d62118b6f --- /dev/null +++ b/json/clamav.json @@ -0,0 +1,73 @@ +{ + "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 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", + "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 + }, + "app_vars": [ + { + "name": "var_scan_dir", + "label": "Scan Folder", + "type": "text", + "default": "/mnt/scan", + "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", + "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 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" + }, + { + "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": "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" + } + ] +}