parent
dc696e2800
commit
63bb133373
@ -0,0 +1,246 @@ |
||||
{ config, lib, pkgs, services, ... }: |
||||
with lib; |
||||
|
||||
let |
||||
cfg = config.services.journalwatch; |
||||
user = "journalwatch"; |
||||
dataDir = "/var/lib/${user}"; |
||||
|
||||
journalwatchConfig = pkgs.writeText "config" ('' |
||||
# (File Generated by NixOS journalwatch module.) |
||||
[DEFAULT] |
||||
mail_binary = ${cfg.mailBinary} |
||||
priority = ${toString cfg.priority} |
||||
mail_from = ${cfg.mailFrom} |
||||
'' |
||||
+ optionalString (cfg.mailTo != null) '' |
||||
mail_to = ${cfg.mailTo} |
||||
'' |
||||
+ cfg.extraConfig); |
||||
|
||||
journalwatchPatterns = pkgs.writeText "patterns" '' |
||||
# (File Generated by NixOS journalwatch module.) |
||||
|
||||
${mkPatterns cfg.filterBlocks} |
||||
''; |
||||
|
||||
# empty line at the end needed to to separate the blocks |
||||
mkPatterns = filterBlocks: concatStringsSep "\n" (map (block: '' |
||||
${block.match} |
||||
${block.filters} |
||||
|
||||
'') filterBlocks); |
||||
|
||||
|
||||
in { |
||||
options = { |
||||
services.journalwatch = { |
||||
enable = mkOption { |
||||
type = types.bool; |
||||
default = false; |
||||
description = '' |
||||
If enabled, periodically check the journal with journalwatch and report the results by mail. |
||||
''; |
||||
}; |
||||
|
||||
priority = mkOption { |
||||
type = types.int; |
||||
default = 6; |
||||
description = '' |
||||
Lowest priority of message to be considered. |
||||
A value between 7 ("debug"), and 0 ("emerg"). Defaults to 6 ("info"). |
||||
If you don't care about anything with "info" priority, you can reduce |
||||
this to e.g. 5 ("notice") to considerably reduce the amount of |
||||
messages without needing many <option>filterBlocks</option>. |
||||
''; |
||||
}; |
||||
|
||||
# HACK: this is a workaround for journalwatch's usage of socket.getfqdn() which always returns localhost if |
||||
# there's an alias for the localhost on a separate line in /etc/hosts, or take for ages if it's not present and |
||||
# then return something right-ish in the direction of /etc/hostname. Just bypass it completely. |
||||
mailFrom = mkOption { |
||||
type = types.str; |
||||
default = "journalwatch@${config.networking.hostName}"; |
||||
description = '' |
||||
Mail address to send journalwatch reports from. |
||||
''; |
||||
}; |
||||
|
||||
mailTo = mkOption { |
||||
type = types.nullOr types.str; |
||||
default = null; |
||||
description = '' |
||||
Mail address to send journalwatch reports to. |
||||
''; |
||||
}; |
||||
|
||||
mailBinary = mkOption { |
||||
type = types.path; |
||||
default = "/run/wrappers/bin/sendmail"; |
||||
description = '' |
||||
Sendmail-compatible binary to be used to send the messages. |
||||
''; |
||||
}; |
||||
|
||||
extraConfig = mkOption { |
||||
type = types.str; |
||||
default = ""; |
||||
description = '' |
||||
Extra lines to be added verbatim to the journalwatch/config configuration file. |
||||
You can add any commandline argument to the config, without the '--'. |
||||
See <literal>journalwatch --help</literal> for all arguments and their description. |
||||
''; |
||||
}; |
||||
|
||||
filterBlocks = mkOption { |
||||
type = types.listOf (types.submodule { |
||||
options = { |
||||
match = mkOption { |
||||
type = types.str; |
||||
example = "SYSLOG_IDENTIFIER = systemd"; |
||||
description = '' |
||||
Syntax: <literal>field = value</literal> |
||||
Specifies the log entry <literal>field</literal> this block should apply to. |
||||
If the <literal>field</literal> of a message matches this <literal>value</literal>, |
||||
this patternBlock's <option>filters</option> are applied. |
||||
If <literal>value</literal> starts and ends with a slash, it is interpreted as |
||||
an extended python regular expression, if not, it's an exact match. |
||||
The journal fields are explained in systemd.journal-fields(7). |
||||
''; |
||||
}; |
||||
|
||||
filters = mkOption { |
||||
type = types.str; |
||||
example = '' |
||||
(Stopped|Stopping|Starting|Started) .* |
||||
(Reached target|Stopped target) .* |
||||
''; |
||||
description = '' |
||||
The filters to apply on all messages which satisfy <option>match</option>. |
||||
Any of those messages that match any specified filter will be removed from journalwatch's output. |
||||
Each filter is an extended Python regular expression. |
||||
You can specify multiple filters and separate them by newlines. |
||||
Lines starting with '#' are comments. Inline-comments are not permitted. |
||||
''; |
||||
}; |
||||
}; |
||||
}); |
||||
|
||||
example = [ |
||||
# examples taken from upstream |
||||
{ |
||||
match = "_SYSTEMD_UNIT = systemd-logind.service"; |
||||
filters = '' |
||||
New session [a-z]?\d+ of user \w+\. |
||||
Removed session [a-z]?\d+\. |
||||
''; |
||||
} |
||||
|
||||
{ |
||||
match = "SYSLOG_IDENTIFIER = /(CROND|crond)/"; |
||||
filters = '' |
||||
pam_unix\(crond:session\): session (opened|closed) for user \w+ |
||||
\(\w+\) CMD .* |
||||
''; |
||||
} |
||||
]; |
||||
|
||||
# another example from upstream. |
||||
# very useful on priority = 6, and required as journalwatch throws an error when no pattern is defined at all. |
||||
default = [ |
||||
{ |
||||
match = "SYSLOG_IDENTIFIER = systemd"; |
||||
filters = '' |
||||
(Stopped|Stopping|Starting|Started) .* |
||||
(Created slice|Removed slice) user-\d*\.slice\. |
||||
Received SIGRTMIN\+24 from PID .* |
||||
(Reached target|Stopped target) .* |
||||
Startup finished in \d*ms\. |
||||
''; |
||||
} |
||||
]; |
||||
|
||||
|
||||
description = '' |
||||
filterBlocks can be defined to blacklist journal messages which are not errors. |
||||
Each block matches on a log entry field, and the filters in that block then are matched |
||||
against all messages with a matching log entry field. |
||||
|
||||
All messages whose PRIORITY is at least 6 (INFO) are processed by journalwatch. |
||||
If you don't specify any filterBlocks, PRIORITY is reduced to 5 (NOTICE) by default. |
||||
|
||||
All regular expressions are extended Python regular expressions, for details |
||||
see: http://doc.pyschools.com/html/regex.html |
||||
''; |
||||
}; |
||||
|
||||
interval = mkOption { |
||||
type = types.str; |
||||
default = "hourly"; |
||||
description = '' |
||||
How often to run journalwatch. |
||||
|
||||
The format is described in systemd.time(7). |
||||
''; |
||||
}; |
||||
accuracy = mkOption { |
||||
type = types.str; |
||||
default = "10min"; |
||||
description = '' |
||||
The time window around the interval in which the journalwatch run will be scheduled. |
||||
|
||||
The format is described in systemd.time(7). |
||||
''; |
||||
}; |
||||
}; |
||||
}; |
||||
|
||||
config = mkIf cfg.enable { |
||||
|
||||
users.extraUsers.${user} = { |
||||
isSystemUser = true; |
||||
createHome = true; |
||||
home = dataDir; |
||||
# for journal access |
||||
group = "systemd-journal"; |
||||
}; |
||||
|
||||
systemd.services.journalwatch = { |
||||
environment = { |
||||
XDG_DATA_HOME = "${dataDir}/share"; |
||||
XDG_CONFIG_HOME = "${dataDir}/config"; |
||||
}; |
||||
serviceConfig = { |
||||
User = user; |
||||
Type = "oneshot"; |
||||
PermissionsStartOnly = true; |
||||
ExecStart = "${pkgs.python3Packages.journalwatch}/bin/journalwatch mail"; |
||||
# lowest CPU and IO priority, but both still in best-effort class to prevent starvation |
||||
Nice=19; |
||||
IOSchedulingPriority=7; |
||||
}; |
||||
preStart = '' |
||||
chown -R ${user}:systemd-journal ${dataDir} |
||||
chmod -R u+rwX,go-w ${dataDir} |
||||
mkdir -p ${dataDir}/config/journalwatch |
||||
ln -sf ${journalwatchConfig} ${dataDir}/config/journalwatch/config |
||||
ln -sf ${journalwatchPatterns} ${dataDir}/config/journalwatch/patterns |
||||
''; |
||||
}; |
||||
|
||||
systemd.timers.journalwatch = { |
||||
description = "Periodic journalwatch run"; |
||||
wantedBy = [ "timers.target" ]; |
||||
timerConfig = { |
||||
OnCalendar = cfg.interval; |
||||
AccuracySec = cfg.accuracy; |
||||
Persistent = true; |
||||
}; |
||||
}; |
||||
|
||||
}; |
||||
|
||||
meta = { |
||||
maintainers = with stdenv.lib.maintainers; [ florianjacob ]; |
||||
}; |
||||
} |
@ -0,0 +1,43 @@ |
||||
{ stdenv, buildPythonPackage, fetchurl, fetchgit, pythonOlder, systemd, pytest }: |
||||
|
||||
buildPythonPackage rec { |
||||
pname = "journalwatch"; |
||||
name = "${pname}-${version}"; |
||||
version = "1.1.0"; |
||||
disabled = pythonOlder "3.3"; |
||||
|
||||
|
||||
src = fetchurl { |
||||
url = "https://github.com/The-Compiler/${pname}/archive/v${version}.tar.gz"; |
||||
sha512 = "3hvbgx95hjfivz9iv0hbhj720wvm32z86vj4a60lji2zdfpbqgr2b428lvg2cpvf71l2xn6ca5v0hzyz57qylgwqzgfrx7hqhl5g38s"; |
||||
}; |
||||
|
||||
# can be removed post 1.1.0 |
||||
postPatch = '' |
||||
substituteInPlace test_journalwatch.py \ |
||||
--replace "U Thu Jan 1 00:00:00 1970 prio foo [1337]" "U Thu Jan 1 00:00:00 1970 pprio foo [1337]" |
||||
''; |
||||
|
||||
|
||||
doCheck = true; |
||||
|
||||
checkPhase = '' |
||||
pytest test_journalwatch.py |
||||
''; |
||||
|
||||
buildInputs = [ |
||||
pytest |
||||
]; |
||||
|
||||
propagatedBuildInputs = [ |
||||
systemd |
||||
]; |
||||
|
||||
|
||||
meta = with stdenv.lib; { |
||||
description = "journalwatch is a tool to find error messages in the systemd journal."; |
||||
homepage = "https://github.com/The-Compiler/journalwatch"; |
||||
license = licenses.gpl3Plus; |
||||
maintainers = with maintainers; [ florianjacob ]; |
||||
}; |
||||
} |
Loading…
Reference in new issue