1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.logcheck;
7
8 defaultRules = pkgs.runCommand "logcheck-default-rules" {} ''
9 cp -prd ${pkgs.logcheck}/etc/logcheck $out
10 chmod u+w $out
11 rm $out/logcheck.*
12 '';
13
14 rulesDir = pkgs.symlinkJoin
15 { name = "logcheck-rules-dir";
16 paths = ([ defaultRules ] ++ cfg.extraRulesDirs);
17 };
18
19 configFile = pkgs.writeText "logcheck.conf" cfg.config;
20
21 logFiles = pkgs.writeText "logcheck.logfiles" cfg.files;
22
23 flags = "-r ${rulesDir} -c ${configFile} -L ${logFiles} -${levelFlag} -m ${cfg.mailTo}";
24
25 levelFlag = getAttrFromPath [cfg.level]
26 { "paranoid" = "p";
27 "server" = "s";
28 "workstation" = "w";
29 };
30
31 cronJob = ''
32 @reboot logcheck env PATH=/run/wrappers/bin:$PATH nice -n10 ${pkgs.logcheck}/sbin/logcheck -R ${flags}
33 2 ${cfg.timeOfDay} * * * logcheck env PATH=/run/wrappers/bin:$PATH nice -n10 ${pkgs.logcheck}/sbin/logcheck ${flags}
34 '';
35
36 writeIgnoreRule = name: {level, regex, ...}:
37 pkgs.writeTextFile
38 { inherit name;
39 destination = "/ignore.d.${level}/${name}";
40 text = ''
41 ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ ${regex}
42 '';
43 };
44
45 writeIgnoreCronRule = name: {level, user, regex, cmdline, ...}:
46 let escapeRegex = escape (stringToCharacters "\\[]{}()^$?*+|.");
47 cmdline_ = builtins.unsafeDiscardStringContext cmdline;
48 re = if regex != "" then regex else if cmdline_ == "" then ".*" else escapeRegex cmdline_;
49 in writeIgnoreRule "cron-${name}" {
50 inherit level;
51 regex = ''
52 (/usr/bin/)?cron\[[0-9]+\]: \(${user}\) CMD \(${re}\)$
53 '';
54 };
55
56 levelOption = mkOption {
57 default = "server";
58 type = types.enum [ "workstation" "server" "paranoid" ];
59 description = ''
60 Set the logcheck level.
61 '';
62 };
63
64 ignoreOptions = {
65 options = {
66 level = levelOption;
67
68 regex = mkOption {
69 default = "";
70 type = types.str;
71 description = ''
72 Regex specifying which log lines to ignore.
73 '';
74 };
75 };
76 };
77
78 ignoreCronOptions = {
79 options = {
80 user = mkOption {
81 default = "root";
82 type = types.str;
83 description = ''
84 User that runs the cronjob.
85 '';
86 };
87
88 cmdline = mkOption {
89 default = "";
90 type = types.str;
91 description = ''
92 Command line for the cron job. Will be turned into a regex for the logcheck ignore rule.
93 '';
94 };
95
96 timeArgs = mkOption {
97 default = null;
98 type = types.nullOr (types.str);
99 example = "02 06 * * *";
100 description = ''
101 "min hr dom mon dow" crontab time args, to auto-create a cronjob too.
102 Leave at null to not do this and just add a logcheck ignore rule.
103 '';
104 };
105 };
106 };
107
108in
109{
110 options = {
111 services.logcheck = {
112 enable = mkOption {
113 default = false;
114 type = types.bool;
115 description = ''
116 Enable the logcheck cron job.
117 '';
118 };
119
120 user = mkOption {
121 default = "logcheck";
122 type = types.str;
123 description = ''
124 Username for the logcheck user.
125 '';
126 };
127
128 timeOfDay = mkOption {
129 default = "*";
130 example = "6";
131 type = types.str;
132 description = ''
133 Time of day to run logcheck. A logcheck will be scheduled at xx:02 each day.
134 Leave default (*) to run every hour. Of course when nothing special was logged,
135 logcheck will be silent.
136 '';
137 };
138
139 mailTo = mkOption {
140 default = "root";
141 example = "you@domain.com";
142 type = types.str;
143 description = ''
144 Email address to send reports to.
145 '';
146 };
147
148 level = mkOption {
149 default = "server";
150 type = types.str;
151 description = ''
152 Set the logcheck level. Either "workstation", "server", or "paranoid".
153 '';
154 };
155
156 config = mkOption {
157 default = "FQDN=1";
158 type = types.string;
159 description = ''
160 Config options that you would like in logcheck.conf.
161 '';
162 };
163
164 files = mkOption {
165 default = [ "/var/log/messages" ];
166 type = types.listOf types.path;
167 example = [ "/var/log/messages" "/var/log/mail" ];
168 description = ''
169 Which log files to check.
170 '';
171 };
172
173 extraRulesDirs = mkOption {
174 default = [];
175 example = "/etc/logcheck";
176 type = types.listOf types.path;
177 description = ''
178 Directories with extra rules.
179 '';
180 };
181
182 ignore = mkOption {
183 default = {};
184 description = ''
185 This option defines extra ignore rules.
186 '';
187 type = with types; attrsOf (submodule ignoreOptions);
188 };
189
190 ignoreCron = mkOption {
191 default = {};
192 description = ''
193 This option defines extra ignore rules for cronjobs.
194 '';
195 type = with types; attrsOf (submodule ignoreCronOptions);
196 };
197
198 extraGroups = mkOption {
199 default = [];
200 type = types.listOf types.str;
201 example = [ "postdrop" "mongodb" ];
202 description = ''
203 Extra groups for the logcheck user, for example to be able to use sendmail,
204 or to access certain log files.
205 '';
206 };
207
208 };
209 };
210
211 config = mkIf cfg.enable {
212 services.logcheck.extraRulesDirs =
213 mapAttrsToList writeIgnoreRule cfg.ignore
214 ++ mapAttrsToList writeIgnoreCronRule cfg.ignoreCron;
215
216 users.extraUsers = optionalAttrs (cfg.user == "logcheck") (singleton
217 { name = "logcheck";
218 uid = config.ids.uids.logcheck;
219 shell = "/bin/sh";
220 description = "Logcheck user account";
221 extraGroups = cfg.extraGroups;
222 });
223
224 system.activationScripts.logcheck = ''
225 mkdir -m 700 -p /var/{lib,lock}/logcheck
226 chown ${cfg.user} /var/{lib,lock}/logcheck
227 '';
228
229 services.cron.systemCronJobs =
230 let withTime = name: {timeArgs, ...}: ! (builtins.isNull timeArgs);
231 mkCron = name: {user, cmdline, timeArgs, ...}: ''
232 ${timeArgs} ${user} ${cmdline}
233 '';
234 in mapAttrsToList mkCron (filterAttrs withTime cfg.ignoreCron)
235 ++ [ cronJob ];
236 };
237}