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