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