1{ config, lib, pkgs, ... }:
2with lib;
3let
4 clamavUser = "clamav";
5 stateDir = "/var/lib/clamav";
6 runDir = "/run/clamav";
7 clamavGroup = clamavUser;
8 cfg = config.services.clamav;
9 pkg = pkgs.clamav;
10
11 clamdConfigFile = pkgs.writeText "clamd.conf" ''
12 DatabaseDirectory ${stateDir}
13 LocalSocket ${runDir}/clamd.ctl
14 PidFile ${runDir}/clamd.pid
15 TemporaryDirectory /tmp
16 User clamav
17 Foreground yes
18
19 ${cfg.daemon.extraConfig}
20 '';
21
22 freshclamConfigFile = pkgs.writeText "freshclam.conf" ''
23 DatabaseDirectory ${stateDir}
24 Foreground yes
25 Checks ${toString cfg.updater.frequency}
26
27 ${cfg.updater.extraConfig}
28
29 DatabaseMirror database.clamav.net
30 '';
31in
32{
33 options = {
34 services.clamav = {
35 daemon = {
36 enable = mkEnableOption "ClamAV clamd daemon";
37
38 extraConfig = mkOption {
39 type = types.lines;
40 default = "";
41 description = ''
42 Extra configuration for clamd. Contents will be added verbatim to the
43 configuration file.
44 '';
45 };
46 };
47 updater = {
48 enable = mkEnableOption "ClamAV freshclam updater";
49
50 frequency = mkOption {
51 type = types.int;
52 default = 12;
53 description = ''
54 Number of database checks per day.
55 '';
56 };
57
58 interval = mkOption {
59 type = types.str;
60 default = "hourly";
61 description = ''
62 How often freshclam is invoked. See systemd.time(7) for more
63 information about the format.
64 '';
65 };
66
67 extraConfig = mkOption {
68 type = types.lines;
69 default = "";
70 description = ''
71 Extra configuration for freshclam. Contents will be added verbatim to the
72 configuration file.
73 '';
74 };
75 };
76 };
77 };
78
79 config = mkIf (cfg.updater.enable || cfg.daemon.enable) {
80 environment.systemPackages = [ pkg ];
81
82 users.users = singleton {
83 name = clamavUser;
84 uid = config.ids.uids.clamav;
85 group = clamavGroup;
86 description = "ClamAV daemon user";
87 home = stateDir;
88 };
89
90 users.groups = singleton {
91 name = clamavGroup;
92 gid = config.ids.gids.clamav;
93 };
94
95 environment.etc."clamav/freshclam.conf".source = freshclamConfigFile;
96 environment.etc."clamav/clamd.conf".source = clamdConfigFile;
97
98 systemd.services.clamav-daemon = optionalAttrs cfg.daemon.enable {
99 description = "ClamAV daemon (clamd)";
100 after = optional cfg.updater.enable "clamav-freshclam.service";
101 requires = optional cfg.updater.enable "clamav-freshclam.service";
102 wantedBy = [ "multi-user.target" ];
103 restartTriggers = [ clamdConfigFile ];
104
105 preStart = ''
106 mkdir -m 0755 -p ${runDir}
107 chown ${clamavUser}:${clamavGroup} ${runDir}
108 '';
109
110 serviceConfig = {
111 ExecStart = "${pkg}/bin/clamd";
112 ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
113 PrivateTmp = "yes";
114 PrivateDevices = "yes";
115 PrivateNetwork = "yes";
116 };
117 };
118
119 systemd.timers.clamav-freshclam = optionalAttrs cfg.updater.enable {
120 description = "Timer for ClamAV virus database updater (freshclam)";
121 wantedBy = [ "timers.target" ];
122 timerConfig = {
123 OnCalendar = cfg.updater.interval;
124 Unit = "clamav-freshclam.service";
125 };
126 };
127
128 systemd.services.clamav-freshclam = optionalAttrs cfg.updater.enable {
129 description = "ClamAV virus database updater (freshclam)";
130 restartTriggers = [ freshclamConfigFile ];
131
132 preStart = ''
133 mkdir -m 0755 -p ${stateDir}
134 chown ${clamavUser}:${clamavGroup} ${stateDir}
135 '';
136
137 serviceConfig = {
138 Type = "oneshot";
139 ExecStart = "${pkg}/bin/freshclam";
140 PrivateTmp = "yes";
141 PrivateDevices = "yes";
142 };
143 };
144 };
145}