Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

logrotate: update to freeform #162063

Merged
merged 7 commits into from
Apr 1, 2022
Merged

logrotate: update to freeform #162063

merged 7 commits into from
Apr 1, 2022

Conversation

martinetd
Copy link
Member

@martinetd martinetd commented Feb 27, 2022

Motivation for this change

logrotate is much bigger than intended through its mailutils dependency as reported in #162001

This attempts to resolve the problem by making the dependency only appear when required.

Notes:

  • also included first commit for logrotate: do not enable logrotate.service itself #161929 that is a release blocker and will (should) get merged first
  • this isn't finished as illustrated by PR being a draft, in particular:
    • setting paths user/group doesn't work and was commented
    • the compat renamedoptions from services.logrotate.config to extraConfig was removed as non-working, extraConfig would need to be readded as an explicit option for it to work (not difficult) but it was suggested that extraConfg be removed in Logrotate adds bloat to installation media closure #162001 (comment) so I'm not quite sure what to do?
    • coupled with logrotate: do not enable logrotate.service itself #161929 since the service is no longer immediately started on system switch, it's very likely that a configuration error will slip unnoticed. I'd like to add a validation step (logrotate --debug config) somehow, ideally at build time, but I'm not sure how to do that.
      (EDIT: looking around I quite like what nixos/modules/services/networking/bird.nix is doing with a checkPhase on a file that can be disabled through cfg.checkConfig in pkgs.writeTextFile. We can do this easily, I'm mostly done with that)
      (EDIT2: done, but doesn't check users -- if someone knows how to do that it'd be great...)
    • some of it is quite ugly and I'll be happy to take any improvement, I've favored compatibility over style but there's definitely ways to make this better...
    • EDIT: I have no idea why the manual update check fails -- updating an existing section is not allowed?
Things done

@github-actions github-actions bot added 6.topic: nixos Issues or PRs affecting NixOS modules, or package usability issues specific to NixOS 8.has: changelog 8.has: documentation This PR adds or changes documentation 8.has: module (update) This PR changes an existing module in `nixos/` labels Feb 27, 2022
@ofborg ofborg bot requested a review from viric February 27, 2022 10:34
@ju1m
Copy link
Contributor

ju1m commented Feb 27, 2022

@martinetd regarding the build error for the manual:

Check DocBook files generated from Markdown are consistent

this is related to a new todo item in the PRs' template:

  • (Release notes changes) Ran nixos/doc/manual/md-to-db.sh to update generated release notes

@martinetd martinetd force-pushed the logrotate_size branch 2 times, most recently from b7c0fed to 8a64038 Compare February 27, 2022 14:29
@martinetd martinetd marked this pull request as ready for review February 27, 2022 14:30
@martinetd martinetd requested a review from dasJ as a code owner February 27, 2022 14:30
@martinetd martinetd marked this pull request as draft February 27, 2022 14:36
@martinetd
Copy link
Member Author

Mostly fixed what I wanted, still need to figure out why the manual check fails now (+ stray tab but that one is easy and isn't related)... out of time for today, later!

@Izorkin
Copy link
Contributor

Izorkin commented Feb 27, 2022

Is it possible to add the ability to specify several different files?

  services.logrotate = {
    paths = {
      files = [ "file1" "file2" ];
      extraConfig = ''
        daily
      '';
    };
  };

Result:

file1
file2
{
  daily
}

@martinetd
Copy link
Member Author

martinetd commented Feb 27, 2022

Is it possible to add the ability to specify several different files?

It was meant to be possible yes! I don't think I changed this part and didn't test but this obviously doesn't work as you've pointed out...
I'll add a test and fix it.

@martinetd
Copy link
Member Author

I'm finishing this up but for your question this actually works, the problem is the usage -- I'm actually curious nix doesn't error on this for you, when I try I get this error as appropriate:

error: A definition for option `services.logrotate.paths.extraConfig' is not of type `attribute set of null or boolean or signed integer or strings'. Definition values:
       - In `/home/asmadeus/nixpkgs/nixos/lib/build-vms.nix':
           ''
             daily
           ''
(use '--show-trace' to show detailed location information)

There are two errors:

  • paths is an attrset of paths, so you don't define a section directly but need to name it
  • the special attribute key to specify files is 'path', not 'files', this must be kept for compatibility
    This appears to work:
      services.logrotate.paths = {
        multipath = {
          path = [ "file1" "file2" ];
          #whatever attributes you want
        };
      };

And generates the following, which should work (I think):

"file1" "file2" {
daily
rotate 20
}

(daily and rotate 20 come from default values of frequency and keep; the current version will also have plenty of empty lines that I've trimmed in an update I'm planning to push as soon as I understand why manual stopped building...)

@martinetd martinetd marked this pull request as ready for review February 27, 2022 22:26
@martinetd
Copy link
Member Author

Okay, I think we're good this time. Style aside I see no more big problem.
There are a couple of caveats:

  • the check added for logrotate isn't perfect, logrotate --debug is very picky (users that don't exist will generate an error (I'm stripping users for test), when running as normal user like now su as other users fail (also dealt with by stripping users), files missing when there is no missingok fail the check (enabled by default so not a big problem), if a permission denied happens because an intermediate directory isn't readable to our builder the check will fail (thanksfully there the sandbox is helpful, file will not not exist..))
    overall it feels better than no check but it might be surprising to some and probably could deserve some improvement, although I don't see how lest of patching logrotate or writing our own verifier....
  • as said in original message I've favored backwards compatibility over style, there's much to be left desired. I didn't bother checking for 'mail' attribute in extraConfig though so these users will need adjusting, that might be worth a warning...

Happy to adjust things as requested, please have a look @ius @Artturin @aanderse @dasJ ! thanks!

@martinetd martinetd force-pushed the logrotate_size branch 2 times, most recently from 3058cc8 to d3c9e54 Compare February 27, 2022 22:37
Copy link
Contributor

@ju1m ju1m left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a consistent code source style, you may run nixpkgs-fmt on the .nix files (in a dedicated commit to facilitate reviewing).
Great work @martined! Thanks for having taken time to revamp this service with so much attention to corner cases. :)

(mapAttrsToList generateLine settings)
++ [
# user and group must be handled together
(if (settings.user or null != null && settings.group or null != null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since user and group have default values this should be equivalent to settings.user != null and settings.group != null. If you ever need to see if a module option has been defined, you may add options to the { config, lib, pkgs, ... }: and use options.services.logrotate.settings.foo.isDefined.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes and no, that would be true for paths attrsets but generateSection is also used with services.logrotate.settings which doesn't have default values there.

# logrotate --debug also checks that users specified in config
# file exist, but we only have sandboxed users here so brown these
# out. according to man page that means su, create and createolddir.
# XXX find a way to use real /etc/passwd and /etc/groups
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's possible to use the real /etc/passwd and /etc/groups at build time, especially when mutableUsers == true.

machine = { ... }: {
nodes = {
# default machine
defMachine = { ... }: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defaultMachine would be clearer and needs no comment.

@@ -36,15 +33,17 @@ let
type = with types; nullOr str;
default = null;
description = ''
The user account to use for rotation.
The user account to use for rotation. This setting is deprecated, please use
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that settings is to be deprecated I would use mkRemovedOptionModule with a message indicating to use su, because a simple change in the description goes easily unnoticed.
And simplify the module and test by removing the new code to support the backward compatibility.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to leave a window with the setting deprecated but still working, it's possible by adding warnings by setting config.warnings conditionally (plenty of examples about) but I didn't get around to it yet.

I agree the description isn't enough in itself, but I think warnings can be added in a subsequent PR so as not to slow this down -- I don't really like PRs growing in scope all the time and prefer to keep goals somewhat constrainted... But if I get around to it fast enough I'll update this PR when I'm done.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(that being said, if the consensus is to deprecate settings the hard way, it'll definitely make this module a bit simpler and I don't particularly mind for myself. We can easily remove everywhere I've marked deprecated and most of the exceptions I added to generateConfig)

default = null;
description = ''
Extra lines prepended to logrotate configuration file.
Deprecated, use settings instead.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a prettier rendering, you may use: <xref linkend="opt-services.logrotate.settings"/>

Copy link
Member Author

@martinetd martinetd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review! I'll fix the two suggestions I didn't reply to tonight, and check nixpkgs-fmt as well.

@@ -36,15 +33,17 @@ let
type = with types; nullOr str;
default = null;
description = ''
The user account to use for rotation.
The user account to use for rotation. This setting is deprecated, please use
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to leave a window with the setting deprecated but still working, it's possible by adding warnings by setting config.warnings conditionally (plenty of examples about) but I didn't get around to it yet.

I agree the description isn't enough in itself, but I think warnings can be added in a subsequent PR so as not to slow this down -- I don't really like PRs growing in scope all the time and prefer to keep goals somewhat constrainted... But if I get around to it fast enough I'll update this PR when I'm done.

(mapAttrsToList generateLine settings)
++ [
# user and group must be handled together
(if (settings.user or null != null && settings.group or null != null)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes and no, that would be true for paths attrsets but generateSection is also used with services.logrotate.settings which doesn't have default values there.

@Izorkin
Copy link
Contributor

Izorkin commented Feb 28, 2022

@martinetd i am currently writing the logrotate configuration with extraConf. The resulting file looks like this:

notifempty
olddir all_log
daily
nocompress
rotate 30
missingok
dateext
dateformat -%Y-%m-%d
nomail
ifempty
sharedscripts

/var/log/sshd/auth.log
{
  postrotate
    systemctl reload syslog-ng
  endscript
}
olddir all_log
createolddir 0755 root root
daily
nocompress
rotate 30
missingok
dateext
dateformat _%Y-%m-%d
dateyesterday
datehourago
nomail
ifempty
sharedscripts

/var/log/nginx/site1_*.log
{
  create 0640 web-nginx www-user1
  postrotate
    [ ! -f /run/nginx/nginx.pid ] || kill -USR1 `cat /run/nginx/nginx.pid`
  endscript
}

...
/var/log/nginx/access.log
/var/log/nginx/error.log
/var/data/web/all/logs/nginx/access.log
/var/data/web/all/logs/nginx/error.log
{
  create 0640 web-nginx web-nginx
  postrotate
    [ ! -f /run/nginx/nginx.pid ] || kill -USR1 `cat /run/nginx/nginx.pid`
  endscript
}

Instead of this variant:

"file1" "file2" {
daily
rotate 20
}

It is possible to generate this variant:

"file1"
"file2"
{
daily
rotate 20
}

Will work too.

@Izorkin
Copy link
Contributor

Izorkin commented Feb 28, 2022

Another option would be to add the option of writing all the parameters to the very top of the configuration, which are used by default. The parameters specified below in the file overwrite the upper parameters.

@Izorkin
Copy link
Contributor

Izorkin commented Feb 28, 2022

  services.logrotate = {
    extraConfigDefault = ''
      daily
      nocompress
      rotate 30
      missingok
      dateext
      dateformat -%Y-%m-%d
      nomail
      ifempty
      sharedscripts
    '';
  };

@martinetd
Copy link
Member Author

add a comment with a link to this pr and that the deprecated options should be removed before 22.11

Thanks for your review!

I've opened a PR with option removal (as draft) and added a comment with a link to it.

Copy link
Member

@Artturin Artturin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems fine

Built a vm and compared the new generated file to the old one and it was ok

@Artturin Artturin force-pushed the logrotate_size branch 2 times, most recently from 21c9129 to 4a466cb Compare March 15, 2022 17:35
@Artturin
Copy link
Member

squashed locally to preserve commit messages

@martinetd
Copy link
Member Author

martinetd commented Mar 15, 2022

Thanks! I prefer to keep things split slightly though to illustrate logical changes, I've fixed the history up so all fixes commits are squashed with their appropriate counterpart, and we can just e.g. revert the logrotate-checkconf service commit if this becomes needed later, or simply for easier history reading while keeping the system bisectable (well, at least I've checked that nixosTests.logrotate passes for each commit individually, so logrotate is "instantiable" at all time)

You can check this results in no code change:

$ git diff 4a466cb 1647fd2
<empty>

I appreciate your effort of manually squashing though, and have kept your squashed commit around in a local branch if you strongly prefer a single commit for merging (just push it back or tell me to and I will)

having pkgs.logrotate depend on mailutils brings in quite a bit of dependencies
through mailutil itself and recursive dependency to guile when most people
do not need it.

Remove mailutils dependency from the package, and conditionally add it to the
service if the user specify the mail option either at top level or in a path

Fixes NixOS#162001
Running once now will make further patches formatting easier
using freeform is the new standard way of using modules and should replace
extraConfig.
In particular, this will allow us to place a condition on mails
Now the service no longer starts immediately,
check if the config we generated makes sense as soon as possible.

The check isn't perfect because logrotate --debug wants to check
users required, there are two problems:
 - /etc/passwd and /etc/group are sandboxed and we don't have
visibility of system users
 - the check phase runs as nixbld which cannot su to other users
and logrotate fails on this

Until these two problems can be addressed, users-related checks
are filtered out, it's still much better than no check.
The check can be disabled with services.logrotate.checkConfig
if required
(bird also has a preCheck param, to prepare the environment
before check, but we can add it if it becomes necessary)

Since this makes for very verbose builds, we only show errors:
There is no way to control log level, but logrotate hardcodes
'error:' at common log level, so we can use grep, taking care
to keep error codes

Some manual tests:
───────┬──────────────────────────────────────────
       │ File: valid-config.conf
───────┼──────────────────────────────────────────
   1   │ missingok
───────┴──────────────────────────────────────────
logrotate --debug ok
grep ok

───────┬──────────────────────────────────────────
       │ File: postrotate-no-end.conf
───────┼──────────────────────────────────────────
   1   │ missingok
   2   │ /file {
   3   │    postrotate
   4   │      test
   5   │ }
───────┴──────────────────────────────────────────
error: postrotate-no-end.conf:prerotate, postrotate or preremove without endscript

───────┬──────────────────────────────────────────
       │ File: missing-file.conf
───────┼──────────────────────────────────────────
   1   │ "test" { daily }
───────┴──────────────────────────────────────────
error: stat of test failed: No such file or directory

───────┬──────────────────────────────────────────
       │ File: unknown-option.conf
───────┼──────────────────────────────────────────
   1   │ some syntax error
───────┴──────────────────────────────────────────
logrotate --debug ok
error: unknown-option.conf:1 unknown option 'some' -- ignoring line

───────┬──────────────────────────────────────────
       │ File: unknown-user.conf
───────┼──────────────────────────────────────────
   1   │ su notauser notagroup
───────┴──────────────────────────────────────────
error: unknown-user.conf:1 unknown user 'notauser'

In particular note that logrotate would not error on unknown option
(it just ignores the line) but this change makes the check fail.
the build-time check is not safe (e.g. doesn't protect from bad users or nomissingok
paths missing), so add a new unit for configuration switch time check
@martinetd martinetd requested a review from a team as a code owner March 31, 2022 22:10
@martinetd
Copy link
Member Author

aa0f27a introduced a small conflict so I've rebased the branch to today's master.

@aanderse my suggestion to review the final form in #164169 instead still stands if you
can find a moment (should be less convoluted than this as compatibility has been removed),
otherwise I think we're getting stuck -- this has had two proper reviews (from ju1m and
Artturin), should I try to find one more?

@Artturin
Copy link
Member

Artturin commented Apr 1, 2022

@ofborg test logrotate gitlab nginx systemd

You have written tests so if these pass I think ill merge

@martinetd
Copy link
Member Author

You have written tests so if these pass I think ill merge

The tests passed! thanks!

@Artturin Artturin merged commit c7ac6ff into NixOS:master Apr 1, 2022
@Artturin
Copy link
Member

Artturin commented Apr 1, 2022

Thanks for your work

@aanderse
Copy link
Member

aanderse commented Apr 1, 2022

🚀

};

mailOption =
if foldr (n: a: a || n ? mail) false (attrValues cfg.settings)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late comment, but this doesn't seem to be accounting for when the user sets mail = false (so that nomail appears in the appropriate section in logrotate.conf).

In other words, if the user sets mail = false in one of the sections in the settings, the --mail option will get added even though no section in the settings actually sets mail = true.

Copy link
Member Author

@martinetd martinetd Jun 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, I'll update it to a || n.mail or false in a new PR (EDIT: when I get home, so now + ~7 hours)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in #177106


mailOption =
if foldr (n: a: a || n ? mail) false (attrValues cfg.settings)
then "--mail=${pkgs.mailutils}/bin/mail"
Copy link
Member

@wizeman wizeman Jun 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, sorry for the late comment, but shouldn't this be something like:

if /* ... */
then "\"--mail=${pkgs.mailutils}/bin/mail -s\""
else ""

i.e., shouldn't the -s argument be specified?

The logrotate man page says (about the --mail option):

This command should accept two arguments: 1) the subject of the message, and 2) the recipient.
(...)
The default mail command is /bin/mail -s.

Please note that I haven't actually tested sending mail, though (neither with nor without the -s option).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, this comes straight from what we had in the package, from the recent diff:

-  ] ++ lib.optionals (mailutils != null) [
-    "--with-default-mail-command=${mailutils}/bin/mail"
   ];

So if this doesn't work then it's likely sending mails from logrotate never worked...

I'll admit I never tested either myself, I'll give it a try this weekend unless someone beats me to it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from logrotate changelog:

106:  - remove `-s` from `DEFAULT_MAIL_COMMAND` and improve its documentation (#152)

You might be looking at old man page? that dates 3.13.0 (2017)....

looking at the code and not testing I think we're fine, my setup is half broken so it's a pain to test and I'll leave it at code inspection...

Copy link
Member

@wizeman wizeman Jun 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think you are correct.
I was looking at https://linux.die.net/man/8/logrotate, which is what man configuration.nix pointed me at, but it seems that this man page is not up-to-date.

@martinetd martinetd deleted the logrotate_size branch October 5, 2022 07:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
6.topic: nixos Issues or PRs affecting NixOS modules, or package usability issues specific to NixOS 8.has: changelog 8.has: documentation This PR adds or changes documentation 8.has: module (update) This PR changes an existing module in `nixos/` 10.rebuild-darwin: 1-10 10.rebuild-darwin: 1 10.rebuild-linux: 1-10
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants