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