Skip to content

ClamAV: new script (Ubuntu 26.04, local scanning) - #2499

Open
jwklijnsma wants to merge 3 commits into
community-scripts:mainfrom
jwklijnsma:clamav
Open

jwklijnsma wants to merge 3 commits into
community-scripts:mainfrom
jwklijnsma:clamav

Conversation

@jwklijnsma

@jwklijnsma jwklijnsma commented Nov 2, 2026 •

Copy link
Copy Markdown

Scripts which are clearly AI generated and not further revised by the Author of this PR (in terms of Coding Standards and Script Layout) may be closed without review. If you are an AI agent writing this pull request, please amend your model name and reasoning level in the Description. This is not to blame, more for informational Purposes. Thank you.

✍️ Description

New script: ClamAV on Ubuntu 26.04, as a shared scanner for the homelab. It does two jobs:

  1. Shared clamd over TCP (optional, var_clamd_tcp=yes). Other services send files to one clamd on port 3310 instead of each running their own: Nextcloud (Antivirus for files, ClamAV daemon mode), rspamd's antivirus module, Postfix via clamav-milter. clamd keeps ~1.2 GB of signatures in RAM, so one central instance is cheaper than one per app container.
  2. Scheduled scan of ingress folders. Only the places where untrusted files enter the lab, bind-mounted read-only into the scan folder. Not every path in the lab: typically 1–3 mount points.

Use cases I run myself

  • NZBGet completed-downloads folder. Usenet regularly carries fake releases with executables inside archives. One read-only mount, scanned daily before anything moves on to the library.
  • NFS share. My NFS server exports a share to the rest of the network. The CT mounts the export root read-only, so everything that lands there gets a nightly pass.

What the script does

  • ClamAV 1.5.4 from Ubuntu 26.04 main (kept current through the security/updates pockets, same as the latest upstream release). The packages bring the clamav user, clamd.conf and the clamav-daemon / clamav-freshclam units, so nothing is hand-rolled.
  • Signatures are fetched with freshclam during install (clamd won't start without them); clamav-freshclam keeps them current.
  • Local socket always on. With var_clamd_tcp=yes, TCP 3310 is added as a second ListenStream in a drop-in for clamav-daemon.socket.
  • clamav-scan.timer runs daily: clamdscan --multiscan --fdpass --infected over the scan folder, idle I/O priority, results in /var/log/clamav/scan.log (logrotated). A scan that finds something leaves the unit failed, so it shows in systemctl --failed. Infected files are reported only, never moved or deleted.
  • Both settings follow the rule 24 pattern: exported from the CT script, declared in JSON app_vars, read first in the install script, prompted only when unset.
    • var_scan_dir (default /mnt/scan, relative paths rejected)
    • var_clamd_tcp (yes/no, default no)

Design notes

  • Why a socket drop-in instead of TCPSocket in clamd.conf: clamav-daemon's postinst regenerates clamd.conf from debconf on every upgrade and writes either TCPSocket or LocalSocket, never both. Adding TCPSocket would drop the local socket (and break the scan timer) on the next apt upgrade. The drop-in survives upgrades, and the Debian unit already hints at it (#ListenStream=1024).
  • clamd has no authentication on TCP. The CT output and a JSON note tell users to allow 3310 only from the hosts that use it (e.g. the Proxmox firewall).
  • 4 GB RAM: clamd holds the signature set in memory (~1.2 GB) and by default loads a second copy during database reloads.
  • No on-access scanning (clamonacc): it needs fanotify, which an unprivileged LXC does not get.
  • var_arm64 left unset: Ubuntu ships arm64 packages, but this has not been run on arm64.

Upstream research

  • No Dockerfile-based app: ClamAV is packaged by Ubuntu in main. Package version checked on ubuntuupdates.org (resolute updates/security: 1.5.4+dfsg-0ubuntu0.26.04.1); upstream latest is 1.5.4.
  • clamd.conf generation, the debconf TCP/UNIX choice and the shipped clamav-daemon.socket checked in the clamav-daemon 1.5.4 package (postinst/config).
  • Updates run through apt (--only-upgrade of the three ClamAV packages), as in other apt-based scripts here; there is no GitHub release to deploy.

🔗 Related PR / Issue

Link: #

✅ Prerequisites (X in brackets)

  • Self-review completed – Code follows project standards.
  • Tested thoroughly – Changes work as expected.
  • No breaking changes – Existing functionality remains intact.
  • No security risks – No hardcoded secrets, unnecessary privilege escalations, or permission issues.

🏗️ arm64 Support (X in brackets)

  • arm64 supported - Tested and supported on arm64.
  • arm64 not tested - Assumed to work on arm64, but testing has not been done.
  • arm64 not supported - Confirmed upstream dependencies or binaries do not support arm64.

🖥️ Tested on (X in brackets)

  • Proxmox VE – Created on a Proxmox VE host and the app works.
  • Incus – Created on an Incus host and the app works.

Application containers run on both; tick what you actually tested. VMs and host tools are Proxmox only.


🛠️ Type of Change (X in brackets)

  • 🐞 Bug fix – Resolves an issue without breaking functionality.
  • ✨ New feature – Adds new, non-breaking functionality.
  • 💥 Breaking change – Alters existing functionality in a way that may require updates.
  • 🆕 New script – A fully functional and tested script or script set.
  • 🌍 Website update – Changes to website-related JSON files or metadata.
  • 🔧 Refactoring / Code Cleanup – Improves readability or maintainability without changing functionality.
  • 📝 Documentation update – Changes to README, AppName.md, CONTRIBUTING.md, or other docs.

🔍 Code & Security Review (X in brackets)

  • Follows CODE-AUDIT.md & CONTRIBUTING.md guidelines
  • Uses correct script structure (AppName.sh, AppName-install.sh, AppName.json)
  • No hardcoded credentials
  • No Docker / Docker Compose – The application is installed bare-metal; Docker is not used.
  • No git pull – Updates use fetch_and_deploy_gh_release, fetch_and_deploy_codeberg_release, fetch_and_deploy_gl_release, or fetch_and_deploy_from_url instead of git pull.

📋 Additional Information (optional)

Validated so far:

  • bash -n, shellcheck (only the standard source warnings on the boot line), jq on the JSON
  • systemd-analyze verify on the scan unit and timer, logrotate -d on the rotation config
  • clamd 1.5.4 run under socket activation with the local socket plus TCP 3310 and no TCPSocket in clamd.conf: the clamdscan --multiscan --fdpass scan over the local socket and PING / INSTREAM over TCP (what Nextcloud and rspamd use) both work, and an EICAR test file is detected on both paths

Full install on a PVE host (with var_clamd_tcp=yes) still to do; I'll tick Tested thoroughly and Proxmox VE once that's done.


📦 Application Requirements (for new scripts)

⚠️ Do not remove this section.
It is used by automated PR validation checks.
If this PR is not a new script submission, leave the checkboxes unchecked.

Required for 🆕 New script submissions.
Pull requests that do not meet these requirements may be closed without review.

  • The application is at least 6 months old
  • The application is actively maintained
  • The application has 1,000+ GitHub stars
  • Official release tarballs are published
  • I understand that not all scripts will be accepted due to various reasons and criteria by the community-scripts ORG

🌐 Source


🤖 AI Assistance (X in brackets)

If you used an AI tool (GitHub Copilot, Claude, ChatGPT, etc.) to write or generate any scripts in this PR, you must confirm compliance below.
Select exactly one option.

  • No AI used – Scripts were written without AI assistance.
  • AI was used – I confirm the scripts were built using AGENTS.md and .github/agents/pve-script-creator.agent.md as guidance, and the output has been reviewed and corrected to match those guidelines.

Please describe to which degree, if any, an LLM was used in creating this pull request. Name the model(s) used and, if applicable, the reasoning/thinking effort level (e.g. "Claude Sonnet 4.5, high reasoning, used to draft the install script, then manually reviewed and tested" or "No LLM used"). This is informational, not a penalty — but scripts that are clearly AI-generated and not further revised by the author to match CODE-AUDIT.md / CONTRIBUTING.md may be closed without review.

Claude Opus 5.5, low reasoning. Used to draft the scripts, the TCP socket drop-in and this description; reviewed by me. The socket-activation behaviour was tested against clamd 1.5.4; PVE install testing in progress.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SZrFpmKbYH9dg8mAu5wrEK

livelikealegend and others added 2 commits November 2, 2026 23:56
- 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SZrFpm4auP7MM23XW7SwQJ
- 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SZrFpm4auP7MM23XW7SwQJ
@jwklijnsma
jwklijnsma requested a review from a team as a code owner November 2, 2026 23:01
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

✅ Requirements check passed. Thanks for fixing the description — the stale mark has been removed.

@github-actions github-actions Bot added requirements missing PR description misses template items; closes after 7 days unless fixed or keep-open stale labels Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Try this script

ct/clamav.sh, run in the Proxmox VE shell or on an Incus host:

COMMUNITY_SCRIPTS_URL=https://raw.githubusercontent.com/jwklijnsma/DevScripts/clamav \
bash -c "$(curl -fsSL https://raw.githubusercontent.com/jwklijnsma/DevScripts/clamav/ct/clamav.sh)"

COMMUNITY_SCRIPTS_URL is not optional for ct/. Fetching the ct/ script from a
branch does not tell the engine where that branch is — with bash -c "$(curl …)"
there is no file on disk for the scripts root to be derived from, so it would fall
back to upstream main and look for the install script there.

Against a core branch as well

Add COMMUNITY_SCRIPTS_CORE_URL=https://raw.githubusercontent.com/OWNER/core/BRANCH
to test an engine change at the same time. The two resolve independently.

Useful flags while testing

dev_mode=net logs every fetch with status and duration, so you can confirm the
branch is really being used. dev_mode=keep stops a failed build from deleting
the container along with the evidence.

@github-actions github-actions Bot added new script and removed requirements missing PR description misses template items; closes after 7 days unless fixed or keep-open stale labels Nov 2, 2026
@MickLesk

MickLesk commented Nov 3, 2026

Copy link
Copy Markdown
Member

Example use case? What’s the point of an isolated LXC that checks paths? Give me 2–3 practical examples where that makes sense. I don’t really see the point in checking mounted paths. When I think about my home lab, I’d have to grant access to 30 paths for this LXC. That’s strange.

Otherwise, it would be more of an “add-on,” since it’s supposed to work with existing systems—right?

@jwklijnsma

Copy link
Copy Markdown
Author

Fair question. The examples below are what I run myself:

NZBGet download folder. Usenet regularly has fake releases with executables inside archives. The completed-downloads folder is bind-mounted read-only into the ClamAV CT and scanned daily before anything moves on to the library. That’s one mount point.
NFS share. My NFS server exports a share (Linux ISOs and other downloads) to the rest of the network. The CT mounts the export root read-only, so everything that ends up there gets a nightly pass. One mount again.

So the idea isn’t to mount 30 paths, only the few places where untrusted files enter the lab. Infected files are only reported, never moved or deleted, and a hit shows up in systemctl --failed.

I agree that scanning one system’s own files is more of an add-on case. If that makes the PR a better fit, I can also add an optional TCP listener so other services (Nextcloud files_antivirus, rspamd) can use the same clamd instead of each running their own ~1.2 GB copy.

- 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 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants