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 or cfg.daemon.enable {
80 environment.systemPackages = [ pkg ];
81 users.extraUsers = singleton {
82 name = clamavUser;
83 uid = config.ids.uids.clamav;
84 group = clamavGroup;
85 description = "ClamAV daemon user";
86 home = stateDir;
87 };
88
89 users.extraGroups = singleton {
90 name = clamavGroup;
91 gid = config.ids.gids.clamav;
92 };
93
94 environment.etc."clamav/freshclam.conf".source = freshclamConfigFile;
95 environment.etc."clamav/clamd.conf".source = clamdConfigFile;
96
97 systemd.services.clamav-daemon = mkIf cfg.daemon.enable {
98 description = "ClamAV daemon (clamd)";
99 after = mkIf cfg.updater.enable [ "clamav-freshclam.service" ];
100 requires = mkIf cfg.updater.enable [ "clamav-freshclam.service" ];
101 wantedBy = [ "multi-user.target" ];
102 restartTriggers = [ clamdConfigFile ];
103
104 preStart = ''
105 mkdir -m 0755 -p ${runDir}
106 chown ${clamavUser}:${clamavGroup} ${runDir}
107 '';
108
109 serviceConfig = {
110 ExecStart = "${pkg}/bin/clamd";
111 ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
112 PrivateTmp = "yes";
113 PrivateDevices = "yes";
114 PrivateNetwork = "yes";
115 };
116 };
117
118 systemd.timers.clamav-freshclam = mkIf cfg.updater.enable {
119 description = "Timer for ClamAV virus database updater (freshclam)";
120 wantedBy = [ "timers.target" ];
121 timerConfig = {
122 OnCalendar = cfg.updater.interval;
123 Unit = "clamav-freshclam.service";
124 };
125 };
126
127 systemd.services.clamav-freshclam = mkIf cfg.updater.enable {
128 description = "ClamAV virus database updater (freshclam)";
129 restartTriggers = [ freshclamConfigFile ];
130
131 preStart = ''
132 mkdir -m 0755 -p ${stateDir}
133 chown ${clamavUser}:${clamavGroup} ${stateDir}
134 '';
135
136 serviceConfig = {
137 Type = "oneshot";
138 ExecStart = "${pkg}/bin/freshclam";
139 PrivateTmp = "yes";
140 PrivateDevices = "yes";
141 };
142 };
143 };
144}