{ config, lib, pkgs, ... }: let cfg = config.services.logcheck; defaultRules = pkgs.runCommand "logcheck-default-rules" { preferLocalBuild = true; } '' cp -prd ${pkgs.logcheck}/etc/logcheck $out chmod u+w $out rm -r $out/logcheck.* ''; rulesDir = pkgs.symlinkJoin { name = "logcheck-rules-dir"; paths = ([ defaultRules ] ++ cfg.extraRulesDirs); }; configFile = pkgs.writeText "logcheck.conf" cfg.config; logFiles = pkgs.writeText "logcheck.logfiles" cfg.files; flags = "-r ${rulesDir} -c ${configFile} -L ${logFiles} -${levelFlag} -m ${cfg.mailTo}"; levelFlag = lib.getAttrFromPath [cfg.level] { paranoid = "p"; server = "s"; workstation = "w"; }; cronJob = '' @reboot logcheck env PATH=/run/wrappers/bin:$PATH nice -n10 ${pkgs.logcheck}/sbin/logcheck -R ${flags} 2 ${cfg.timeOfDay} * * * logcheck env PATH=/run/wrappers/bin:$PATH nice -n10 ${pkgs.logcheck}/sbin/logcheck ${flags} ''; writeIgnoreRule = name: {level, regex, ...}: pkgs.writeTextFile { inherit name; destination = "/ignore.d.${level}/${name}"; text = '' ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ ${regex} ''; }; writeIgnoreCronRule = name: {level, user, regex, cmdline, ...}: let escapeRegex = lib.escape (lib.stringToCharacters "\\[]{}()^$?*+|."); cmdline_ = builtins.unsafeDiscardStringContext cmdline; re = if regex != "" then regex else if cmdline_ == "" then ".*" else escapeRegex cmdline_; in writeIgnoreRule "cron-${name}" { inherit level; regex = '' (/usr/bin/)?cron\[[0-9]+\]: \(${user}\) CMD \(${re}\)$ ''; }; levelOption = lib.mkOption { default = "server"; type = lib.types.enum [ "workstation" "server" "paranoid" ]; description = '' Set the logcheck level. ''; }; ignoreOptions = { options = { level = levelOption; regex = lib.mkOption { default = ""; type = lib.types.str; description = '' Regex specifying which log lines to ignore. ''; }; }; }; ignoreCronOptions = { options = { user = lib.mkOption { default = "root"; type = lib.types.str; description = '' User that runs the cronjob. ''; }; cmdline = lib.mkOption { default = ""; type = lib.types.str; description = '' Command line for the cron job. Will be turned into a regex for the logcheck ignore rule. ''; }; timeArgs = lib.mkOption { default = null; type = lib.types.nullOr (lib.types.str); example = "02 06 * * *"; description = '' "min hr dom mon dow" crontab time args, to auto-create a cronjob too. Leave at null to not do this and just add a logcheck ignore rule. ''; }; }; }; in { options = { services.logcheck = { enable = lib.mkEnableOption "logcheck cron job, to mail anomalies in the system logfiles to the administrator"; user = lib.mkOption { default = "logcheck"; type = lib.types.str; description = '' Username for the logcheck user. ''; }; timeOfDay = lib.mkOption { default = "*"; example = "6"; type = lib.types.str; description = '' Time of day to run logcheck. A logcheck will be scheduled at xx:02 each day. Leave default (*) to run every hour. Of course when nothing special was logged, logcheck will be silent. ''; }; mailTo = lib.mkOption { default = "root"; example = "you@domain.com"; type = lib.types.str; description = '' Email address to send reports to. ''; }; level = lib.mkOption { default = "server"; type = lib.types.str; description = '' Set the logcheck level. Either "workstation", "server", or "paranoid". ''; }; config = lib.mkOption { default = "FQDN=1"; type = lib.types.lines; description = '' Config options that you would like in logcheck.conf. ''; }; files = lib.mkOption { default = [ "/var/log/messages" ]; type = lib.types.listOf lib.types.path; example = [ "/var/log/messages" "/var/log/mail" ]; description = '' Which log files to check. ''; }; extraRulesDirs = lib.mkOption { default = []; example = [ "/etc/logcheck" ]; type = lib.types.listOf lib.types.path; description = '' Directories with extra rules. ''; }; ignore = lib.mkOption { default = {}; description = '' This option defines extra ignore rules. ''; type = with lib.types; attrsOf (submodule ignoreOptions); }; ignoreCron = lib.mkOption { default = {}; description = '' This option defines extra ignore rules for cronjobs. ''; type = with lib.types; attrsOf (submodule ignoreCronOptions); }; extraGroups = lib.mkOption { default = []; type = lib.types.listOf lib.types.str; example = [ "postdrop" "mongodb" ]; description = '' Extra groups for the logcheck user, for example to be able to use sendmail, or to access certain log files. ''; }; }; }; config = lib.mkIf cfg.enable { services.logcheck.extraRulesDirs = lib.mapAttrsToList writeIgnoreRule cfg.ignore ++ lib.mapAttrsToList writeIgnoreCronRule cfg.ignoreCron; users.users = lib.optionalAttrs (cfg.user == "logcheck") { logcheck = { group = "logcheck"; isSystemUser = true; shell = "/bin/sh"; description = "Logcheck user account"; extraGroups = cfg.extraGroups; }; }; users.groups = lib.optionalAttrs (cfg.user == "logcheck") { logcheck = {}; }; systemd.tmpfiles.settings.logcheck = { "/var/lib/logcheck".d = { mode = "700"; inherit (cfg) user; }; "/var/lock/logcheck".d = { mode = "700"; inherit (cfg) user; }; }; services.cron.systemCronJobs = let withTime = name: {timeArgs, ...}: timeArgs != null; mkCron = name: {user, cmdline, timeArgs, ...}: '' ${timeArgs} ${user} ${cmdline} ''; in lib.mapAttrsToList mkCron (lib.filterAttrs withTime cfg.ignoreCron) ++ [ cronJob ]; }; }