Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions ct/clamav.sh
Original file line number Diff line number Diff line change
@@ -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 <CTID> -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}"
136 changes: 136 additions & 0 deletions install/clamav-install.sh
Original file line number Diff line number Diff line change
@@ -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 <<EOF >/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 <<EOF >/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 <<EOF >/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 <<EOF >/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 <<EOF >/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
73 changes: 73 additions & 0 deletions json/clamav.json
Original file line number Diff line number Diff line change
@@ -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 <CTID> -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"
}
]
}
Loading