grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / logging / logrotate.nix
blob6fb5f0588d97a8e041511daadf1c0a4c1958e03b
1 { config, lib, pkgs, utils, ... }:
2 let
3   cfg = config.services.logrotate;
5   generateLine = n: v:
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"
13     else "${n} ${v}\n";
14   generateSection = indent: settings: lib.concatStringsSep (lib.fixedWidthString indent " " "") (
15     lib.filter (x: x != null) (lib.mapAttrsToList generateLine settings)
16   );
18   # generateSection includes a final newline hence weird closing brace
19   mkConf = settings:
20     if settings.global or false then generateSection 0 settings
21     else ''
22       ${lib.concatMapStringsSep "\n" (files: ''"${files}"'') (lib.toList settings.files)} {
23         ${generateSection 2 settings}}
24     '';
26   settings = lib.sortProperties (lib.attrValues (lib.filterAttrs (_: settings: settings.enable) (
27     lib.foldAttrs lib.recursiveUpdate { } [
28       {
29         header = {
30           enable = true;
31           missingok = true;
32           notifempty = true;
33           frequency = "weekly";
34           rotate = 4;
35         };
36       }
37       cfg.settings
38       { header = { global = true; priority = 100; }; }
39     ]
40   )));
41   configFile = pkgs.writeTextFile {
42     name = "logrotate.conf";
43     text = lib.concatStringsSep "\n" (
44       map mkConf settings
45     );
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//" \
56           $out > logrotate.conf
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
60       # to keep error codes
61       set -o pipefail
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" "-------"
68               cat logrotate.conf
69               printf "%s\n" "-------"
70               echo "The error reported by logrotate was as follow:"
71               printf "%s\n" "-------"
72               cat logrotate-error
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!"
77               false
78       fi
79     '';
80   };
82   mailOption =
83     lib.optionalString (lib.foldr (n: a: a || (n.mail or false) != false) false (lib.attrValues cfg.settings))
84     "--mail=${pkgs.mailutils}/bin/mail";
87   imports = [
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")
91   ];
93   options = {
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 != {}";
98       };
100       allowNetworking = lib.mkEnableOption "network access for logrotate";
102       settings = lib.mkOption {
103         default = { };
104         description = ''
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.
112         '';
113         example = lib.literalExpression ''
114           {
115             # global options
116             header = {
117               dateext = true;
118             };
119             # example custom files
120             "/var/log/mylog.log" = {
121               frequency = "daily";
122               rotate = 3;
123             };
124             "multiple paths" = {
125                files = [
126                 "/var/log/first*.log"
127                 "/var/log/second.log"
128               ];
129             };
130             # specify custom order of sections
131             "/var/log/myservice/*.log" = {
132               # ensure lower priority
133               priority = 110;
134               postrotate = '''
135                 systemctl reload myservice
136               ''';
137             };
138           };
139           '';
140         type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: {
141           freeformType = with lib.types; attrsOf (nullOr (oneOf [ int bool str ]));
143           options = {
144             enable = lib.mkEnableOption "setting individual kill switch" // {
145               default = true;
146             };
148             global = lib.mkOption {
149               type = lib.types.bool;
150               default = false;
151               description = ''
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.
154               '';
155             };
156             files = lib.mkOption {
157               type = with lib.types; either str (listOf str);
158               default = name;
159               defaultText = ''
160                 The attrset name if not specified
161               '';
162               description = ''
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.
167               '';
168             };
170             frequency = lib.mkOption {
171               type = lib.types.nullOr lib.types.str;
172               default = null;
173               description = ''
174                 How often to rotate the logs. Defaults to previously set global setting,
175                 which itself defaults to weekly.
176               '';
177             };
179             priority = lib.mkOption {
180               type = lib.types.int;
181               default = 1000;
182               description = ''
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.
185               '';
186             };
187           };
189         }));
190       };
192       configFile = lib.mkOption {
193         type = lib.types.path;
194         default = configFile;
195         defaultText = ''
196           A configuration file automatically generated by NixOS.
197         '';
198         description = ''
199           Override the configuration file used by logrotate. By default,
200           NixOS generates one automatically from [](#opt-services.logrotate.settings).
201         '';
202         example = lib.literalExpression ''
203           pkgs.writeText "logrotate.conf" '''
204             missingok
205             "/var/log/*.log" {
206               rotate 4
207               weekly
208             }
209           ''';
210         '';
211       };
213       checkConfig = lib.mkOption {
214         type = lib.types.bool;
215         default = true;
216         description = ''
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
221           not known.
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.
231         '';
232       };
234       extraArgs = lib.mkOption {
235         type = lib.types.listOf lib.types.str;
236         default = [];
237         description = "Additional command line arguments to pass on logrotate invocation";
238       };
239     };
240   };
242   config = lib.mkIf cfg.enable {
243     systemd.services.logrotate = {
244       description = "Logrotate Service";
245       documentation = [
246         "man:logrotate(8)"
247         "man:logrotate(5)"
248       ];
249       startAt = "hourly";
251       serviceConfig = {
252         Type = "oneshot";
253         ExecStart = "${lib.getExe pkgs.logrotate} ${utils.escapeSystemdExecArgs cfg.extraArgs} ${mailOption} ${cfg.configFile}";
255         # performance
256         Nice = 19;
257         IOSchedulingClass = "best-effort";
258         IOSchedulingPriority = 7;
260         # hardening
261         CapabilityBoundingSet = [
262           "CAP_CHOWN"
263           "CAP_SETGID"
264         ];
265         DevicePolicy = "closed";
266         LockPersonality = true;
267         MemoryDenyWriteExecute = true;
268         NoNewPrivileges = true;
269         PrivateDevices = true;
270         PrivateTmp = true;
271         ProcSubset = "pid";
272         ProtectClock = true;
273         ProtectControlGroups = true;
274         ProtectHome = 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";
285         SystemCallFilter = [
286           "@system-service"
287           "~@privileged @resources"
288           "@chown"
289         ];
290         UMask = "0027";
291       } // lib.optionalAttrs (!cfg.allowNetworking) {
292         PrivateNetwork = true;
293         RestrictAddressFamilies = "none";
294       };
295     };
296     systemd.services.logrotate-checkconf = {
297       description = "Logrotate configuration check";
298       wantedBy = [ "multi-user.target" ];
299       serviceConfig = {
300         Type = "oneshot";
301         RemainAfterExit = true;
302         ExecStart = "${pkgs.logrotate}/sbin/logrotate ${utils.escapeSystemdExecArgs cfg.extraArgs} --debug ${cfg.configFile}";
303       };
304     };
305   };