Subject: [PATCH] home: services: Add goimapnotify service.

From: Nils Landt <[email-masked]>
Date: Sun, 3 Dec 2023 16:53:08 +0100

* gnu/home/services/mail.scm: (home-goimapnotify-configuration,
home-goimapnotify-service-type, goimapnotify-account,
goimapnotify-tls-options): New variables.
(goimapnotify-format-field, goimapnotify-serialize-field,  goimapnotify-serialize-goimapnotify-tls-options): New procedures.
* doc/guix.texi (Mail Home Services): New node.
---
 doc/guix.texi              | 139 +++++++++++++++++++++++++++
 gnu/home/services/mail.scm | 186 ++++++++++++++++++++++++++++++++++++-
 2 files changed, 324 insertions(+), 1 deletion(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 1fd2e21608..aed15685e5 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -44996,6 +44996,145 @@ Mail Home Services
 The @code{(gnu home services mail)} module provides services that help
 you set up the tools to work with emails in your home environment.

+@cindex goimapnotify
+@uref{https://gitlab.com/shackra/goimapnotify, goimapnotify} watches your
+mailbox(es) and executes a script on (new / deleted / updated) messages.
+
+Using @code{home-goimapnotify-configuration}, you can generate a config file
+for each account you want to watch (file name relative to @code{$HOME}), e.g.:
+
+@lisp
+(simple-service 'mail-imapnotify-config-examples
+                home-goimapnotify-service-type
+                (home-goimapnotify-configuration
+                  (accounts (list
+                              `(".config/goimapnotify/private-account.conf"
+                                ,(goimapnotify-account
+                                    (host "imap.example.org")
+                                    (port 993)
+                                    (tls #t)
+                                    (username "example")
+                                    (password-cmd
+                                      (file-append password-store
+                                        "/bin/pass my-private-email-account")
+                                    (on-new-mail
+                                      (file-append mbsync "/bin/mbsync private-account"))
+                                    (on-new-mail-post
+                                      (file-append mu "/bin/mu index"))
+                                    (boxes '("INBOX"))))
+                              `(".config/goimapnotify/work-account.conf"
+                                ,(goimapnotify-account
+                                    (host "imap.work.example.org")
+                                    (port 993)
+                                    (tls #t)
+                                    (username "example")
+                                    (password "12345")
+                                    (on-new-mail
+                                      (file-append mbsync "/bin/mbsync work-account"))
+                                    (on-new-mail-post
+                                      "notify-send 'New mail'")
+                                    (boxes '("INBOX"
+                                             "On Call")))))))))
+@end lisp
+
+Note: to utilize the config files, you need to start a separate goimapnotify
+process for each one.  Continuing the example above:
+@code{goimapnotify -conf "$HOME/.config/goimapnotify/private-account.conf"} and
+@code{goimapnotify -conf "$HOME/.config/goimapnotify/work-account.conf"}.
+
+@c %start of fragment
+@deftp {Data Type} home-goimapnotify-configuration
+Available @code{home-goimapnotify-configuration} fields are:
+
+@table @asis
+@item @code{accounts} (default: @code{()}) (type: list-of-goimapnotify-accounts)
+List of accounts that goimapnotify should watch.  For each account, a
+separate configuration file will be generated.
+@end table
+
+@end deftp
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} goimapnotify-account
+Available @code{goimapnotify-account} fields are:
+
+@table @asis
+@item @code{host} (type: maybe-string)
+Address of the IMAP server to connect to.
+
+@item @code{host-cmd} (type: maybe-string-or-file-like)
+An executable or script that retrieves your host from somewhere, we
+cannot pass arguments to this command from stdin.
+
+@item @code{port} (type: maybe-integer)
+Port of the IMAP server to connect to.
+
+@item @code{tls?} (type: maybe-boolean)
+
+Use TLS?
+
+@item @code{tls-options} (type: maybe-goimapnotify-tls-options)
+Option(s) for the TLS connection.  Currently, only one option is
+supported.
+
+@item @code{username} (type: maybe-string)
+Username for authentication.
+
+@item @code{username-cmd} (type: maybe-string-or-file-like)
+An executable or script that retrieves your username from
+somewhere, we cannot pass arguments to this command from stdin.
+
+@item @code{password} (type: maybe-string)
+Password for authentication.
+
+@item @code{password-cmd} (type:
+ maybe-string-or-file-like)
+An executable or script that retrieves your password from somewhere, we
+cannot pass arguments to this command from stdin.
+
+@item @code{xoauth2?}
+(type: maybe-boolean)
+You can also use xoauth2 instead of password based authentication by
+setting the xoauth2 option to true and the output of a tool which can
+provide xoauth2 encoded tokens in passwordCmd.  Examples:
+@uref{https://github.com/google/oauth2l,Google oauth2l} or
+@uref{https://github.com/harishkrupo/oauth2ms,xoauth2 fetcher for O36
+5}.
+
+@item @code{on-new-mail} (type: maybe-string-or-file-like)
+An executable or script to run when new mail has arrived.
+
+@item @code{on-new-mail-post} (type: maybe-string-or-file-like)
+An executable or script to run after onNewMail has ran.
+
+@item @code{wait} (type: maybe-integer)
+The delay in seconds before the mail syncing is triggered.
+
+@item @code{boxes} (type: maybe-list-of-strings)
+Mailboxes to watch.
+
+@end table
+
+@end deftp
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} goimapnotify-tls-options
+Available @code{goimapnotify-tls-options} fields are:
+
+@table @asis
+@item @code{reject-unauthorized?} (type: maybe-boolean)
+Skip verifying CA server identify?
+
+@end table
+
+@end deftp
+@c %end of fragment
+
 @cindex msmtp
 @uref{https://marlam.de/msmtp, MSMTP} is a @acronym{SMTP, Simple Mail
 Transfer Protocol} client.  It sends mail to a predefined SMTP server
diff --git a/gnu/home/services/mail.scm b/gnu/home/services/mail.scm
index 5445c82c67..60042be8d4 100644
--- a/gnu/home/services/mail.scm
+++ b/gnu/home/services/mail.scm
@@ -18,15 +18,45 @@

 (define-module (gnu home services mail)
   #:use-module (guix gexp)
+  #:use-module (guix records)
   #:use-module (gnu services)
   #:use-module (gnu services configuration)
   #:use-module (gnu home services)
   #:use-module (gnu home services shepherd)
+  #:use-module (gnu home services utils)
   #:use-module (gnu packages mail)
+  #:use-module (gnu packages guile)
+  #:use-module (ice-9 match)
   #:use-module (ice-9 string-fun)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-26)
-  #:export (home-msmtp-configuration
+  #:use-module (srfi srfi-171)
+  #:export (home-goimapnotify-configuration
+            home-goimapnotify-configuration-fields
+            home-goimapnotify-configuration?
+            home-goimapnotify-configuration-accounts
+            home-goimapnotify-service-type
+            goimapnotify-account
+            goimapnotify-account-fields
+            goimapnotify-account-host
+            goimapnotify-account-host-cmd
+            goimapnotify-account-port
+            goimapnotify-account-tls
+            goimapnotify-account-tls-options
+            goimapnotify-account-username
+            goimapnotify-account-username-cmd
+            goimapnotify-account-password
+            goimapnotify-account-password-cmd
+            goimapnotify-account-xoauth2
+            goimapnotify-account-on-new-mail
+            goimapnotify-account-on-new-mail-post
+            goimapnotify-account-wait
+            goimapnotify-account-boxes
+            goimapnotify-tls-options
+            goimapnotify-tls-options-fields
+            goimapnotify-tls-options-reject-unauthorized
+
+            home-msmtp-configuration
             home-msmtp-configuration?
             home-msmtp-configuration-defaults
             home-msmtp-configuration-accounts
@@ -220,3 +250,157 @@ (define home-msmtp-service-type
                 (description "Configure msmtp, a simple
 @acronym{SMTP, Simple Mail Transfer Protocol} client that can relay email
 to SMTP servers.")))
+
+; Configuration for goimapnotify from (gnu packages mail)
+
+(define-maybe string
+  (prefix goimapnotify-))
+(define-maybe integer
+  (prefix goimapnotify-))
+(define-maybe boolean
+  (prefix goimapnotify-))
+(define-maybe list-of-strings
+  (prefix goimapnotify-))
+(define-maybe string-or-file-like
+  (prefix goimapnotify-))
+
+(define (string-or-file-like? value)
+  (or (string? value)
+      (file-like? value)))
+
+(define (goimapnotify-format-field field-name)
+  (object->camel-case-string
+    (string-trim-right
+      (object->string field-name)
+      #\?)))
+
+(define (goimapnotify-serialize-field field-name value)
+  "This is converted to JSON later, so we don't return a string here"
+  #~(#$(goimapnotify-format-field field-name) . #$value))
+
+(define goimapnotify-serialize-string goimapnotify-serialize-field)
+(define goimapnotify-serialize-string-or-file-like goimapnotify-serialize-string)
+(define goimapnotify-serialize-string goimapnotify-serialize-field)
+(define goimapnotify-serialize-integer goimapnotify-serialize-field)
+(define goimapnotify-serialize-boolean goimapnotify-serialize-field)
+(define (goimapnotify-serialize-list-of-strings field-name value)
+  (goimapnotify-serialize-field field-name (list->array 1 value)))
+
+(define-configuration goimapnotify-tls-options
+                      (reject-unauthorized?
+                        maybe-boolean
+                        "Skip verifying CA server identify?")
+                      (prefix goimapnotify-))
+
+(define-maybe goimapnotify-tls-options
+  (prefix goimapnotify-))
+
+; See https://gitlab.com/shackra/goimapnotify/-/blob/423f0e65350f7e042edbd2c373f252c5a0d31dc2/config.go#L46-62
+(define-configuration goimapnotify-account
+                      (host
+                        maybe-string
+                        "Address of the IMAP server to connect to.")
+                      (host-cmd
+                        maybe-string-or-file-like
+                        "An executable or script that retrieves your host from somewhere,
+                        we cannot pass arguments to this command from stdin.")
+                      (port
+                        maybe-integer
+                        "Port of the IMAP server to connect to.")
+                      (tls?
+                        maybe-boolean
+                        "Use TLS?")
+                      (tls-options
+                        maybe-goimapnotify-tls-options
+                        "Option(s) for the TLS connection. Currently, only one option is
+                        supported.")
+                      (username
+                        maybe-string
+                        "Username for authentication.")
+                      (username-cmd
+                        maybe-string-or-file-like
+                        "An executable or script that retrieves your username from
+                        somewhere, we cannot pass arguments to this command from stdin.")
+                      (password
+                        maybe-string
+                        "Password for authentication.")
+                      (password-cmd
+                        maybe-string-or-file-like
+                        "An executable or script that retrieves your password from
+                        somewhere, we cannot pass arguments to this command from stdin.")
+                      (xoauth2?
+                        maybe-boolean
+                        "You can also use xoauth2 instead of password based authentication
+                        by setting the xoauth2 option to true and the output of a tool
+                        which can provide xoauth2 encoded tokens in passwordCmd.
+                        Examples: @url{https://github.com/google/oauth2l, Google oauth2l}
+                        or
+                        @url{https://github.com/harishkrupo/oauth2ms, xoauth2 fetcher for O365}.")
+                      (on-new-mail
+                        maybe-string-or-file-like
+                        "An executable or script to run when new mail has arrived.")
+                      (on-new-mail-post
+                        maybe-string-or-file-like
+                        "An executable or script to run after on-new-mail has ran.")
+                      (wait
+                        maybe-integer
+                        "The delay in seconds before the mail syncing is triggered.")
+                      (boxes
+                        maybe-list-of-strings
+                        "Mailboxes to watch.")
+                      (prefix goimapnotify-))
+
+(define list-of-goimapnotify-accounts?
+  (list-of (match-lambda (? string?) ($ <goimapnotify-account>))))
+
+(define-configuration/no-serialization home-goimapnotify-configuration
+                                       (accounts
+                                         (list-of-goimapnotify-accounts '())
+                                         "List of accounts that goimapnotify should watch.
+                                         For each account, a separate configuration file
+                                         will be generated."))
+
+(define (home-goimapnotify-extension old-config extensions)
+  (match-record old-config <home-goimapnotify-configuration>
+                (accounts)
+                (home-goimapnotify-configuration
+                  (inherit old-config)
+                  (accounts (append accounts
+                                    (append-map
+                                      home-goimapnotify-configuration-accounts
+                                      extensions))))))
+
+(define (goimapnotify-files config)
+  (match-record config <home-goimapnotify-configuration>
+                (accounts)
+    (map (match-lambda
+           ((path account)
+            (list path
+                  (computed-file
+                   (string-append "mail-imapnotify-config-"
+                                  (goimapnotify-account-host account))
+                   (with-extensions (list guile-json-4)
+                     #~(begin
+                         (use-modules (json builder))
+                         (with-output-to-file #$output
+                           (lambda ()
+                             (scm->json
+                              '(#$@(list-transduce
+                                     (base-transducer account)
+                                     rcons
+                                     goimapnotify-account-fields))
+                              #:pretty #t)))))))))
+         accounts)))
+
+(define home-goimapnotify-service-type
+  (service-type
+    (name 'home-goimapnotify-service)
+    (extensions
+      (list (service-extension
+              home-files-service-type
+              goimapnotify-files)))
+    (compose identity)
+    (extend home-goimapnotify-extension)
+    (default-value (home-goimapnotify-configuration))
+    (description "Configure goimapnotify to execute scripts on IMAP
+                 mailbox changes.")))

base-commit: 513bf164592e2b44e3e556cc5099a19bd6977790
--
2.41.0