···
1
+
{ config, lib, pkgs, services, ... }:
5
+
cfg = config.services.journalwatch;
6
+
user = "journalwatch";
7
+
dataDir = "/var/lib/${user}";
9
+
journalwatchConfig = pkgs.writeText "config" (''
10
+
# (File Generated by NixOS journalwatch module.)
12
+
mail_binary = ${cfg.mailBinary}
13
+
priority = ${toString cfg.priority}
14
+
mail_from = ${cfg.mailFrom}
16
+
+ optionalString (cfg.mailTo != null) ''
17
+
mail_to = ${cfg.mailTo}
21
+
journalwatchPatterns = pkgs.writeText "patterns" ''
22
+
# (File Generated by NixOS journalwatch module.)
24
+
${mkPatterns cfg.filterBlocks}
27
+
# empty line at the end needed to to separate the blocks
28
+
mkPatterns = filterBlocks: concatStringsSep "\n" (map (block: ''
37
+
services.journalwatch = {
42
+
If enabled, periodically check the journal with journalwatch and report the results by mail.
46
+
priority = mkOption {
50
+
Lowest priority of message to be considered.
51
+
A value between 7 ("debug"), and 0 ("emerg"). Defaults to 6 ("info").
52
+
If you don't care about anything with "info" priority, you can reduce
53
+
this to e.g. 5 ("notice") to considerably reduce the amount of
54
+
messages without needing many <option>filterBlocks</option>.
58
+
# HACK: this is a workaround for journalwatch's usage of socket.getfqdn() which always returns localhost if
59
+
# there's an alias for the localhost on a separate line in /etc/hosts, or take for ages if it's not present and
60
+
# then return something right-ish in the direction of /etc/hostname. Just bypass it completely.
61
+
mailFrom = mkOption {
63
+
default = "journalwatch@${config.networking.hostName}";
65
+
Mail address to send journalwatch reports from.
70
+
type = types.nullOr types.str;
73
+
Mail address to send journalwatch reports to.
77
+
mailBinary = mkOption {
79
+
default = "/run/wrappers/bin/sendmail";
81
+
Sendmail-compatible binary to be used to send the messages.
85
+
extraConfig = mkOption {
89
+
Extra lines to be added verbatim to the journalwatch/config configuration file.
90
+
You can add any commandline argument to the config, without the '--'.
91
+
See <literal>journalwatch --help</literal> for all arguments and their description.
95
+
filterBlocks = mkOption {
96
+
type = types.listOf (types.submodule {
100
+
example = "SYSLOG_IDENTIFIER = systemd";
102
+
Syntax: <literal>field = value</literal>
103
+
Specifies the log entry <literal>field</literal> this block should apply to.
104
+
If the <literal>field</literal> of a message matches this <literal>value</literal>,
105
+
this patternBlock's <option>filters</option> are applied.
106
+
If <literal>value</literal> starts and ends with a slash, it is interpreted as
107
+
an extended python regular expression, if not, it's an exact match.
108
+
The journal fields are explained in systemd.journal-fields(7).
112
+
filters = mkOption {
115
+
(Stopped|Stopping|Starting|Started) .*
116
+
(Reached target|Stopped target) .*
119
+
The filters to apply on all messages which satisfy <option>match</option>.
120
+
Any of those messages that match any specified filter will be removed from journalwatch's output.
121
+
Each filter is an extended Python regular expression.
122
+
You can specify multiple filters and separate them by newlines.
123
+
Lines starting with '#' are comments. Inline-comments are not permitted.
130
+
# examples taken from upstream
132
+
match = "_SYSTEMD_UNIT = systemd-logind.service";
134
+
New session [a-z]?\d+ of user \w+\.
135
+
Removed session [a-z]?\d+\.
140
+
match = "SYSLOG_IDENTIFIER = /(CROND|crond)/";
142
+
pam_unix\(crond:session\): session (opened|closed) for user \w+
148
+
# another example from upstream.
149
+
# very useful on priority = 6, and required as journalwatch throws an error when no pattern is defined at all.
152
+
match = "SYSLOG_IDENTIFIER = systemd";
154
+
(Stopped|Stopping|Starting|Started) .*
155
+
(Created slice|Removed slice) user-\d*\.slice\.
156
+
Received SIGRTMIN\+24 from PID .*
157
+
(Reached target|Stopped target) .*
158
+
Startup finished in \d*ms\.
165
+
filterBlocks can be defined to blacklist journal messages which are not errors.
166
+
Each block matches on a log entry field, and the filters in that block then are matched
167
+
against all messages with a matching log entry field.
169
+
All messages whose PRIORITY is at least 6 (INFO) are processed by journalwatch.
170
+
If you don't specify any filterBlocks, PRIORITY is reduced to 5 (NOTICE) by default.
172
+
All regular expressions are extended Python regular expressions, for details
173
+
see: http://doc.pyschools.com/html/regex.html
177
+
interval = mkOption {
179
+
default = "hourly";
181
+
How often to run journalwatch.
183
+
The format is described in systemd.time(7).
186
+
accuracy = mkOption {
190
+
The time window around the interval in which the journalwatch run will be scheduled.
192
+
The format is described in systemd.time(7).
198
+
config = mkIf cfg.enable {
200
+
users.extraUsers.${user} = {
201
+
isSystemUser = true;
204
+
# for journal access
205
+
group = "systemd-journal";
208
+
systemd.services.journalwatch = {
210
+
XDG_DATA_HOME = "${dataDir}/share";
211
+
XDG_CONFIG_HOME = "${dataDir}/config";
216
+
PermissionsStartOnly = true;
217
+
ExecStart = "${pkgs.python3Packages.journalwatch}/bin/journalwatch mail";
218
+
# lowest CPU and IO priority, but both still in best-effort class to prevent starvation
220
+
IOSchedulingPriority=7;
223
+
chown -R ${user}:systemd-journal ${dataDir}
224
+
chmod -R u+rwX,go-w ${dataDir}
225
+
mkdir -p ${dataDir}/config/journalwatch
226
+
ln -sf ${journalwatchConfig} ${dataDir}/config/journalwatch/config
227
+
ln -sf ${journalwatchPatterns} ${dataDir}/config/journalwatch/patterns
231
+
systemd.timers.journalwatch = {
232
+
description = "Periodic journalwatch run";
233
+
wantedBy = [ "timers.target" ];
235
+
OnCalendar = cfg.interval;
236
+
AccuracySec = cfg.accuracy;
244
+
maintainers = with stdenv.lib.maintainers; [ florianjacob ];