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

nixos/dovecot: More sieve options and precompiling of scripts #35536

Closed
wants to merge 3 commits into from

Conversation

griff
Copy link
Contributor

@griff griff commented Feb 25, 2018

Motivation for this change

This PR does 4 different things in the 3 commit:

test of sieve scripts

Before making changes to how sieve scripts were handled I thought it best that the dovecot test included some testing of the sieve script functionality.

Patching dovecot_pigeonhole and builder to precompile scripts

One of the changes I wanted to make was to precompile sieve scripts as part of a Nix derivation. But to do that dovecot_pigeonhole needs to be changed since when source and compiled script have same mtime it thinks that the compiled script is out of date. I have tried to upstream that change with dovecot/pigeonhole#4 but it is as of yet not merged.

The builder does its compilation in the install phase because the compiled .svbin files contain a reference to the full path of the source .sieve file and so the source needs to be in $out when doing the compile.

Sieve related options in the dovecot2 module

When precompiling sieve scripts the compiler needs a dovecot configuration that defines sieve_plugins, sieve_extensions and sieve_global_extensions to know which extensions and plugin functionality to allow but you can’t just use the dovecot2 config file directly because that would create a dependency cycle. So to better support precompilation I have added explicit options for these 3 things to the dovecot2 module so that I can reuse their values when precompiling.

The sieveScripts option followed very closely how dovecot configures global scripts and was a direct mapping from attribute name to sieve_${attribute name} which has some problems. E.g. if you defined sieveScripts.after2 without defining sieveScripts.after your script would never be called. It also wasn’t totally clear without reading the dovecot documentation what scripts you could define.

So I have added explicit options sieve.beforeScripts, sieve.defaultScript, sieve.afterScripts and sieve.discardScript for each of the scripts you can define with beforeScripts and afterScripts being lists of paths that are automatically translated to the correct numbered dovecot configuration and with the added bonus that multiple definitions of the option are merged into one list. I have also included functionality so that existing configurations that use the sieveScripts option continue to work as before but now with a warning pointing to the new options.

The other place were you might use global sieve scripts and so would like to precompile the scripts is when using the imapsieve plugin. Before there were no options for this so I have added options for enabling and configuring imapsieve with support for automatic precompilation of scripts.

Things done
  • Tested using sandboxing (nix.useSandbox on NixOS, or option build-use-sandbox in nix.conf on non-NixOS)
  • Built on platform(s)
    • NixOS
    • macOS
    • other Linux distributions
  • Tested via one or more NixOS test(s) if existing and applicable for the change (look inside nixos/tests)
  • Tested compilation of all pkgs that depend on this change using nix-shell -p nox --run "nox-review wip"
  • Tested execution of all binary files (usually in ./result/bin/)
  • Fits CONTRIBUTING.md.

@dotlambda
Copy link
Member

/cc @andir @fpletz

'';
};
beforeScript = mkOption {
type = types.nullOr types.path;
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the reason to ask for a path to a sieve script instead of an actual string containing the sieve script here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the sieve script needs to be written to a file no matter what and as a user of this module I can prefer to store my sieve scripts either in files or inline in the configuration.

If I prefer to store my sieve scripts in files and the option is a path I can just provide the path to the sieve script. And if I prefer to store my sieve scripts inline in the configuration I can simply use the writeText builder.

On the other hand if this option was a string and I prefer to store my sieve scripts in files I would have to use fileContents to load the data from the file, only for the module to write that content to a different file which seems a waste.

I will also admit that my personal preference is to store my sieve scripts in files so that things like syntax highlighting work.

Copy link
Contributor

Choose a reason for hiding this comment

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

On the other hand if this option was a string and I prefer to store my sieve scripts in files I would have to use fileContents to load the data from the file, only for the module to write that content to a different file which seems a waste

Yes, but using a string is standard… and it's not like there's not a lot of copying going on during builds ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@wmertens I know. I guess I just like writeText more than fileContents.

I have considered supporting both strings and path by making the value an attrset like environment.etc or services.rspamd.locals but a listOf attrset is not as nice to work with as the named submodules like environment.etc and services.rspamd.locals.

Copy link
Contributor

Choose a reason for hiding this comment

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

How about having both text and path attributes? Or, if a Sieve script can never be a single line starting with /, autodetect?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@wmertens I looked at the Sieve RFC and from that it seems that while a sieve script can start with / it has to start with /* in that case which is obviously not a valid store path. So I am working on an update for this PR the resolves merge conflicts and supports having both text and path attributes for Sieve scripts.

@stale
Copy link

stale bot commented Jun 1, 2020

Thank you for your contributions.
This has been automatically marked as stale because it has had no activity for 180 days.
If this is still important to you, we ask that you leave a comment below. Your comment can be as simple as "still important to me". This lets people see that at least one person still cares about this. Someone will have to do this at most twice a year if there is no other activity.
Here are suggestions that might help resolve this more quickly:

  1. Search for maintainers and people that previously touched the
    related code and @ mention them in a comment.
  2. Ask on the NixOS Discourse. 3. Ask on the #nixos channel on
    irc.freenode.net.

@stale stale bot added 2.status: stale https://github.com/NixOS/nixpkgs/blob/master/.github/STALE-BOT.md and removed 2.status: stale https://github.com/NixOS/nixpkgs/blob/master/.github/STALE-BOT.md labels Jun 1, 2020
@wmertens
Copy link
Contributor

wmertens commented Jun 1, 2020

I don't use Sieve but LGTM. I'm not that bothered by the paths vs text issue.

@griff
Copy link
Contributor Author

griff commented Oct 20, 2020

I have now resolved all the merge conflicts and removed the commit that was no longer relevant since the functionality had since been implemented in another PR.

I have also done as @wmertens sugested and implemented support for Sieve scripts being either a string with the script or a path and the module will automatically detect the string script and write it to the store.

@uwap I hope this addresses the reservations you had.

Copy link
Member

@nlewo nlewo left a comment

Choose a reason for hiding this comment

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

These changes are really nice!
Here some minor comments and i still would like to play with this PR a bit to see what is generated and better understand it.

${concatStringsSep "\n" (imap1 (i: v: ''
mkdir -p after/${toString i}
if [ -d '${v}' ]; then
cp -p '${v}'/*.sieve after/${toString i}
Copy link
Member

Choose a reason for hiding this comment

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

Would it be possible to create symlinks instead of copying files?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! And while looking at this I found that when the path was outside the store the user feedback wasn't that great and would result in an unclean build in non-sandboxed builds so I have added logic to use fileContents to write paths outside the store to the store first.

plugins = cfg.sieve.plugins;
extensions = cfg.sieve.extensions;
globalExtensions = cfg.sieve.globalExtensions;
phases = ["buildPhase" "installPhase" "fixupPhase"];
Copy link
Member

Choose a reason for hiding this comment

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

It is recommended to use dontConfigure instead of defining your own list of phases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done! But needed to also set dontUnpack

@@ -11,6 +11,15 @@ stdenv.mkDerivation rec {

buildInputs = [ dovecot openssl ];

patches = [
# Fix mtime comparison so that scripts can be precompiled
Copy link
Member

Choose a reason for hiding this comment

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

Could you link your PR also?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done!

stateDir = "/var/lib/dovecot";

sieveToPath = text:
if isString text && builtins.substring 0 2 text == "/*"
Copy link
Member

Choose a reason for hiding this comment

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

The lib.hasPrefix would be more explicit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

@stale
Copy link

stale bot commented Jul 21, 2021

I marked this as stale due to inactivity. → More info

@stale stale bot added the 2.status: stale https://github.com/NixOS/nixpkgs/blob/master/.github/STALE-BOT.md label Jul 21, 2021
@poscat0x04
Copy link
Contributor

What's the current status of this PR? may I ask.

@stale stale bot removed the 2.status: stale https://github.com/NixOS/nixpkgs/blob/master/.github/STALE-BOT.md label Oct 11, 2021
@ofborg ofborg bot requested a review from ajs124 October 11, 2021 19:23
@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/prs-ready-for-review/3032/604

then let value = config.services.dovecot2.sieveScripts."${type}${indexName}";
in if (substring 0 1 (toString value) == "/")
then [value] ++ (listOfScripts type (idx + 1) config)
else []
Copy link
Member

Choose a reason for hiding this comment

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

I think we can use lib.optionals here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! And also rewrote the function to use or and hasPrefix so that it is shorter and nicer.

else
echo "Compiling $k -> $(readlink -f "$k")"
fi
${dovecot_pigeonhole}/bin/sievec -c "${config}" "$out/sieve/$k"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
${dovecot_pigeonhole}/bin/sievec -c "${config}" "$out/sieve/$k"
sievec -c "${config}" "$out/sieve/$k"

This should use nativeBuildInputs where we add dovecot_pigeonhole.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

@stale stale bot added the 2.status: stale https://github.com/NixOS/nixpkgs/blob/master/.github/STALE-BOT.md label Oct 30, 2022
@RaitoBezarius
Copy link
Member

Can you fix the conflicts please? I think this PR is ready to merge.

@stale stale bot removed the 2.status: stale https://github.com/NixOS/nixpkgs/blob/master/.github/STALE-BOT.md label Nov 2, 2023
@griff
Copy link
Contributor Author

griff commented Nov 2, 2023

@RaitoBezarius this has been ready to merge for years so I wasn't that motivated to rebase it again and again. In the meantime the patch to pigeonhole has been rejected so I also needed to move the 2 line patch into nixpkgs.

@RaitoBezarius
Copy link
Member

Apologies for the delay, I missed your message… @2xsaiko became maintainer of that module, can I let you shepherd this PR and give me the go for a merge?

@2xsaiko
Copy link
Contributor

2xsaiko commented Jan 12, 2024

Sure, I'll take a look in a bit!

Copy link
Contributor

@2xsaiko 2xsaiko left a comment

Choose a reason for hiding this comment

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

Please rebase.

Comment on lines +11 to +23
sieveToPath = text:
# Sieve scripts can start with a block comment
if isString text && hasPrefix "/*" text
then pkgs.writeText "script.sieve" text
# Paths in the store are used as is
else if types.path.check text && hasPrefix "${builtins.storeDir}/" (toString text)
then text
# Paths outside the store get written to the store
else if types.path.check text
then pkgs.writeText "script.sieve" (fileContents text)
# All other is assumed to be file content
else pkgs.writeText "script.sieve" text;
scriptType = types.coercedTo types.lines sieveToPath types.path;
Copy link
Contributor

Choose a reason for hiding this comment

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

I would just use the same mechanism as environment.etc where you can set either text, source or target options to explicitly specify what it is, instead of trying to be overly smart trying to figure out what the string means.

mkdir sieve
cd sieve
${concatStringsSep "\n" (imap1 (i: v: ''
if [ -d '${v}' ]; then
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if [ -d '${v}' ]; then
if [[ -d ${lib.escapeShellArg v} ]]; then

generally use escapeShellArg instead of simple quoting especially with user given text, and we have bash, you can use [[

@@ -1,4 +1,4 @@
{ lib, stdenv, fetchurl, dovecot, openssl }:
{ lib, stdenv, fetchpatch, fetchurl, dovecot, openssl }:
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 this is used.

Comment on lines +22 to +32
for k in $(find * -name \*.sieve); do
mkdir -p "$out/sieve/$(dirname "$k")"
cp -a "$k" "$out/sieve/$k"
if [[ ! -L "$k" ]]; then
echo "Compiling $k"
touch -m --date=@0 "$out/sieve/$k"
else
echo "Compiling $k -> $(readlink -f "$k")"
fi
sievec -c "${config}" "$out/sieve/$k"
done
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
for k in $(find * -name \*.sieve); do
mkdir -p "$out/sieve/$(dirname "$k")"
cp -a "$k" "$out/sieve/$k"
if [[ ! -L "$k" ]]; then
echo "Compiling $k"
touch -m --date=@0 "$out/sieve/$k"
else
echo "Compiling $k -> $(readlink -f "$k")"
fi
sievec -c "${config}" "$out/sieve/$k"
done
while IFS= read -r k; do
mkdir -p "$out/sieve/$(dirname "$k")"
cp -a "$k" "$out/sieve/$k"
if [[ ! -L "$k" ]]; then
echo "Compiling $k"
touch -m --date=@0 "$out/sieve/$k"
else
echo "Compiling $k -> $(readlink -f "$k")"
fi
sievec -c "${config}" "$out/sieve/$k"
done < <(find -name '*.sieve')

to handle files with spaces

else pkgs.writeText "script.sieve" text;
scriptType = types.coercedTo types.lines sieveToPath types.path;

compileSieveScripts = pkgs.buildSieveScripts {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
compileSieveScripts = pkgs.buildSieveScripts {
compiledSieveScripts = pkgs.buildSieveScripts {

Does compiling these in advance break include for non-store paths? Someone may be using that for dynamically generated rules. If it does, would they be doable another way?

@dotlambda
Copy link
Member

Can this be closed now that #275031 was merged?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet