ClamAV - Hardening version 3
Well, its that time of year to review all this again.
Linux is not getting any safer. Below is the script after a few teething bugs, put through the AI and further enhanced. It runs on all laptops within this house.
Please note that the script hardens an existing installation and does not do the installation itself. Please refer to standard documentation on how to do that.
Hardening Script
As per below
#!/bin/bash
# ─────────────────────────────────────────────────────────────────────────────
# setup-clamav.sh
# Harden ClamAV configuration for Debian / Linux Mint systems
# ─────────────────────────────────────────────────────────────────────────────
#
# PURPOSE
# Applies a hardened, best-practice ClamAV configuration focused on
# real-time workstation protection, home directory defence, and
# scheduled full-system sweeps. Designed for Linux Mint laptops.
#
# BEHAVIOUR
# - Self-contained: resets ClamAV config files to a known-good baseline
# before applying settings — safe for first run and re-runs alike
# - Idempotent: safely re-runnable; existing config is backed up then
# replaced with a clean copy before directives are applied
# - Supports --dry-run mode: validates what would change without touching
# any files or services → ./setup-clamav.sh --dry-run
# - Must be run as root
# - Audit-style section comments included for compliance traceability
#
# UPDATE STRATEGY
# Signature updates are managed entirely by the clamav-freshclam systemd
# service — NOT by cron. This avoids log-lock conflicts caused by multiple
# freshclam processes competing for the log file.
#
# PRE-REQUISITES
# - ClamAV packages installed: clamav-daemon, clamav-freshclam
# - Kernel with fanotify support (Linux Mint 20+ / kernel 5.4+) for on-access scanning
# - Local MTA (Postfix) installed and configured for email notifications
#
# PROTECTION COVERAGE
# Real-time (on-access, BLOCKING): /home /tmp /var/tmp /root
# Quarantine destination: /var/quarantine/clamav (mode 0700)
# Signature updates: Every 4 hours via freshclam systemd service
#
# Version: 3.1.0
# Updated: 25-03-2026
#
# Change Log:
# v3.1.0 — 25-03-2026
# - ADDED: systemd failure notification via notify-failure@.service template
# - OnFailure= drop-ins installed for clamav-daemon and clamav-freshclam
# - Failure emails sent via local MTA (Postfix) — silent on success
# - notify-failure@.service is reusable for any other systemd unit
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail
# --- Output helpers ---
info() { echo -e "\033[0;32m[INFO]\033[0m $*"; }
warn() { echo -e "\033[0;33m[WARN]\033[0m $*" >&2; }
error() { echo -e "\033[0;31m[ERROR]\033[0m $*" >&2; }
section() { echo -e "\n\033[1;34m>>> $* \033[0m"; }
# --- Root privilege check ---
if [[ "${EUID}" -ne 0 ]]; then
error "This script must be run as root."
exit 1
fi
DRY_RUN=false
if [[ "${1:-}" == "--dry-run" ]]; then
DRY_RUN=true
info "Dry-run mode enabled: no changes will be applied."
fi
# --- Paths ---
CLAMD_CONF="/etc/clamav/clamd.conf"
FRESHCLAM_CONF="/etc/clamav/freshclam.conf"
LOGFILE="/var/log/clamav/harden_clamav.log"
QUARANTINE_DIR="/var/quarantine/clamav"
SCAN_LOG="/var/log/clamav/daily_scan.log"
BACKUP_DIR="/etc/clamav/backups/$(date +%Y%m%d_%H%M%S)"
# --- Ensure log dir and quarantine dir exist ---
if ! $DRY_RUN; then
mkdir -p "$(dirname "$LOGFILE")" "$QUARANTINE_DIR"
chmod 0700 "$QUARANTINE_DIR"
chown root:root "$QUARANTINE_DIR"
touch "$LOGFILE" "$SCAN_LOG"
chown clamav:clamav "$LOGFILE" "$SCAN_LOG"
chmod 0640 "$LOGFILE" "$SCAN_LOG"
fi
# ─────────────────────────────────────────────────────────────────────────────
# ensure_config: idempotently set a directive in a config file.
# Handles the case where the key already exists (replaces) or is absent (appends).
# ─────────────────────────────────────────────────────────────────────────────
ensure_config() {
local file="$1"
local key="$2"
local value="$3"
if grep -qE "^[[:space:]]*${key}[[:space:]]" "$file"; then
if $DRY_RUN; then
info "[DRY] Would update: $key → $value ($file)"
else
sed -i "s|^[[:space:]]*${key}[[:space:]].*|${key} ${value}|" "$file"
info "Updated: $key = $value"
fi
else
if $DRY_RUN; then
info "[DRY] Would add: $key $value ($file)"
else
echo "${key} ${value}" >> "$file"
info "Added: $key = $value"
fi
fi
}
# ─────────────────────────────────────────────────────────────────────────────
# ensure_multivalue: idempotently add a directive that may appear multiple times
# (e.g. OnAccessIncludePath, ExcludePath, OnAccessExcludePath).
# Uses exact string matching — will not add a duplicate line.
# ─────────────────────────────────────────────────────────────────────────────
ensure_multivalue() {
local file="$1"
local directive="$2"
local value="$3"
if ! grep -qF "${directive} ${value}" "$file"; then
if $DRY_RUN; then
info "[DRY] Would add: ${directive} ${value}"
else
echo "${directive} ${value}" >> "$file"
info "Added: ${directive} ${value}"
fi
else
info "Already present: ${directive} ${value}"
fi
}
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 0 — Stop services and back up existing config
#
# We stop both services before touching config files, then reset each config
# to the vendor-supplied default. This guarantees no stale/conflicting
# directives carry over from previous runs or manual edits.
#
# The vendor defaults are at /usr/share/doc/clamav-daemon/examples/ and
# /usr/share/doc/clamav-freshclam/examples/ — but dpkg-reconfigure is the
# cleanest way to regenerate known-good minimal configs.
# ─────────────────────────────────────────────────────────────────────────────
section "Stopping ClamAV services"
if ! $DRY_RUN; then
info "Stopping clamav-daemon and clamav-freshclam..."
systemctl stop clamav-daemon.service 2>/dev/null || true
systemctl stop clamav-freshclam.service 2>/dev/null || true
sleep 1
# Kill any stale freshclam processes (prevents log lock on restart)
if pgrep -x freshclam >/dev/null 2>&1; then
warn "Stale freshclam process found — terminating..."
pkill -x freshclam || true
sleep 1
fi
# Remove any stale lock / pid files
rm -f /var/run/clamav/freshclam.pid
rm -f /var/run/clamav/clamd.pid
info "Stale PID files cleared."
else
info "[DRY] Would stop clamav-daemon and clamav-freshclam services"
info "[DRY] Would kill any stale freshclam processes"
info "[DRY] Would remove stale PID files"
fi
section "Backing up existing configuration"
if ! $DRY_RUN; then
mkdir -p "$BACKUP_DIR"
[[ -f "$CLAMD_CONF" ]] && cp "$CLAMD_CONF" "${BACKUP_DIR}/clamd.conf.bak" && info "Backed up: $CLAMD_CONF → ${BACKUP_DIR}/clamd.conf.bak"
[[ -f "$FRESHCLAM_CONF" ]] && cp "$FRESHCLAM_CONF" "${BACKUP_DIR}/freshclam.conf.bak" && info "Backed up: $FRESHCLAM_CONF → ${BACKUP_DIR}/freshclam.conf.bak"
else
info "[DRY] Would back up $CLAMD_CONF and $FRESHCLAM_CONF to $BACKUP_DIR"
fi
section "Resetting configuration files to vendor baseline"
if ! $DRY_RUN; then
info "Regenerating clamd.conf and freshclam.conf via dpkg-reconfigure..."
# Non-interactive reconfigure restores vendor defaults without prompts
DEBIAN_FRONTEND=noninteractive dpkg-reconfigure clamav-daemon 2>/dev/null || true
DEBIAN_FRONTEND=noninteractive dpkg-reconfigure clamav-freshclam 2>/dev/null || true
info "Config files reset to vendor defaults."
else
info "[DRY] Would run dpkg-reconfigure clamav-daemon and clamav-freshclam"
fi
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 1 — Socket Permissions
# ─────────────────────────────────────────────────────────────────────────────
section "Socket Permissions"
ensure_config "$CLAMD_CONF" "LocalSocketMode" "660"
ensure_config "$CLAMD_CONF" "LocalSocketGroup" "clamav"
ensure_config "$CLAMD_CONF" "PidFile" "/var/run/clamav/clamd.pid"
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 2 — Logging
# ─────────────────────────────────────────────────────────────────────────────
section "Logging"
ensure_config "$CLAMD_CONF" "LogFile" "/var/log/clamav/clamav.log"
ensure_config "$CLAMD_CONF" "LogFileMaxSize" "52428800" # 50 MB
ensure_config "$CLAMD_CONF" "LogTime" "yes"
ensure_config "$CLAMD_CONF" "LogVerbose" "yes"
ensure_config "$CLAMD_CONF" "LogSyslog" "yes"
ensure_config "$CLAMD_CONF" "LogFacility" "LOG_LOCAL6"
ensure_config "$CLAMD_CONF" "LogRotate" "yes"
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 3 — ExcludePath (daemon)
# Excludes virtual/kernel filesystems and container storage to avoid false
# positives and unnecessary CPU load. NOT excluded from on-access scanning.
# ─────────────────────────────────────────────────────────────────────────────
section "ExcludePath Directives"
for path in \
"/proc/" \
"/sys/" \
"/dev/" \
"/run/" \
"/var/lib/docker/" \
"/var/lib/containers/" \
"/var/lib/mysql/" \
"/var/lib/postgresql/" \
"/var/cache/apt/archives/" \
"$QUARANTINE_DIR/"; do
ensure_multivalue "$CLAMD_CONF" "ExcludePath" "$path"
done
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 4 — Alert Settings
# ─────────────────────────────────────────────────────────────────────────────
section "Alert Settings"
for key in \
AlertEncryptedArchive \
AlertEncryptedDoc \
AlertOLE2Macros \
AlertPhishingSSLMismatch \
AlertPhishingCloak \
AlertBrokenExecutables \
AlertBrokenMedia \
AlertPartitionIntersection; do
ensure_config "$CLAMD_CONF" "$key" "yes"
done
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 5 — Database Hygiene
# OfficialDatabaseOnly is 'no' to allow third-party signature databases
# (e.g. Sanesecurity, URLhaus). Set to 'yes' if using ONLY official DBs.
# ─────────────────────────────────────────────────────────────────────────────
section "Database Hygiene"
ensure_config "$CLAMD_CONF" "OfficialDatabaseOnly" "no"
ensure_config "$CLAMD_CONF" "FailIfCvdOlderThan" "7"
ensure_config "$CLAMD_CONF" "ConcurrentDatabaseReload" "yes"
ensure_config "$CLAMD_CONF" "SelfCheck" "1800" # 30-minute self-check interval
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 6 — Detection Refinements
# AlgorithmicDetection was deprecated in ClamAV 0.101 — do not use it.
# HeuristicAlerts is the correct replacement.
# ─────────────────────────────────────────────────────────────────────────────
section "Detection Refinements"
ensure_config "$CLAMD_CONF" "DetectPUA" "yes"
ensure_config "$CLAMD_CONF" "HeuristicAlerts" "yes"
ensure_config "$CLAMD_CONF" "HeuristicScanPrecedence" "yes"
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 7 — File & Scan Limits
# Balanced for typical laptop workloads.
# ─────────────────────────────────────────────────────────────────────────────
section "File and Scan Limits"
ensure_config "$CLAMD_CONF" "MaxScanSize" "209715200" # 200 MB
ensure_config "$CLAMD_CONF" "MaxFileSize" "52428800" # 50 MB
ensure_config "$CLAMD_CONF" "MaxRecursion" "16"
ensure_config "$CLAMD_CONF" "MaxFiles" "15000"
ensure_config "$CLAMD_CONF" "MaxEmbeddedPE" "10485760" # 10 MB
ensure_config "$CLAMD_CONF" "MaxHTMLNormalize" "10485760"
ensure_config "$CLAMD_CONF" "MaxHTMLNoTags" "2097152"
ensure_config "$CLAMD_CONF" "MaxScriptNormalize" "5242880"
ensure_config "$CLAMD_CONF" "MaxZipTypeRcg" "1048576"
ensure_config "$CLAMD_CONF" "OnAccessMaxFileSize" "26214400" # 25 MB
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 8 — On-Access Scanning (real-time workstation protection)
#
# OnAccessPrevention BLOCKS access to detected files rather than just alerting.
# Requires kernel fanotify support (Linux Mint 20+ / kernel 5.4+).
# clamav-daemon must run as root for fanotify to work.
#
# NOTE: OnAccessExtraScanning was permanently disabled in ClamAV 0.100.2 due
# to a kernel resource cleanup bug — omitted to avoid spurious warnings.
# ─────────────────────────────────────────────────────────────────────────────
section "On-Access Scanning (Home Directory Protection)"
# Clear all existing OnAccessIncludePath entries for a clean rewrite
if ! $DRY_RUN; then
if grep -qE "^[[:space:]]*OnAccessIncludePath[[:space:]]" "$CLAMD_CONF"; then
sed -i "/^[[:space:]]*OnAccessIncludePath[[:space:]]/d" "$CLAMD_CONF"
info "Cleared existing OnAccessIncludePath entries for clean rewrite."
fi
fi
for oa_path in "/home" "/tmp" "/var/tmp" "/root"; do
ensure_multivalue "$CLAMD_CONF" "OnAccessIncludePath" "$oa_path"
done
ensure_config "$CLAMD_CONF" "OnAccessPrevention" "yes"
ensure_config "$CLAMD_CONF" "OnAccessExcludeUID" "0" # Prevents clamd scanning its own writes
for path in "/proc" "/sys" "/dev" "/run" "$QUARANTINE_DIR"; do
ensure_multivalue "$CLAMD_CONF" "OnAccessExcludePath" "$path"
done
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 9 — Freshclam (signature updates via systemd service)
#
# UPDATE STRATEGY: clamav-freshclam.service manages all signature updates.
# No cron job is used. This eliminates the log-lock conflict caused by
# multiple freshclam processes (systemd daemon + cron job) competing for
# /var/log/clamav/freshclam.log simultaneously.
#
# Checks 6 = freshclam checks for new signatures 6 times per day (every 4 hours).
# This matches Sanesecurity's recommended mirror polling frequency and stays within
# URLhaus (abuse.ch) rate limits. Do not increase above 6 in fleet deployments.
# ─────────────────────────────────────────────────────────────────────────────
section "Freshclam Settings"
ensure_config "$FRESHCLAM_CONF" "Checks" "6"
ensure_config "$FRESHCLAM_CONF" "MaxAttempts" "5"
ensure_config "$FRESHCLAM_CONF" "ConnectTimeout" "30"
ensure_config "$FRESHCLAM_CONF" "ReceiveTimeout" "30"
ensure_config "$FRESHCLAM_CONF" "LogVerbose" "yes"
ensure_config "$FRESHCLAM_CONF" "LogSyslog" "yes"
ensure_config "$FRESHCLAM_CONF" "LogFacility" "LOG_LOCAL6"
ensure_config "$FRESHCLAM_CONF" "NotifyClamd" "$CLAMD_CONF" # Triggers live DB reload after update
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 9b — Third-Party Signatures via DatabaseCustomURL
#
# Freshclam natively supports pulling third-party signature databases via
# DatabaseCustomURL. OfficialDatabaseOnly must be 'no' (set in Section 5).
#
# SOURCES:
# Sanesecurity — active since 2006, hourly updates, free. Covers phishing,
# malware, macros, spam and zero-day threats.
# URLhaus — abuse.ch project, updated frequently. Active malware
# distribution URLs. Free, no account required.
#
# FALSE POSITIVE RISK: Only Low FP risk databases are enabled below.
# Medium/High FP databases are commented out — enable with caution.
#
# MEMORY: Each database increases clamd RAM usage ~150-250 MB total for
# the selection below. Monitor with: systemctl status clamav-daemon
# ─────────────────────────────────────────────────────────────────────────────
section "Third-Party Signatures (DatabaseCustomURL)"
SANESEC_MIRROR="https://mirror.rollernet.us/sanesecurity"
# --- Required Sanesecurity support files ---
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/sanesecurity.ftm"
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/sigwhitelist.ign2"
# --- Low FP risk: Sanesecurity core databases ---
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/junk.ndb"
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/jurlbl.ndb"
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/phish.ndb"
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/rogue.hdb"
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/scam.ndb"
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/spamimg.hdb"
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/spamattach.hdb"
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/blurl.ndb"
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/malwarehash.hsb"
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/hackingteam.hsb"
# --- Low FP risk: Foxhole (executable/archive content detection) ---
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/foxhole_generic.cdb"
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/foxhole_filename.cdb"
# --- Low FP risk: OITC winnow databases ---
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/winnow_malware.hdb"
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/winnow_malware_links.ndb"
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/winnow_extended_malware.hdb"
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/winnow.attachments.hdb"
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "${SANESEC_MIRROR}/winnow_bad_cw.hdb"
# --- URLhaus: active malware distribution URLs (abuse.ch) — Low FP risk ---
ensure_multivalue "$FRESHCLAM_CONF" "DatabaseCustomURL" "https://urlhaus.abuse.ch/downloads/urlhaus.ndb"
# --- MEDIUM FP RISK — disabled by default, uncomment to enable ---
# DatabaseCustomURL ${SANESEC_MIRROR}/jurlbla.ndb
# DatabaseCustomURL ${SANESEC_MIRROR}/lott.ndb
# DatabaseCustomURL ${SANESEC_MIRROR}/spam.ldb
# DatabaseCustomURL ${SANESEC_MIRROR}/badmacro.ndb
# DatabaseCustomURL ${SANESEC_MIRROR}/shelter.ldb
# DatabaseCustomURL ${SANESEC_MIRROR}/spear.ndb
# DatabaseCustomURL ${SANESEC_MIRROR}/spearl.ndb
# DatabaseCustomURL ${SANESEC_MIRROR}/foxhole_js.cdb
# DatabaseCustomURL ${SANESEC_MIRROR}/foxhole_js.ndb
# DatabaseCustomURL ${SANESEC_MIRROR}/winnow_spam_complete.ndb
# DatabaseCustomURL ${SANESEC_MIRROR}/winnow_phish_complete_url.ndb
# DatabaseCustomURL ${SANESEC_MIRROR}/winnow.complex.patterns.ldb
# DatabaseCustomURL ${SANESEC_MIRROR}/winnow_extended_malware_links.ndb
# --- HIGH FP RISK — not recommended for workstations ---
# DatabaseCustomURL ${SANESEC_MIRROR}/foxhole_all.cdb
# DatabaseCustomURL ${SANESEC_MIRROR}/foxhole_all.ndb
# DatabaseCustomURL ${SANESEC_MIRROR}/foxhole_mail.cdb
# DatabaseCustomURL ${SANESEC_MIRROR}/winnow_phish_complete.ndb
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 10 — Remove legacy cron job (if present from earlier script versions)
#
# Previous versions of this script installed a cron job at /etc/cron.d/clamav-scheduled
# to manage freshclam updates. This caused log-lock conflicts when both the cron job
# and the systemd daemon attempted to run freshclam simultaneously.
# This section removes that file if it exists.
# ─────────────────────────────────────────────────────────────────────────────
section "Removing legacy cron job (if present)"
LEGACY_CRON="/etc/cron.d/clamav-scheduled"
if [[ -f "$LEGACY_CRON" ]]; then
if $DRY_RUN; then
warn "[DRY] Would remove legacy cron file: $LEGACY_CRON"
else
rm -f "$LEGACY_CRON"
warn "Removed legacy cron file: $LEGACY_CRON"
fi
else
info "No legacy cron file found — nothing to remove."
fi
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 11 — Enable and start services
#
# clamav-freshclam.service: manages signature updates (Checks 24 = hourly)
# clamav-daemon.service: provides on-access scanning and clamdscan socket
#
# freshclam runs first to ensure a current signature database exists before
# the daemon starts. If the daemon starts with a missing/outdated DB and
# FailIfCvdOlderThan is set, it will refuse to start.
# ─────────────────────────────────────────────────────────────────────────────
section "Enabling and starting ClamAV services"
if $DRY_RUN; then
info "[DRY] Would enable and start clamav-freshclam.service"
info "[DRY] Would run an initial freshclam update"
info "[DRY] Would enable and start clamav-daemon.service"
else
info "Enabling clamav-freshclam.service..."
systemctl enable clamav-freshclam.service
info "Running initial freshclam update (this may take a moment)..."
freshclam --quiet || warn "Initial freshclam update failed — daemon may start with existing DB if present."
info "Starting clamav-freshclam.service..."
systemctl start clamav-freshclam.service
sleep 2
if systemctl is-active --quiet clamav-freshclam.service; then
info "SUCCESS: clamav-freshclam.service is active."
else
warn "clamav-freshclam.service did not start cleanly."
systemctl status clamav-freshclam.service || true
fi
info "Enabling clamav-daemon.service..."
systemctl enable clamav-daemon.service
info "Starting clamav-daemon.service..."
if systemctl start clamav-daemon.service; then
sleep 2
if systemctl is-active --quiet clamav-daemon.service; then
info "SUCCESS: clamav-daemon.service is active."
else
error "clamav-daemon failed to start."
systemctl status clamav-daemon.service
exit 1
fi
else
error "Failed to start clamav-daemon."
exit 1
fi
fi
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 12 — EICAR test (functional validation)
# ─────────────────────────────────────────────────────────────────────────────
section "EICAR Functional Test"
if $DRY_RUN; then
info "[DRY] Would run EICAR detection test against clamd."
else
EICAR_FILE="/tmp/eicar_test_$$.txt"
python3 -c "open('${EICAR_FILE}','w').write('X5O!P%@AP[4\\PZX54(P^)7CC)7}\$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!\$H+H*')"
sleep 1
info "Running EICAR test against clamd..."
SCAN_RESULT=$(clamdscan --fdpass --no-summary "$EICAR_FILE" 2>&1 || true)
if echo "$SCAN_RESULT" | grep -q "FOUND"; then
info "EICAR test: PASSED — clamd detected test signature correctly."
else
warn "EICAR test: FAILED or clamd not yet responding."
warn "Scan output: $SCAN_RESULT"
warn "Manual test: clamdscan --fdpass /tmp/eicar_test.txt"
fi
rm -f "$EICAR_FILE"
fi
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 13 — Systemd Failure Notification
#
# Installs a reusable service template: notify-failure@.service
# When any monitored unit fails, systemd instantiates this template with the
# failed unit name and sends a failure email via the local MTA (Postfix).
#
# The template uses %i (the instance name) to identify which service failed.
# OnFailure= drop-in overrides are written for both ClamAV units — these are
# placed in /etc/systemd/system/<unit>.d/override.conf so they survive
# package updates without modifying vendor unit files.
#
# EMAIL DESTINATION: root (Postfix alias — redirect in /etc/aliases as needed)
# SILENT ON SUCCESS: no email is sent for normal operation
# REUSABLE: notify-failure@.service can be referenced by any other unit
# ─────────────────────────────────────────────────────────────────────────────
section "Systemd Failure Notification"
NOTIFY_UNIT="/etc/systemd/system/notify-failure@.service"
NOTIFY_CONTENT="[Unit]
Description=Failure notification for %i
After=network.target
[Service]
Type=oneshot
ExecStart=/bin/bash -c '\
UNIT=%i; \
HOST=\$(hostname -f); \
TIMESTAMP=\$(date \"+%%Y-%%m-%%d %%H:%%M:%%S\"); \
JOURNAL=\$(journalctl -u \"\${UNIT}\" -n 50 --no-pager 2>/dev/null || echo \"Journal unavailable\"); \
printf \"Subject: [ClamAV FAILURE] \${UNIT} failed on \${HOST}\\nTo: root\\nContent-Type: text/plain\\n\\nService failure alert\\n======================\\nHost: \${HOST}\\nUnit: \${UNIT}\\nTime: \${TIMESTAMP}\\n\\nLast 50 journal lines:\\n\\n\${JOURNAL}\\n\" \
| /usr/sbin/sendmail -t'
"
if $DRY_RUN; then
info "[DRY] Would install notify-failure@.service template at $NOTIFY_UNIT"
else
printf "%s" "$NOTIFY_CONTENT" > "$NOTIFY_UNIT"
chmod 0644 "$NOTIFY_UNIT"
info "Installed: $NOTIFY_UNIT"
fi
# Install OnFailure= drop-in overrides for both ClamAV units
for UNIT in clamav-daemon.service clamav-freshclam.service; do
DROPIN_DIR="/etc/systemd/system/${UNIT}.d"
DROPIN_FILE="${DROPIN_DIR}/notify-on-failure.conf"
DROPIN_CONTENT="[Unit]
OnFailure=notify-failure@%n.service
"
if $DRY_RUN; then
info "[DRY] Would install OnFailure drop-in for ${UNIT} at ${DROPIN_FILE}"
else
mkdir -p "$DROPIN_DIR"
printf "%s" "$DROPIN_CONTENT" > "$DROPIN_FILE"
chmod 0644 "$DROPIN_FILE"
info "Installed drop-in: $DROPIN_FILE"
fi
done
# Reload systemd to pick up the new unit and drop-ins
if $DRY_RUN; then
info "[DRY] Would run systemctl daemon-reload"
else
systemctl daemon-reload
info "systemctl daemon-reload complete."
fi
# End of scriptWarning
If you let the official ClamAV database, get older than 7 days - the daemon won't start
To fix this run the following commands. This will pull a fresh version of the database and restart the daemon.
sudo freshclamsudo systemctl start clamav-daemon.service && systemctl status clamav-daemon.service
Changes
- Refer to readme top of file. Problems were discovered and rectified
Hope this helps
Now back to the LXC Application server. I found another hole. Rather annoyed.
On the plus side - the LXC proxy script worked a treat. About 15 minutes to rebuild and restore from the Zoraxy backup file. Version bumped to latest.
#enoughsaid