summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2205.section.xml17
-rw-r--r--nixos/doc/manual/release-notes/rl-2205.section.md5
-rw-r--r--nixos/modules/services/logging/logrotate.nix192
-rw-r--r--nixos/modules/services/misc/gitlab.nix16
-rw-r--r--nixos/modules/services/networking/lxd-image-server.nix18
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix21
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix16
-rw-r--r--nixos/modules/system/boot/systemd.nix18
-rw-r--r--nixos/modules/virtualisation/azure-agent.nix14
-rw-r--r--nixos/tests/logrotate.nix80
10 files changed, 297 insertions, 100 deletions
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
index 99af050d7ba..9535d441740 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
@@ -1655,9 +1655,20 @@
       </listitem>
       <listitem>
         <para>
-          <literal>services.logrotate.enable</literal> now defaults to
-          true if any rotate path has been defined, and some paths have
-          been added by default.
+          <link linkend="opt-services.logrotate.enable">services.logrotate.enable</link>
+          now defaults to true if any rotate path has been defined, and
+          some paths have been added by default.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The logrotate module also has been updated to freeform syntax:
+          <link linkend="opt-services.logrotate.paths">services.logrotate.paths</link>
+          and
+          <link linkend="opt-services.logrotate.extraConfig">services.logrotate.extraConfig</link>
+          will work, but issue deprecation warnings and
+          <link linkend="opt-services.logrotate.settings">services.logrotate.settings</link>
+          should now be used instead.
         </para>
       </listitem>
       <listitem>
diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md
index 3e883625664..377dd1b5cae 100644
--- a/nixos/doc/manual/release-notes/rl-2205.section.md
+++ b/nixos/doc/manual/release-notes/rl-2205.section.md
@@ -578,8 +578,11 @@ In addition to numerous new and upgraded packages, this release has the followin
 - `services.mattermost.plugins` has been added to allow the declarative installation of Mattermost plugins.
   Plugins are automatically repackaged using autoPatchelf.
 
-- `services.logrotate.enable` now defaults to true if any rotate path has
+- [services.logrotate.enable](#opt-services.logrotate.enable) now defaults to true if any rotate path has
   been defined, and some paths have been added by default.
+- The logrotate module also has been updated to freeform syntax: [services.logrotate.paths](#opt-services.logrotate.paths)
+  and [services.logrotate.extraConfig](#opt-services.logrotate.extraConfig) will work, but issue deprecation
+  warnings and [services.logrotate.settings](#opt-services.logrotate.settings) should now be used instead.
 
 - The `zrepl` package has been updated from 0.4.0 to 0.5:
 
diff --git a/nixos/modules/services/logging/logrotate.nix b/nixos/modules/services/logging/logrotate.nix
index d16a5a571ba..6a9ed469fd3 100644
--- a/nixos/modules/services/logging/logrotate.nix
+++ b/nixos/modules/services/logging/logrotate.nix
@@ -5,6 +5,9 @@ with lib;
 let
   cfg = config.services.logrotate;
 
+  # deprecated legacy compat settings
+  # these options will be removed before 22.11 in the following PR:
+  # https://github.com/NixOS/nixpkgs/pull/164169
   pathOpts = { name, ... }: {
     options = {
       enable = mkOption {
@@ -86,27 +89,77 @@ let
     config.name = name;
   };
 
-  mkConf = pathOpts: ''
-    # generated by NixOS using the `services.logrotate.paths.${pathOpts.name}` attribute set
-    ${concatMapStringsSep " " (path: ''"${path}"'') (toList pathOpts.path)} {
-      ${optionalString (pathOpts.user != null || pathOpts.group != null) "su ${pathOpts.user} ${pathOpts.group}"}
-      ${pathOpts.frequency}
-      rotate ${toString pathOpts.keep}
-      ${pathOpts.extraConfig}
-    }
-  '';
-
-  paths = sortProperties (attrValues (filterAttrs (_: pathOpts: pathOpts.enable) cfg.paths));
-  configText = concatStringsSep "\n" (
-    [ "missingok" "notifempty" cfg.extraConfig ] ++ (map mkConf paths)
+  generateLine = n: v:
+    if builtins.elem n [ "files" "priority" "enable" "global" ] || v == null then null
+    else if builtins.elem n [ "extraConfig" "frequency" ] then "${v}\n"
+    else if builtins.elem n [ "firstaction" "lastaction" "prerotate" "postrotate" "preremove" ]
+         then "${n}\n    ${v}\n  endscript\n"
+    else if isInt v then "${n} ${toString v}\n"
+    else if v == true then "${n}\n"
+    else if v == false then "no${n}\n"
+    else "${n} ${v}\n";
+  generateSection = indent: settings: concatStringsSep (fixedWidthString indent " " "") (
+    filter (x: x != null) (mapAttrsToList generateLine settings)
+  );
+
+  # generateSection includes a final newline hence weird closing brace
+  mkConf = settings:
+    if settings.global or false then generateSection 0 settings
+    else ''
+      ${concatMapStringsSep "\n" (files: ''"${files}"'') (toList settings.files)} {
+        ${generateSection 2 settings}}
+    '';
+
+  # below two mapPaths are compat functions
+  mapPathOptToSetting = n: v:
+    if n == "keep" then nameValuePair "rotate" v
+    else if n == "path" then nameValuePair "files" v
+    else nameValuePair n v;
+
+  mapPathsToSettings = path: pathOpts:
+    nameValuePair path (
+      filterAttrs (n: v: ! builtins.elem n [ "user" "group" "name" ] && v != "") (
+        (mapAttrs' mapPathOptToSetting pathOpts) //
+        {
+          su =
+            if pathOpts.user != null
+            then "${pathOpts.user} ${pathOpts.group}"
+            else null;
+        }
+      )
+    );
+
+  settings = sortProperties (attrValues (filterAttrs (_: settings: settings.enable) (
+    foldAttrs recursiveUpdate { } [
+      {
+        header = {
+          enable = true;
+          missingok = true;
+          notifempty = true;
+          frequency = "weekly";
+          rotate = 4;
+        };
+        # compat section
+        extraConfig = {
+          enable = (cfg.extraConfig != "");
+          global = true;
+          extraConfig = cfg.extraConfig;
+          priority = 101;
+        };
+      }
+      (mapAttrs' mapPathsToSettings cfg.paths)
+      cfg.settings
+      { header = { global = true; priority = 100; }; }
+    ]
+  )));
+  configFile = pkgs.writeText "logrotate.conf" (
+    concatStringsSep "\n" (
+      map mkConf settings
+    )
   );
-  configFile = pkgs.writeText "logrotate.conf" configText;
 
   mailOption =
-    # add mail option to service if a mail is requested in config
-    # this ugly match will be replaced by cleaner attribute check in
-    # the near future
-    if builtins.match "(.*[[:space:]])?mail[[:space:]].*" configText != null
+    if foldr (n: a: a || n ? mail) false (attrValues cfg.settings)
     then "--mail=${pkgs.mailutils}/bin/mail"
     else "";
 in
@@ -118,8 +171,68 @@ in
   options = {
     services.logrotate = {
       enable = mkEnableOption "the logrotate systemd service" // {
-        default = foldr (n: a: a || n.enable) false (attrValues cfg.paths);
-        defaultText = literalExpression "cfg.paths != {}";
+        default = foldr (n: a: a || n.enable) false (attrValues cfg.settings);
+        defaultText = literalExpression "cfg.settings != {}";
+      };
+
+      settings = mkOption {
+        default = { };
+        description = ''
+          logrotate freeform settings: each attribute here will define its own section,
+          ordered by priority, which can either define files to rotate with their settings
+          or settings common to all further files settings.
+          Refer to <link xlink:href="https://linux.die.net/man/8/logrotate"/> for details.
+        '';
+        type = types.attrsOf (types.submodule ({ name, ... }: {
+          freeformType = with types; attrsOf (nullOr (oneOf [ int bool str ]));
+
+          options = {
+            enable = mkEnableOption "setting individual kill switch" // {
+              default = true;
+            };
+
+            global = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Whether this setting is a global option or not: set to have these
+                settings apply to all files settings with a higher priority.
+              '';
+            };
+            files = mkOption {
+              type = with types; either str (listOf str);
+              default = name;
+              defaultText = ''
+                The attrset name if not specified
+              '';
+              description = ''
+                Single or list of files for which rules are defined.
+                The files are quoted with double-quotes in logrotate configuration,
+                so globs and spaces are supported.
+                Note this setting is ignored if globals is true.
+              '';
+            };
+
+            frequency = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              description = ''
+                How often to rotate the logs. Defaults to previously set global setting,
+                which itself defauts to weekly.
+              '';
+            };
+
+            priority = mkOption {
+              type = types.int;
+              default = 1000;
+              description = ''
+                Order of this logrotate block in relation to the others. The semantics are
+                the same as with `lib.mkOrder`. Smaller values are inserted first.
+              '';
+            };
+          };
+
+        }));
       };
 
       configFile = mkOption {
@@ -130,7 +243,7 @@ in
         '';
         description = ''
           Override the configuration file used by MySQL. By default,
-          NixOS generates one automatically from <option>services.logrotate.settings</option>.
+          NixOS generates one automatically from <xref linkend="opt-services.logrotate.settings"/>.
         '';
         example = literalExpression ''
           pkgs.writeText "logrotate.conf" '''
@@ -143,6 +256,7 @@ in
         '';
       };
 
+      # deprecated legacy compat settings
       paths = mkOption {
         type = with types; attrsOf (submodule pathOpts);
         default = { };
@@ -150,6 +264,7 @@ in
           Attribute set of paths to rotate. The order each block appears in the generated configuration file
           can be controlled by the <link linkend="opt-services.logrotate.paths._name_.priority">priority</link> option
           using the same semantics as `lib.mkOrder`. Smaller values have a greater priority.
+          This setting has been deprecated in favor of <link linkend="opt-services.logrotate.settings">logrotate settings</link>.
         '';
         example = literalExpression ''
           {
@@ -178,22 +293,37 @@ in
         description = ''
           Extra contents to append to the logrotate configuration file. Refer to
           <link xlink:href="https://linux.die.net/man/8/logrotate"/> for details.
+          This setting has been deprecated in favor of
+          <link linkend="opt-services.logrotate.settings">logrotate settings</link>.
         '';
       };
     };
   };
 
   config = mkIf cfg.enable {
-    assertions = mapAttrsToList
-      (name: pathOpts:
-        {
-          assertion = (pathOpts.user != null) == (pathOpts.group != null);
-          message = ''
-            If either of `services.logrotate.paths.${name}.user` or `services.logrotate.paths.${name}.group` are specified then *both* must be specified.
-          '';
-        }
-      )
-      cfg.paths;
+    assertions =
+      mapAttrsToList
+        (name: pathOpts:
+          {
+            assertion = (pathOpts.user != null) == (pathOpts.group != null);
+            message = ''
+              If either of `services.logrotate.paths.${name}.user` or `services.logrotate.paths.${name}.group` are specified then *both* must be specified.
+            '';
+          })
+        cfg.paths;
+
+    warnings =
+      (mapAttrsToList
+        (name: pathOpts: ''
+          Using config.services.logrotate.paths.${name} is deprecated and will become unsupported in a future release.
+          Please use services.logrotate.settings instead.
+        '')
+        cfg.paths
+      ) ++
+      (optional (cfg.extraConfig != "") ''
+        Using config.services.logrotate.extraConfig is deprecated and will become unsupported in a future release.
+        Please use services.logrotate.settings with globals=true instead.
+      '');
 
     systemd.services.logrotate = {
       description = "Logrotate Service";
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index e48444f7161..488c3be7b65 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -848,10 +848,7 @@ in {
 
         extraConfig = mkOption {
           type = types.lines;
-          default = ''
-            copytruncate
-            compress
-          '';
+          default = "";
           description = ''
             Extra logrotate config options for this path. Refer to
             <link xlink:href="https://linux.die.net/man/8/logrotate"/> for details.
@@ -977,13 +974,14 @@ in {
     # Enable rotation of log files
     services.logrotate = {
       enable = cfg.logrotate.enable;
-      paths = {
+      settings = {
         gitlab = {
-          path = "${cfg.statePath}/log/*.log";
-          user = cfg.user;
-          group = cfg.group;
+          files = "${cfg.statePath}/log/*.log";
+          su = "${cfg.user} ${cfg.group}";
           frequency = cfg.logrotate.frequency;
-          keep = cfg.logrotate.keep;
+          rotate = cfg.logrotate.keep;
+          copytruncate = true;
+          compress = true;
           extraConfig = cfg.logrotate.extraConfig;
         };
       };
diff --git a/nixos/modules/services/networking/lxd-image-server.nix b/nixos/modules/services/networking/lxd-image-server.nix
index b119ba8acf6..d326626eed4 100644
--- a/nixos/modules/services/networking/lxd-image-server.nix
+++ b/nixos/modules/services/networking/lxd-image-server.nix
@@ -51,18 +51,14 @@ in
 
       environment.etc."lxd-image-server/config.toml".source = format.generate "config.toml" cfg.settings;
 
-      services.logrotate.paths.lxd-image-server = {
-        path = "/var/log/lxd-image-server/lxd-image-server.log";
+      services.logrotate.settings.lxd-image-server = {
+        files = "/var/log/lxd-image-server/lxd-image-server.log";
         frequency = "daily";
-        keep = 21;
-        extraConfig = ''
-          create 755 lxd-image-server ${cfg.group}
-          missingok
-          compress
-          delaycompress
-          copytruncate
-          notifempty
-        '';
+        rotate = 21;
+        create = "755 lxd-image-server ${cfg.group}";
+        compress = true;
+        delaycompress = true;
+        copytruncate = true;
       };
 
       systemd.tmpfiles.rules = [
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index d817ff6019a..3099705acbe 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -710,20 +710,15 @@ in
 
     services.logrotate = optionalAttrs (cfg.logFormat != "none") {
       enable = mkDefault true;
-      paths.httpd = {
-        path = "${cfg.logDir}/*.log";
-        user = cfg.user;
-        group = cfg.group;
+      settings.httpd = {
+        files = "${cfg.logDir}/*.log";
+        su = "${cfg.user} ${cfg.group}";
         frequency = "daily";
-        keep = 28;
-        extraConfig = ''
-          sharedscripts
-          compress
-          delaycompress
-          postrotate
-            systemctl reload httpd.service > /dev/null 2>/dev/null || true
-          endscript
-        '';
+        rotate = 28;
+        sharedscripts = true;
+        compress = true;
+        delaycompress = true;
+        postrotate = "systemctl reload httpd.service > /dev/null 2>/dev/null || true";
       };
     };
 
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index e046c28dd6b..1e18956c2dc 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -989,17 +989,13 @@ in
       nginx.gid = config.ids.gids.nginx;
     };
 
-    services.logrotate.paths.nginx = mapAttrs (_: mkDefault) {
-      path = "/var/log/nginx/*.log";
+    services.logrotate.settings.nginx = mapAttrs (_: mkDefault) {
+      files = "/var/log/nginx/*.log";
       frequency = "weekly";
-      keep = 26;
-      extraConfig = ''
-        compress
-        delaycompress
-        postrotate
-          [ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`
-        endscript
-      '';
+      rotate = 26;
+      compress = true;
+      delaycompress = true;
+      postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";
     };
   };
 }
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 297a80d4681..f69c5d3d5a6 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -612,22 +612,18 @@ in
 
     boot.kernelParams = optional (!cfg.enableUnifiedCgroupHierarchy) "systemd.unified_cgroup_hierarchy=0";
 
-    services.logrotate.paths = {
+    services.logrotate.settings = {
       "/var/log/btmp" = mapAttrs (_: mkDefault) {
         frequency = "monthly";
-        keep = 1;
-        extraConfig = ''
-          create 0660 root ${config.users.groups.utmp.name}
-          minsize 1M
-        '';
+        rotate = 1;
+        create = "0660 root ${config.users.groups.utmp.name}";
+        minsize = "1M";
       };
       "/var/log/wtmp" = mapAttrs (_: mkDefault) {
         frequency = "monthly";
-        keep = 1;
-        extraConfig = ''
-          create 0664 root ${config.users.groups.utmp.name}
-          minsize 1M
-        '';
+        rotate = 1;
+        create = "0664 root ${config.users.groups.utmp.name}";
+        minsize = "1M";
       };
     };
   };
diff --git a/nixos/modules/virtualisation/azure-agent.nix b/nixos/modules/virtualisation/azure-agent.nix
index bd8c7f8c1ee..e2425b44eac 100644
--- a/nixos/modules/virtualisation/azure-agent.nix
+++ b/nixos/modules/virtualisation/azure-agent.nix
@@ -146,15 +146,11 @@ in
 
     services.logrotate = {
       enable = true;
-      extraConfig = ''
-        /var/log/waagent.log {
-            compress
-            monthly
-            rotate 6
-            notifempty
-            missingok
-        }
-      '';
+      settings."/var/log/waagent.log" = {
+        compress = true;
+        frequency = "monthly";
+        rotate = 6;
+      };
     };
 
     systemd.targets.provisioned = {
diff --git a/nixos/tests/logrotate.nix b/nixos/tests/logrotate.nix
index 85923465593..3087119c339 100644
--- a/nixos/tests/logrotate.nix
+++ b/nixos/tests/logrotate.nix
@@ -1,5 +1,14 @@
 # Test logrotate service works and is enabled by default
 
+let
+  importTest = { ... }: {
+    services.logrotate.settings.import = {
+      olddir = false;
+    };
+  };
+
+in
+
 import ./make-test-python.nix ({ pkgs, ... }: rec {
   name = "logrotate";
   meta = with pkgs.lib.maintainers; {
@@ -9,10 +18,59 @@ import ./make-test-python.nix ({ pkgs, ... }: rec {
   nodes = {
     defaultMachine = { ... }: { };
     machine = { config, ... }: {
-      services.logrotate.paths = {
+      imports = [ importTest ];
+
+      services.logrotate.settings = {
+        # remove default frequency header and add another
+        header = {
+          frequency = null;
+          delaycompress = true;
+        };
+        # extra global setting... affecting nothing
+        last_line = {
+          global = true;
+          priority = 2000;
+          shred = true;
+        };
         # using mail somewhere should add --mail to logrotate invokation
         sendmail = {
-          extraConfig = "mail user@domain.tld";
+          mail = "user@domain.tld";
+        };
+        # postrotate should be suffixed by 'endscript'
+        postrotate = {
+          postrotate = "touch /dev/null";
+        };
+        # multiple paths should be aggregated
+        multipath = {
+          files = [ "file1" "file2" ];
+        };
+        # overriding imported path should keep existing attributes
+        # (e.g. olddir is still set)
+        import = {
+          notifempty = true;
+        };
+      };
+      # extraConfig compatibility - should be added to top level, early.
+      services.logrotate.extraConfig = ''
+        nomail
+      '';
+      # paths compatibility
+      services.logrotate.paths = {
+        compat_path = {
+          path = "compat_test_path";
+        };
+        # user/group should be grouped as 'su user group'
+        compat_user = {
+          user = config.users.users.root.name;
+          group = "root";
+        };
+        # extraConfig in path should be added to block
+        compat_extraConfig = {
+          extraConfig = "dateext";
+        };
+        # keep -> rotate
+        compat_keep = {
+          keep = 1;
         };
       };
     };
@@ -44,5 +102,23 @@ import ./make-test-python.nix ({ pkgs, ... }: rec {
           defaultMachine.fail("systemctl cat logrotate.service | grep -- --mail")
       with subtest("using mails adds mail option"):
           machine.succeed("systemctl cat logrotate.service | grep -- --mail")
+      with subtest("check generated config matches expectation"):
+          machine.succeed(
+              # copy conf to /tmp/logrotate.conf for easy grep
+              "conf=$(systemctl cat logrotate | grep -oE '/nix/store[^ ]*logrotate.conf'); cp $conf /tmp/logrotate.conf",
+              "! grep weekly /tmp/logrotate.conf",
+              "grep -E '^delaycompress' /tmp/logrotate.conf",
+              "tail -n 1 /tmp/logrotate.conf | grep shred",
+              "sed -ne '/\"sendmail\" {/,/}/p' /tmp/logrotate.conf | grep 'mail user@domain.tld'",
+              "sed -ne '/\"postrotate\" {/,/}/p' /tmp/logrotate.conf | grep endscript",
+              "grep '\"file1\"\n\"file2\" {' /tmp/logrotate.conf",
+              "sed -ne '/\"import\" {/,/}/p' /tmp/logrotate.conf | grep noolddir",
+              "sed -ne '1,/^\"/p' /tmp/logrotate.conf | grep nomail",
+              "grep '\"compat_test_path\" {' /tmp/logrotate.conf",
+              "sed -ne '/\"compat_user\" {/,/}/p' /tmp/logrotate.conf | grep 'su root root'",
+              "sed -ne '/\"compat_extraConfig\" {/,/}/p' /tmp/logrotate.conf | grep dateext",
+              "[[ $(sed -ne '/\"compat_keep\" {/,/}/p' /tmp/logrotate.conf | grep -w rotate) = \"  rotate 1\" ]]",
+              "! sed -ne '/\"compat_keep\" {/,/}/p' /tmp/logrotate.conf | grep -w keep",
+          )
     '';
 })