at master 10 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 clamavUser = "clamav"; 9 stateDir = "/var/lib/clamav"; 10 clamavGroup = clamavUser; 11 cfg = config.services.clamav; 12 13 toKeyValue = lib.generators.toKeyValue { 14 mkKeyValue = lib.generators.mkKeyValueDefault { } " "; 15 listsAsDuplicateKeys = true; 16 }; 17 18 clamdConfigFile = pkgs.writeText "clamd.conf" (toKeyValue cfg.daemon.settings); 19 freshclamConfigFile = pkgs.writeText "freshclam.conf" (toKeyValue cfg.updater.settings); 20 fangfrischConfigFile = pkgs.writeText "fangfrisch.conf" '' 21 ${lib.generators.toINI { } cfg.fangfrisch.settings} 22 ''; 23in 24{ 25 imports = [ 26 (lib.mkRemovedOptionModule [ 27 "services" 28 "clamav" 29 "updater" 30 "config" 31 ] "Use services.clamav.updater.settings instead.") 32 (lib.mkRemovedOptionModule [ 33 "services" 34 "clamav" 35 "updater" 36 "extraConfig" 37 ] "Use services.clamav.updater.settings instead.") 38 (lib.mkRemovedOptionModule [ 39 "services" 40 "clamav" 41 "daemon" 42 "extraConfig" 43 ] "Use services.clamav.daemon.settings instead.") 44 ]; 45 46 options = { 47 services.clamav = { 48 package = lib.mkPackageOption pkgs "clamav" { }; 49 daemon = { 50 enable = lib.mkEnableOption "ClamAV clamd daemon"; 51 52 settings = lib.mkOption { 53 type = 54 with lib.types; 55 attrsOf (oneOf [ 56 bool 57 int 58 str 59 (listOf str) 60 ]); 61 default = { }; 62 description = '' 63 ClamAV configuration. Refer to <https://linux.die.net/man/5/clamd.conf>, 64 for details on supported values. 65 ''; 66 }; 67 }; 68 updater = { 69 enable = lib.mkEnableOption "ClamAV freshclam updater"; 70 71 frequency = lib.mkOption { 72 type = lib.types.int; 73 default = 12; 74 description = '' 75 Number of database checks per day. 76 ''; 77 }; 78 79 interval = lib.mkOption { 80 type = lib.types.str; 81 default = "hourly"; 82 description = '' 83 How often freshclam is invoked. See {manpage}`systemd.time(7)` for more 84 information about the format. 85 ''; 86 }; 87 88 settings = lib.mkOption { 89 type = 90 with lib.types; 91 attrsOf (oneOf [ 92 bool 93 int 94 str 95 (listOf str) 96 ]); 97 default = { }; 98 description = '' 99 freshclam configuration. Refer to <https://linux.die.net/man/5/freshclam.conf>, 100 for details on supported values. 101 ''; 102 }; 103 }; 104 fangfrisch = { 105 enable = lib.mkEnableOption "ClamAV fangfrisch updater"; 106 107 interval = lib.mkOption { 108 type = lib.types.str; 109 default = "hourly"; 110 description = '' 111 How often freshclam is invoked. See {manpage}`systemd.time(7)` for more 112 information about the format. 113 ''; 114 }; 115 116 settings = lib.mkOption { 117 type = lib.types.submodule { 118 freeformType = 119 with lib.types; 120 attrsOf ( 121 attrsOf (oneOf [ 122 str 123 int 124 bool 125 ]) 126 ); 127 }; 128 default = { }; 129 example = { 130 securiteinfo = { 131 enabled = "yes"; 132 customer_id = "your customer_id"; 133 }; 134 }; 135 description = '' 136 fangfrisch configuration. Refer to <https://rseichter.github.io/fangfrisch/#_configuration>, 137 for details on supported values. 138 Note that by default urlhaus and sanesecurity are enabled. 139 ''; 140 }; 141 }; 142 143 scanner = { 144 enable = lib.mkEnableOption "ClamAV scanner"; 145 146 interval = lib.mkOption { 147 type = lib.types.str; 148 default = "*-*-* 04:00:00"; 149 description = '' 150 How often clamdscan is invoked. See {manpage}`systemd.time(7)` for more 151 information about the format. 152 By default this runs using 10 cores at most, be sure to run it at a time of low traffic. 153 ''; 154 }; 155 156 scanDirectories = lib.mkOption { 157 type = with lib.types; listOf str; 158 default = [ 159 "/home" 160 "/var/lib" 161 "/tmp" 162 "/etc" 163 "/var/tmp" 164 ]; 165 description = '' 166 List of directories to scan. 167 The default includes everything I could think of that is valid for nixos. Feel free to contribute a PR to add to the default if you see something missing. 168 ''; 169 }; 170 }; 171 }; 172 }; 173 174 config = lib.mkIf (cfg.updater.enable || cfg.daemon.enable) { 175 environment.systemPackages = [ cfg.package ]; 176 177 users.users.${clamavUser} = { 178 uid = config.ids.uids.clamav; 179 group = clamavGroup; 180 description = "ClamAV daemon user"; 181 home = stateDir; 182 }; 183 184 users.groups.${clamavGroup} = { 185 gid = config.ids.gids.clamav; 186 }; 187 188 services.clamav.daemon.settings = { 189 DatabaseDirectory = stateDir; 190 LocalSocket = "/run/clamav/clamd.ctl"; 191 PidFile = "/run/clamav/clamd.pid"; 192 User = "clamav"; 193 Foreground = true; 194 }; 195 196 services.clamav.updater.settings = { 197 DatabaseDirectory = stateDir; 198 Foreground = true; 199 Checks = cfg.updater.frequency; 200 DatabaseMirror = [ "database.clamav.net" ]; 201 }; 202 203 services.clamav.fangfrisch.settings = { 204 DEFAULT.db_url = lib.mkDefault "sqlite:////var/lib/clamav/fangfrisch_db.sqlite"; 205 DEFAULT.local_directory = lib.mkDefault stateDir; 206 DEFAULT.log_level = lib.mkDefault "INFO"; 207 urlhaus.enabled = lib.mkDefault "yes"; 208 urlhaus.max_size = lib.mkDefault "2MB"; 209 sanesecurity.enabled = lib.mkDefault "yes"; 210 }; 211 212 environment.etc."clamav/freshclam.conf".source = freshclamConfigFile; 213 environment.etc."clamav/clamd.conf".source = clamdConfigFile; 214 215 systemd.slices.system-clamav = { 216 description = "ClamAV Antivirus Slice"; 217 }; 218 219 systemd.services.clamav-daemon = lib.mkIf cfg.daemon.enable { 220 description = "ClamAV daemon (clamd)"; 221 documentation = [ "man:clamd(8)" ]; 222 after = lib.optionals cfg.updater.enable [ "clamav-freshclam.service" ]; 223 wants = lib.optionals cfg.updater.enable [ "clamav-freshclam.service" ]; 224 wantedBy = [ "multi-user.target" ]; 225 restartTriggers = [ clamdConfigFile ]; 226 227 serviceConfig = { 228 ExecStart = "${cfg.package}/bin/clamd"; 229 ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID"; 230 User = clamavUser; 231 Group = clamavGroup; 232 StateDirectory = "clamav"; 233 RuntimeDirectory = "clamav"; 234 PrivateTmp = "yes"; 235 PrivateDevices = "yes"; 236 PrivateNetwork = "yes"; 237 Slice = "system-clamav.slice"; 238 }; 239 }; 240 241 systemd.timers.clamav-freshclam = lib.mkIf cfg.updater.enable { 242 description = "Timer for ClamAV virus database updater (freshclam)"; 243 wantedBy = [ "timers.target" ]; 244 timerConfig = { 245 OnCalendar = cfg.updater.interval; 246 Unit = "clamav-freshclam.service"; 247 }; 248 }; 249 250 systemd.services.clamav-freshclam = lib.mkIf cfg.updater.enable { 251 description = "ClamAV virus database updater (freshclam)"; 252 documentation = [ "man:freshclam(1)" ]; 253 restartTriggers = [ freshclamConfigFile ]; 254 requires = [ "network-online.target" ]; 255 after = [ "network-online.target" ]; 256 257 serviceConfig = { 258 Type = "oneshot"; 259 ExecStart = "${cfg.package}/bin/freshclam"; 260 SuccessExitStatus = "1"; # if databases are up to date 261 StateDirectory = "clamav"; 262 User = clamavUser; 263 Group = clamavGroup; 264 PrivateTmp = "yes"; 265 PrivateDevices = "yes"; 266 Slice = "system-clamav.slice"; 267 }; 268 }; 269 270 systemd.services.clamav-fangfrisch-init = lib.mkIf cfg.fangfrisch.enable { 271 wantedBy = [ "multi-user.target" ]; 272 # if the sqlite file can be found assume the database has already been initialised 273 script = '' 274 db_url="${cfg.fangfrisch.settings.DEFAULT.db_url}" 275 db_path="''${db_url#sqlite:///}" 276 277 if [ ! -f "$db_path" ]; then 278 ${pkgs.fangfrisch}/bin/fangfrisch --conf ${fangfrischConfigFile} initdb 279 fi 280 ''; 281 serviceConfig = { 282 Type = "oneshot"; 283 StateDirectory = "clamav"; 284 User = clamavUser; 285 Group = clamavGroup; 286 PrivateTmp = "yes"; 287 PrivateDevices = "yes"; 288 Slice = "system-clamav.slice"; 289 }; 290 }; 291 292 systemd.timers.clamav-fangfrisch = lib.mkIf cfg.fangfrisch.enable { 293 description = "Timer for ClamAV virus database updater (fangfrisch)"; 294 wantedBy = [ "timers.target" ]; 295 timerConfig = { 296 OnCalendar = cfg.fangfrisch.interval; 297 Unit = "clamav-fangfrisch.service"; 298 }; 299 }; 300 301 systemd.services.clamav-fangfrisch = lib.mkIf cfg.fangfrisch.enable { 302 description = "ClamAV virus database updater (fangfrisch)"; 303 restartTriggers = [ fangfrischConfigFile ]; 304 requires = [ "network-online.target" ]; 305 after = [ 306 "network-online.target" 307 "clamav-fangfrisch-init.service" 308 ]; 309 310 serviceConfig = { 311 Type = "oneshot"; 312 ExecStart = "${pkgs.fangfrisch}/bin/fangfrisch --conf ${fangfrischConfigFile} refresh"; 313 StateDirectory = "clamav"; 314 User = clamavUser; 315 Group = clamavGroup; 316 PrivateTmp = "yes"; 317 PrivateDevices = "yes"; 318 Slice = "system-clamav.slice"; 319 }; 320 }; 321 322 systemd.timers.clamdscan = lib.mkIf cfg.scanner.enable { 323 description = "Timer for ClamAV virus scanner"; 324 wantedBy = [ "timers.target" ]; 325 timerConfig = { 326 OnCalendar = cfg.scanner.interval; 327 Unit = "clamdscan.service"; 328 }; 329 }; 330 331 systemd.services.clamdscan = lib.mkIf cfg.scanner.enable { 332 description = "ClamAV virus scanner"; 333 documentation = [ "man:clamdscan(1)" ]; 334 after = lib.optionals cfg.updater.enable [ "clamav-freshclam.service" ]; 335 wants = lib.optionals cfg.updater.enable [ "clamav-freshclam.service" ]; 336 337 serviceConfig = { 338 Type = "oneshot"; 339 ExecStart = "${cfg.package}/bin/clamdscan --multiscan --fdpass --infected --allmatch ${lib.concatStringsSep " " cfg.scanner.scanDirectories}"; 340 Slice = "system-clamav.slice"; 341 }; 342 }; 343 }; 344}