at 23.11-pre 9.3 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.logrotate; 7 8 generateLine = n: v: 9 if builtins.elem n [ "files" "priority" "enable" "global" ] || v == null then null 10 else if builtins.elem n [ "frequency" ] then "${v}\n" 11 else if builtins.elem n [ "firstaction" "lastaction" "prerotate" "postrotate" "preremove" ] 12 then "${n}\n ${v}\n endscript\n" 13 else if isInt v then "${n} ${toString v}\n" 14 else if v == true then "${n}\n" 15 else if v == false then "no${n}\n" 16 else "${n} ${v}\n"; 17 generateSection = indent: settings: concatStringsSep (fixedWidthString indent " " "") ( 18 filter (x: x != null) (mapAttrsToList generateLine settings) 19 ); 20 21 # generateSection includes a final newline hence weird closing brace 22 mkConf = settings: 23 if settings.global or false then generateSection 0 settings 24 else '' 25 ${concatMapStringsSep "\n" (files: ''"${files}"'') (toList settings.files)} { 26 ${generateSection 2 settings}} 27 ''; 28 29 settings = sortProperties (attrValues (filterAttrs (_: settings: settings.enable) ( 30 foldAttrs recursiveUpdate { } [ 31 { 32 header = { 33 enable = true; 34 missingok = true; 35 notifempty = true; 36 frequency = "weekly"; 37 rotate = 4; 38 }; 39 } 40 cfg.settings 41 { header = { global = true; priority = 100; }; } 42 ] 43 ))); 44 configFile = pkgs.writeTextFile { 45 name = "logrotate.conf"; 46 text = concatStringsSep "\n" ( 47 map mkConf settings 48 ); 49 checkPhase = optionalString cfg.checkConfig '' 50 # logrotate --debug also checks that users specified in config 51 # file exist, but we only have sandboxed users here so brown these 52 # out. according to man page that means su, create and createolddir. 53 # files required to exist also won't be present, so missingok is forced. 54 user=$(${pkgs.buildPackages.coreutils}/bin/id -un) 55 group=$(${pkgs.buildPackages.coreutils}/bin/id -gn) 56 sed -e "s/\bsu\s.*/su $user $group/" \ 57 -e "s/\b\(create\s\+[0-9]*\s*\|createolddir\s\+[0-9]*\s\+\).*/\1$user $group/" \ 58 -e "1imissingok" -e "s/\bnomissingok\b//" \ 59 $out > logrotate.conf 60 # Since this makes for very verbose builds only show real error. 61 # There is no way to control log level, but logrotate hardcodes 62 # 'error:' at common log level, so we can use grep, taking care 63 # to keep error codes 64 set -o pipefail 65 if ! ${pkgs.buildPackages.logrotate}/sbin/logrotate -s logrotate.status \ 66 --debug logrotate.conf 2>&1 \ 67 | ( ! grep "error:" ) > logrotate-error; then 68 echo "Logrotate configuration check failed." 69 echo "The failing configuration (after adjustments to pass tests in sandbox) was:" 70 printf "%s\n" "-------" 71 cat logrotate.conf 72 printf "%s\n" "-------" 73 echo "The error reported by logrotate was as follow:" 74 printf "%s\n" "-------" 75 cat logrotate-error 76 printf "%s\n" "-------" 77 echo "You can disable this check with services.logrotate.checkConfig = false," 78 echo "but if you think it should work please report this failure along with" 79 echo "the config file being tested!" 80 false 81 fi 82 ''; 83 }; 84 85 mailOption = 86 optionalString (foldr (n: a: a || (n.mail or false) != false) false (attrValues cfg.settings)) 87 "--mail=${pkgs.mailutils}/bin/mail"; 88in 89{ 90 imports = [ 91 (mkRemovedOptionModule [ "services" "logrotate" "config" ] "Modify services.logrotate.settings.header instead") 92 (mkRemovedOptionModule [ "services" "logrotate" "extraConfig" ] "Modify services.logrotate.settings.header instead") 93 (mkRemovedOptionModule [ "services" "logrotate" "paths" ] "Add attributes to services.logrotate.settings instead") 94 ]; 95 96 options = { 97 services.logrotate = { 98 enable = mkEnableOption (lib.mdDoc "the logrotate systemd service") // { 99 default = foldr (n: a: a || n.enable) false (attrValues cfg.settings); 100 defaultText = literalExpression "cfg.settings != {}"; 101 }; 102 103 settings = mkOption { 104 default = { }; 105 description = lib.mdDoc '' 106 logrotate freeform settings: each attribute here will define its own section, 107 ordered by priority, which can either define files to rotate with their settings 108 or settings common to all further files settings. 109 Refer to <https://linux.die.net/man/8/logrotate> for details. 110 ''; 111 example = literalExpression '' 112 { 113 # global options 114 header = { 115 dateext = true; 116 }; 117 # example custom files 118 "/var/log/mylog.log" = { 119 frequency = "daily"; 120 rotate = 3; 121 }; 122 "multiple paths" = { 123 files = [ 124 "/var/log/first*.log" 125 "/var/log/second.log" 126 ]; 127 }; 128 }; 129 ''; 130 type = types.attrsOf (types.submodule ({ name, ... }: { 131 freeformType = with types; attrsOf (nullOr (oneOf [ int bool str ])); 132 133 options = { 134 enable = mkEnableOption (lib.mdDoc "setting individual kill switch") // { 135 default = true; 136 }; 137 138 global = mkOption { 139 type = types.bool; 140 default = false; 141 description = lib.mdDoc '' 142 Whether this setting is a global option or not: set to have these 143 settings apply to all files settings with a higher priority. 144 ''; 145 }; 146 files = mkOption { 147 type = with types; either str (listOf str); 148 default = name; 149 defaultText = '' 150 The attrset name if not specified 151 ''; 152 description = lib.mdDoc '' 153 Single or list of files for which rules are defined. 154 The files are quoted with double-quotes in logrotate configuration, 155 so globs and spaces are supported. 156 Note this setting is ignored if globals is true. 157 ''; 158 }; 159 160 frequency = mkOption { 161 type = types.nullOr types.str; 162 default = null; 163 description = lib.mdDoc '' 164 How often to rotate the logs. Defaults to previously set global setting, 165 which itself defaults to weekly. 166 ''; 167 }; 168 169 priority = mkOption { 170 type = types.int; 171 default = 1000; 172 description = lib.mdDoc '' 173 Order of this logrotate block in relation to the others. The semantics are 174 the same as with `lib.mkOrder`. Smaller values are inserted first. 175 ''; 176 }; 177 }; 178 179 })); 180 }; 181 182 configFile = mkOption { 183 type = types.path; 184 default = configFile; 185 defaultText = '' 186 A configuration file automatically generated by NixOS. 187 ''; 188 description = lib.mdDoc '' 189 Override the configuration file used by logrotate. By default, 190 NixOS generates one automatically from [](#opt-services.logrotate.settings). 191 ''; 192 example = literalExpression '' 193 pkgs.writeText "logrotate.conf" ''' 194 missingok 195 "/var/log/*.log" { 196 rotate 4 197 weekly 198 } 199 '''; 200 ''; 201 }; 202 203 checkConfig = mkOption { 204 type = types.bool; 205 default = true; 206 description = lib.mdDoc '' 207 Whether the config should be checked at build time. 208 209 Some options are not checkable at build time because of the build sandbox: 210 for example, the test does not know about existing files and system users are 211 not known. 212 These limitations mean we must adjust the file for tests (missingok is forced 213 and users are replaced by dummy users), so tests are complemented by a 214 logrotate-checkconf service that is enabled by default. 215 This extra check can be disabled by disabling it at the systemd level with the 216 {option}`services.systemd.services.logrotate-checkconf.enable` option. 217 218 Conversely there are still things that might make this check fail incorrectly 219 (e.g. a file path where we don't have access to intermediate directories): 220 in this case you can disable the failing check with this option. 221 ''; 222 }; 223 }; 224 }; 225 226 config = mkIf cfg.enable { 227 systemd.services.logrotate = { 228 description = "Logrotate Service"; 229 startAt = "hourly"; 230 231 serviceConfig = { 232 Restart = "no"; 233 User = "root"; 234 ExecStart = "${pkgs.logrotate}/sbin/logrotate ${mailOption} ${cfg.configFile}"; 235 }; 236 }; 237 systemd.services.logrotate-checkconf = { 238 description = "Logrotate configuration check"; 239 wantedBy = [ "multi-user.target" ]; 240 serviceConfig = { 241 Type = "oneshot"; 242 RemainAfterExit = true; 243 ExecStart = "${pkgs.logrotate}/sbin/logrotate --debug ${cfg.configFile}"; 244 }; 245 }; 246 }; 247}