Subject: [PATCH v6 2/3] services: dovecot: Provide plugins through a /gnu/store directory.

From: Alexey Abramov <[email-masked]>
Date: Thu, 21 Nov 2024 12:25:57 +0000

* gnu/services/mail.scm (package-list?, serialize-package-list):
* gnu/services/mail.scm (dovecot-configuration)[extensions]: New field. The field
lets you provide a list of dovecot plugins that need to be available during
the runtime. A union of the set of modules will be created on the activation time.
* gnu/services/mail.scm (opaque-dovecot-configuration)[extensions]: Likewise.
* gnu/services/mail.scm (make-dovecot-moduledir): New function.
* gnu/services/mail.scm (%dovecot-activation): Add step to compute a set of
modules, and provide them over the shared link at /usr/lib/dovecot.
* doc/guix.texi (Mail Services)[extension]: Add documentation. Clarify
the purpose and usage of the extensions parameter. Add an example
showing how to enable Sieve filtering using dovecot-pigeonhole. Better
explain the module directory structure and requirements.
---
 doc/guix.texi         | 19 +++++++++++++++++++
 gnu/services/mail.scm | 44 +++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 61 insertions(+), 2 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 1c39628ffaf..8ab628af0a4 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -26629,6 +26629,25 @@ Available @code{dovecot-configuration} fields are:
 The dovecot package.
 @end deftypevr
 
+@deftypevr {@code{dovecot-configuration} parameter} package-list extensions
+A list of additional Dovecot plugin packages to make available at runtime. During
+service activation, the @file{lib/dovecot} directory from each specified package
+is combined with Dovecot's core modules into a unified module directory.
+
+For example, to enable Sieve filtering:
+
+@lisp
+(extensions (list dovecot-pigeonhole))
+@end lisp
+
+Each package in the list must provide its modules at @file{lib/dovecot},
+as this is where @code{make-dovecot-moduledir} expects to find its
+extensions. The service combines these directories to create a unified
+module structure.
+
+The default value is an empty list, providing only core Dovecot functionality.
+@end deftypevr
+
 @deftypevr {@code{dovecot-configuration} parameter} comma-separated-string-list listen
 A list of IPs or hosts where to listen for connections.  @samp{*}
 listens on all IPv4 interfaces, @samp{::} listens on all IPv6
diff --git a/gnu/services/mail.scm b/gnu/services/mail.scm
index 18b482272c7..ce0a37dc5f9 100644
--- a/gnu/services/mail.scm
+++ b/gnu/services/mail.scm
@@ -521,11 +521,21 @@ (define (serialize-list-of-namespace-configuration field-name val)
               (serialize-namespace-configuration field-name val))
             val))
 
+(define (package-list? val)
+  (and (list? val) (and-map package? val)))
+(define (serialize-package-list field-name val)
+  #f)
+
 (define-configuration dovecot-configuration
   (dovecot
    (file-like dovecot)
    "The dovecot package.")
 
+  (extensions
+   (package-list '())
+   "Plugins and extensions to the Dovecot package. Specify a list of dovecot
+plugins that needs to be available for dovecot and its modules.")
+
   (listen
    (comma-separated-string-list '("*" "::"))
    "A list of IPs or hosts where to listen in for connections.  @samp{*}
@@ -1109,7 +1119,7 @@ (define-configuration dovecot-configuration
 @item fcntl
 Use this if possible.  Works with NFS too if lockd is used.
 @item flock
-May not exist in all systems.  Doesn't work with NFS. 
+May not exist in all systems.  Doesn't work with NFS.
 @item lockf
 May not exist in all systems.  Doesn't work with NFS.
 @end table
@@ -1500,6 +1510,11 @@ (define-configuration opaque-dovecot-configuration
    (file-like dovecot)
    "The dovecot package.")
 
+  (extensions
+   (package-list '())
+   "Plugins and extensions to the Dovecot package. Specify a list of dovecot
+plugins that needs to be available for dovecot and its modules.")
+
   (string
    (string (configuration-missing-field 'opaque-dovecot-configuration
                                         'string))
@@ -1525,6 +1540,21 @@ (define %dovecot-accounts
          (home-directory "/var/empty")
          (shell (file-append shadow "/sbin/nologin")))))
 
+(define (make-dovecot-moduledir packages)
+  "Return a computed file containing a union of Dovecot module directories from PACKAGES.
+Each package's '/lib/dovecot' directory is combined into a single location."
+  ;; Create a union of the set of modules and dovecot itself.
+  (with-imported-modules '((guix build union))
+    (computed-file
+     "dovecot-moduledir"
+     #~(begin
+         (use-modules (guix build union) (srfi srfi-26))
+
+         (union-build #$output
+                      (map (cut string-append <>
+                                "/lib/dovecot")
+                           (list #$@packages)))))))
+
 (define (%dovecot-activation config)
   ;; Activation gexp.
   (let ((config-str
@@ -1535,7 +1565,15 @@ (define (%dovecot-activation config)
            (with-output-to-string
              (lambda ()
                (serialize-configuration config
-                                        dovecot-configuration-fields)))))))
+                                        dovecot-configuration-fields))))))
+        (moduledir-directory
+         (cond
+          ((opaque-dovecot-configuration? config)
+           (make-dovecot-moduledir (cons* (opaque-dovecot-configuration-dovecot config)
+                                          (opaque-dovecot-configuration-extensions config))))
+          (else
+           (make-dovecot-moduledir (cons* (dovecot-configuration-dovecot config)
+                                          (dovecot-configuration-extensions config)))))))
     (with-imported-modules (source-module-closure '((gnu build activation)))
       #~(begin
           (use-modules (guix build utils) (gnu build activation))
@@ -1586,6 +1624,8 @@ (define* (create-self-signed-certificate-if-absent
             (copy-file #$(plain-file "dovecot.conf" config-str)
                        "/etc/dovecot/dovecot.conf")
             (mkdir-p/perms "/etc/dovecot/private" user #o700)
+            (mkdir-p "/usr/lib")
+            (switch-symlinks "/usr/lib/dovecot" #$moduledir-directory)
             (create-self-signed-certificate-if-absent
              #:private-key "/etc/dovecot/private/default.pem"
              #:public-key "/etc/dovecot/default.pem"
-- 
2.46.0