1 { config, lib, pkgs, utils, ... }:
3 cfg = config.services.logrotate;
6 if builtins.elem n [ "files" "priority" "enable" "global" ] || v == null then null
7 else if builtins.elem n [ "frequency" ] then "${v}\n"
8 else if builtins.elem n [ "firstaction" "lastaction" "prerotate" "postrotate" "preremove" ]
9 then "${n}\n ${v}\n endscript\n"
10 else if lib.isInt v then "${n} ${toString v}\n"
11 else if v == true then "${n}\n"
12 else if v == false then "no${n}\n"
14 generateSection = indent: settings: lib.concatStringsSep (lib.fixedWidthString indent " " "") (
15 lib.filter (x: x != null) (lib.mapAttrsToList generateLine settings)
18 # generateSection includes a final newline hence weird closing brace
20 if settings.global or false then generateSection 0 settings
22 ${lib.concatMapStringsSep "\n" (files: ''"${files}"'') (lib.toList settings.files)} {
23 ${generateSection 2 settings}}
26 settings = lib.sortProperties (lib.attrValues (lib.filterAttrs (_: settings: settings.enable) (
27 lib.foldAttrs lib.recursiveUpdate { } [
38 { header = { global = true; priority = 100; }; }
41 configFile = pkgs.writeTextFile {
42 name = "logrotate.conf";
43 text = lib.concatStringsSep "\n" (
46 checkPhase = lib.optionalString cfg.checkConfig ''
47 # logrotate --debug also checks that users specified in config
48 # file exist, but we only have sandboxed users here so brown these
49 # out. according to man page that means su, create and createolddir.
50 # files required to exist also won't be present, so missingok is forced.
51 user=$(${pkgs.buildPackages.coreutils}/bin/id -un)
52 group=$(${pkgs.buildPackages.coreutils}/bin/id -gn)
53 sed -e "s/\bsu\s.*/su $user $group/" \
54 -e "s/\b\(create\s\+[0-9]*\s*\|createolddir\s\+[0-9]*\s\+\).*/\1$user $group/" \
55 -e "1imissingok" -e "s/\bnomissingok\b//" \
57 # Since this makes for very verbose builds only show real error.
58 # There is no way to control log level, but logrotate hardcodes
59 # 'error:' at common log level, so we can use grep, taking care
62 if ! ${pkgs.buildPackages.logrotate}/sbin/logrotate -s logrotate.status \
63 --debug logrotate.conf 2>&1 \
64 | ( ! grep "error:" ) > logrotate-error; then
65 echo "Logrotate configuration check failed."
66 echo "The failing configuration (after adjustments to pass tests in sandbox) was:"
67 printf "%s\n" "-------"
69 printf "%s\n" "-------"
70 echo "The error reported by logrotate was as follow:"
71 printf "%s\n" "-------"
73 printf "%s\n" "-------"
74 echo "You can disable this check with services.logrotate.checkConfig = false,"
75 echo "but if you think it should work please report this failure along with"
76 echo "the config file being tested!"
83 lib.optionalString (lib.foldr (n: a: a || (n.mail or false) != false) false (lib.attrValues cfg.settings))
84 "--mail=${pkgs.mailutils}/bin/mail";
88 (lib.mkRemovedOptionModule [ "services" "logrotate" "config" ] "Modify services.logrotate.settings.header instead")
89 (lib.mkRemovedOptionModule [ "services" "logrotate" "extraConfig" ] "Modify services.logrotate.settings.header instead")
90 (lib.mkRemovedOptionModule [ "services" "logrotate" "paths" ] "Add attributes to services.logrotate.settings instead")
94 services.logrotate = {
95 enable = lib.mkEnableOption "the logrotate systemd service" // {
96 default = lib.foldr (n: a: a || n.enable) false (lib.attrValues cfg.settings);
97 defaultText = lib.literalExpression "cfg.settings != {}";
100 allowNetworking = lib.mkEnableOption "network access for logrotate";
102 settings = lib.mkOption {
105 logrotate freeform settings: each attribute here will define its own section,
106 ordered by {option}`services.logrotate.settings.<name>.priority`,
107 which can either define files to rotate with their settings
108 or settings common to all further files settings.
109 All attribute names not explicitly defined as sub-options here are passed through
110 as logrotate config directives,
111 refer to <https://linux.die.net/man/8/logrotate> for details.
113 example = lib.literalExpression ''
119 # example custom files
120 "/var/log/mylog.log" = {
126 "/var/log/first*.log"
127 "/var/log/second.log"
130 # specify custom order of sections
131 "/var/log/myservice/*.log" = {
132 # ensure lower priority
135 systemctl reload myservice
140 type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: {
141 freeformType = with lib.types; attrsOf (nullOr (oneOf [ int bool str ]));
144 enable = lib.mkEnableOption "setting individual kill switch" // {
148 global = lib.mkOption {
149 type = lib.types.bool;
152 Whether this setting is a global option or not: set to have these
153 settings apply to all files settings with a higher priority.
156 files = lib.mkOption {
157 type = with lib.types; either str (listOf str);
160 The attrset name if not specified
163 Single or list of files for which rules are defined.
164 The files are quoted with double-quotes in logrotate configuration,
165 so globs and spaces are supported.
166 Note this setting is ignored if globals is true.
170 frequency = lib.mkOption {
171 type = lib.types.nullOr lib.types.str;
174 How often to rotate the logs. Defaults to previously set global setting,
175 which itself defaults to weekly.
179 priority = lib.mkOption {
180 type = lib.types.int;
183 Order of this logrotate block in relation to the others. The semantics are
184 the same as with `lib.mkOrder`. Smaller values are inserted first.
192 configFile = lib.mkOption {
193 type = lib.types.path;
194 default = configFile;
196 A configuration file automatically generated by NixOS.
199 Override the configuration file used by logrotate. By default,
200 NixOS generates one automatically from [](#opt-services.logrotate.settings).
202 example = lib.literalExpression ''
203 pkgs.writeText "logrotate.conf" '''
213 checkConfig = lib.mkOption {
214 type = lib.types.bool;
217 Whether the config should be checked at build time.
219 Some options are not checkable at build time because of the build sandbox:
220 for example, the test does not know about existing files and system users are
222 These limitations mean we must adjust the file for tests (missingok is forced
223 and users are replaced by dummy users), so tests are complemented by a
224 logrotate-checkconf service that is enabled by default.
225 This extra check can be disabled by disabling it at the systemd level with the
226 {option}`systemd.services.logrotate-checkconf.enable` option.
228 Conversely there are still things that might make this check fail incorrectly
229 (e.g. a file path where we don't have access to intermediate directories):
230 in this case you can disable the failing check with this option.
234 extraArgs = lib.mkOption {
235 type = lib.types.listOf lib.types.str;
237 description = "Additional command line arguments to pass on logrotate invocation";
242 config = lib.mkIf cfg.enable {
243 systemd.services.logrotate = {
244 description = "Logrotate Service";
253 ExecStart = "${lib.getExe pkgs.logrotate} ${utils.escapeSystemdExecArgs cfg.extraArgs} ${mailOption} ${cfg.configFile}";
257 IOSchedulingClass = "best-effort";
258 IOSchedulingPriority = 7;
261 CapabilityBoundingSet = [
265 DevicePolicy = "closed";
266 LockPersonality = true;
267 MemoryDenyWriteExecute = true;
268 NoNewPrivileges = true;
269 PrivateDevices = true;
273 ProtectControlGroups = true;
275 ProtectHostname = true;
276 ProtectKernelLogs = true;
277 ProtectKernelModules = true;
278 ProtectKernelTunables = true;
279 ProtectProc = "invisible";
280 ProtectSystem = "full";
281 RestrictNamespaces = true;
282 RestrictRealtime = true;
283 RestrictSUIDSGID = true;
284 SystemCallArchitectures = "native";
287 "~@privileged @resources"
291 } // lib.optionalAttrs (!cfg.allowNetworking) {
292 PrivateNetwork = true;
293 RestrictAddressFamilies = "none";
296 systemd.services.logrotate-checkconf = {
297 description = "Logrotate configuration check";
298 wantedBy = [ "multi-user.target" ];
301 RemainAfterExit = true;
302 ExecStart = "${pkgs.logrotate}/sbin/logrotate ${utils.escapeSystemdExecArgs cfg.extraArgs} --debug ${cfg.configFile}";