at 24.11-pre 10 kB view raw
1{ config, lib, pkgs, utils, ... }: 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 "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 = '' 106 logrotate freeform settings: each attribute here will define its own section, 107 ordered by {option}`services.logrotate.settings.<name>.priority`, 108 which can either define files to rotate with their settings 109 or settings common to all further files settings. 110 All attribute names not explicitly defined as sub-options here are passed through 111 as logrotate config directives, 112 refer to <https://linux.die.net/man/8/logrotate> for details. 113 ''; 114 example = literalExpression '' 115 { 116 # global options 117 header = { 118 dateext = true; 119 }; 120 # example custom files 121 "/var/log/mylog.log" = { 122 frequency = "daily"; 123 rotate = 3; 124 }; 125 "multiple paths" = { 126 files = [ 127 "/var/log/first*.log" 128 "/var/log/second.log" 129 ]; 130 }; 131 # specify custom order of sections 132 "/var/log/myservice/*.log" = { 133 # ensure lower priority 134 priority = 110; 135 postrotate = ''' 136 systemctl reload myservice 137 '''; 138 }; 139 }; 140 ''; 141 type = types.attrsOf (types.submodule ({ name, ... }: { 142 freeformType = with types; attrsOf (nullOr (oneOf [ int bool str ])); 143 144 options = { 145 enable = mkEnableOption "setting individual kill switch" // { 146 default = true; 147 }; 148 149 global = mkOption { 150 type = types.bool; 151 default = false; 152 description = '' 153 Whether this setting is a global option or not: set to have these 154 settings apply to all files settings with a higher priority. 155 ''; 156 }; 157 files = mkOption { 158 type = with types; either str (listOf str); 159 default = name; 160 defaultText = '' 161 The attrset name if not specified 162 ''; 163 description = '' 164 Single or list of files for which rules are defined. 165 The files are quoted with double-quotes in logrotate configuration, 166 so globs and spaces are supported. 167 Note this setting is ignored if globals is true. 168 ''; 169 }; 170 171 frequency = mkOption { 172 type = types.nullOr types.str; 173 default = null; 174 description = '' 175 How often to rotate the logs. Defaults to previously set global setting, 176 which itself defaults to weekly. 177 ''; 178 }; 179 180 priority = mkOption { 181 type = types.int; 182 default = 1000; 183 description = '' 184 Order of this logrotate block in relation to the others. The semantics are 185 the same as with `lib.mkOrder`. Smaller values are inserted first. 186 ''; 187 }; 188 }; 189 190 })); 191 }; 192 193 configFile = mkOption { 194 type = types.path; 195 default = configFile; 196 defaultText = '' 197 A configuration file automatically generated by NixOS. 198 ''; 199 description = '' 200 Override the configuration file used by logrotate. By default, 201 NixOS generates one automatically from [](#opt-services.logrotate.settings). 202 ''; 203 example = literalExpression '' 204 pkgs.writeText "logrotate.conf" ''' 205 missingok 206 "/var/log/*.log" { 207 rotate 4 208 weekly 209 } 210 '''; 211 ''; 212 }; 213 214 checkConfig = mkOption { 215 type = types.bool; 216 default = true; 217 description = '' 218 Whether the config should be checked at build time. 219 220 Some options are not checkable at build time because of the build sandbox: 221 for example, the test does not know about existing files and system users are 222 not known. 223 These limitations mean we must adjust the file for tests (missingok is forced 224 and users are replaced by dummy users), so tests are complemented by a 225 logrotate-checkconf service that is enabled by default. 226 This extra check can be disabled by disabling it at the systemd level with the 227 {option}`systemd.services.logrotate-checkconf.enable` option. 228 229 Conversely there are still things that might make this check fail incorrectly 230 (e.g. a file path where we don't have access to intermediate directories): 231 in this case you can disable the failing check with this option. 232 ''; 233 }; 234 235 extraArgs = lib.mkOption { 236 type = lib.types.listOf lib.types.str; 237 default = []; 238 description = "Additional command line arguments to pass on logrotate invocation"; 239 }; 240 }; 241 }; 242 243 config = mkIf cfg.enable { 244 systemd.services.logrotate = { 245 description = "Logrotate Service"; 246 startAt = "hourly"; 247 248 serviceConfig = { 249 Restart = "no"; 250 User = "root"; 251 ExecStart = "${pkgs.logrotate}/sbin/logrotate ${utils.escapeSystemdExecArgs cfg.extraArgs} ${mailOption} ${cfg.configFile}"; 252 }; 253 }; 254 systemd.services.logrotate-checkconf = { 255 description = "Logrotate configuration check"; 256 wantedBy = [ "multi-user.target" ]; 257 serviceConfig = { 258 Type = "oneshot"; 259 RemainAfterExit = true; 260 ExecStart = "${pkgs.logrotate}/sbin/logrotate ${utils.escapeSystemdExecArgs cfg.extraArgs} --debug ${cfg.configFile}"; 261 }; 262 }; 263 }; 264}