[PATCH] home: services: Add goimapnotify service.

  • Open
  • quality assurance status badge
Details
3 participants
  • Bruno Victal
  • Nils Landt
  • Ricardo Wurmus
Owner
unassigned
Submitted by
Nils Landt
Severity
normal
N
N
Nils Landt wrote on 15 Oct 2023 16:01
(address . guix-patches@gnu.org)(name . Nils Landt)(address . nils.landt@nlsoft.de)
c9d8798670448a18779e3c24b9b8a88902942936.1697378478.git.nils@landt.email
From: Nils Landt <nils.landt@nlsoft.de>

* 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.
---
This patch adds a home service for generating goimapnotify JSON
configuration files.

I was unable to get generate-documentation working with
sub-documentation, so the configurations are documented separately.

doc/guix.texi | 209 +++++++++++++++++++++++++++------
gnu/home/services/mail.scm | 234 ++++++++++++++++++++++++++++++++++++-
2 files changed, 406 insertions(+), 37 deletions(-)

Toggle diff (536 lines)
diff --git a/doc/guix.texi b/doc/guix.texi
index 3517c95251..fba13d4a43 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -44703,25 +44703,162 @@ Sound Home Services

@node Mail Home Services
@subsection 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 "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
that takes care of proper delivery.
-
+
The service reference is given below.
-
+
@defvar home-msmtp-service-type
This is the service type for @command{msmtp}. Its value must be a
@code{home-msmtp-configuration}, as shown below. It provides the
@file{~/.config/msmtp/config} file.
-
+
As an example, here is how you would configure @code{msmtp} for a single
account:
-
+
@lisp
(service home-msmtp-service-type
(home-msmtp-configuration
@@ -44739,101 +44876,101 @@ Mail Home Services
@end defvar

@c %start of fragment
-
+
@deftp {Data Type} home-msmtp-configuration
Available @code{home-msmtp-configuration} fields are:
-
+
@table @asis
@item @code{defaults} (type: msmtp-configuration)
The configuration that will be set as default for all accounts.
-
+
@item @code{accounts} (default: @code{'()}) (type: list-of-msmtp-accounts)
A list of @code{msmtp-account} records which contain information about
all your accounts.
-
+
@item @code{default-account} (type: maybe-string)
Set the default account.
-
+
@item @code{extra-content} (default: @code{""}) (type: string)
Extra content appended as-is to the configuration file. Run
@command{man msmtp} for more information about the configuration file
format.
-
+
@end table
-
+
@end deftp
-
+
@c %end of fragment
-
+
@c %start of fragment
-
+
@deftp {Data Type} msmtp-account
Available @code{msmtp-account} fields are:
-
+
@table @asis
@item @code{name} (type: string)
The unique name of the account.
-
+
@item @code{configuration} (type: msmtp-configuration)
The configuration for this given account.
-
+
@end table
-
+
@end deftp
-
+
@c %end of fragment

@c %start of fragment
-
+
@deftp {Data Type} msmtp-configuration
Available @code{msmtp-configuration} fields are:
-
+
@table @asis
@item @code{auth?} (type: maybe-boolean)
Enable or disable authentication.
-
+
@item @code{tls?} (type: maybe-boolean)
Enable or disable TLS (also known as SSL) for secured connections.
-
+
@item @code{tls-starttls?} (type: maybe-boolean)
Choose the TLS variant: start TLS from within the session (‘on’,
default), or tunnel the session through TLS (‘off’).
-
+
@item @code{tls-trust-file} (type: maybe-string)
Activate server certificate verification using a list of trusted
Certification Authorities (CAs).
-
+
@item @code{log-file} (type: maybe-string)
Enable logging to the specified file. An empty argument disables
logging. The file name ‘-’ directs the log information to standard
output.
-
+
@item @code{host} (type: maybe-string)
The SMTP server to send the mail to.
-
+
@item @code{port} (type: maybe-integer)
The port that the SMTP server listens on. The default is 25 ("smtp"),
unless TLS without STARTTLS is used, in which case it is 465 ("smtps").
-
+
@item @code{user} (type: maybe-string)
Set the user name for authentication.
-
+
@item @code{from} (type: maybe-string)
Set the envelope-from address.
-
+
@item @code{password-eval} (type: maybe-string)
Set the password for authentication to the output (stdout) of the
command cmd.
-
+
@item @code{extra-content} (default: @code{""}) (type: string)
Extra content appended as-is to the configuration block. Run
@command{man msmtp} for more information about the configuration file
format.
-
+
@end table
-
+
@end deftp
-
+
@c %end of fragment

@node Messaging Home Services
diff --git a/gnu/home/services/mail.scm b/gnu/home/services/mail.scm
index 5445c82c67..923867ca66 100644
--- a/gnu/home/services/mail.scm
+++ b/gnu/home/services/mail.scm
@@ -18,15 +18,44 @@

(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
+ #: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 +249,206 @@ (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)
+(define-maybe integer)
+(define-maybe boolean)
+(define-maybe list-of-strings)
+(define-maybe string-or-file-like)
+
+(define (string-or-file-like? value)
+ (or (string? value)
+ (file-like? value)))
+
+(define (goimapnotify-format-field field-name)
+ (object->camel-case-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-or-file-like field-name value)
+ (goimapnotify-serialize-string field-name value))
+
+(define (goimapnotify-maybe-serialize field-name value serialization-function)
+ (if (maybe-value-set? value)
+ (serialization-function field-name value)
+ ""))
+
+(define (goimapnotify-serialize-maybe-string-or-file-like field-name value)
+ (goimapnotify-maybe-serialize field-name value
+ goimapnotify-serialize-string-or-file-like))
+
+(define goimapnotify-serialize-string goimapnotify-serialize-field)
+(define (goimapnotify-serialize-maybe-string field-name value)
+ (goimapnotify-maybe-serialize field-name value goimapnotify-serialize-string))
+
+(define (goimapnotify-serialize-maybe-integer field-name value)
+ (goimapnotify-maybe-serialize field-name value goimapnotify-serialize-integer))
+(define goimapnotify-serialize-integer goimapnotify-serialize-field)
+
+(define (goimapnotify-serialize-maybe-boolean field-name value)
+ (goimapnotify-maybe-serialize field-name value goimapnotify-serialize-boolean))
+(define goimapnotify-serialize-boolean goimapnotify-serialize-field)
+
+(define (goimapnotify-serialize-maybe-list-of-strings field-name value)
+ (goimapnotify-maybe-serialize field-name value goimapnotify-serialize-list-of-strings))
+(define (goimapnotify-serialize-list-of-strings field-name value)
+ (goimapnotify-serialize-field field-name (list->array 1 value)))
+
+(define (goimapnotify-serialize-maybe-goimapnotify-tls-options field-name config)
+ (goimapnotify-maybe-serialize field-name config
+ goimapnotify-serialize-goimapnotify-tls-options))
+
+(define (goimapnotify-serialize-goimapnotify-tls-options field-name config)
+ (goimapnotify-serialize-field
+ field-name
+ (prepare-configuration-for-json config goimapnotify-tls-options-fields)))
+
+(define (prepare-configuration-for-json config fields)
+ "Convert the configuration to the format expected by guile-json.
+ Unset maybe-values do not appear in the configuration file."
+ (filter
+ (lambda (val)
+ (not (unspecified? val)))
+ (map
+ (lambda (field)
+ (let ((value ((configuration-field-getter field) config)))
+ (if (maybe-value-set? value)
+ ((configuration-field-serializer field)
+ (configuration-field-name field)
+ value)
+ *unspecified*)))
+ fields)))
+
+(define-configuration goimapnotify-tls-options
+ (reject-unauthorized
+ (maybe-boolean)
+ "Skip verifying CA server identify?")
+ (prefix goimapnotify-))
+
+(define-maybe goimapnotify-tls-options)
+
+; See https://gitlab.com/shackra/goimapnotify/-/blob/master/config.go?ref_type=heads#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 onNewMail 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? lst)
+ "List is in the form of '((file-name file-like))"
+ (every (lambda (element)
+ (match element
+ ((string ($ <goimapnotify-account>))
+ #t)
+ (_ #f)))
+ lst))
+
+(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)
+ (define* (account->json account-config-and-path)
+ (match
+ account-config-and-path
+ ((path account-config)
+ (let ((prepared-config
+ (prepare-configuration-for-json
+ account-config
+ goimapnotify-account-fields)))
+ `((,path
+ ,(computed-file
+ (string-append
+ "mail-imapnotify-config-"
+ (goimapnotify-account-host acco
This message was truncated. Download the full message here.
R
R
Ricardo Wurmus wrote on 2 Nov 2023 09:55
(address . 66557@debbugs.gnu.org)
87h6m4tr15.fsf@elephly.net
Hi Nils,

thank you for this service! I had been looking for a goimapnotify
service just a few days ago, so this will definitely come in handy.

I don’t have the time for a comprehensive review, but I’ll comment on a
few things that stuck out to me.

Toggle quote (4 lines)
> + (password-cmd "pass my-private-email-account")
> + (on-new-mail
> + (file-append mbsync "/bin/mbsync private-account"))

It seems wrong to me to compose a command as a single string. Usually
we separate the executable from the arguments. People who want to run
the command in a shell can still do that by using “/bin/sh” “-c”
“command string”.

So I think it would be better to let these fields accept command lists.
FILE-APPEND should only join the package value with the file name of the
executable, but not include any arguments.

I suppose these values end up in the generated configuration file as
plain strings anyway, so perhaps it doesn’t matter much. In that case
please also use FILE-APPEND to embed a reference to PASS.

Toggle quote (4 lines)
> +(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))

Could this be (cons (goimapnotify-format-field field-name) value)
instead? I don’t think we need this wrapping and unwrapping with G-exp
syntax.

Toggle quote (16 lines)
> +(define (prepare-configuration-for-json config fields)
> + "Convert the configuration to the format expected by guile-json.
> + Unset maybe-values do not appear in the configuration file."
> + (filter
> + (lambda (val)
> + (not (unspecified? val)))
> + (map
> + (lambda (field)
> + (let ((value ((configuration-field-getter field) config)))
> + (if (maybe-value-set? value)
> + ((configuration-field-serializer field)
> + (configuration-field-name field)
> + value)
> + *unspecified*)))
> + fields)))

This looks a little too convoluted to me. It’s the IF with the
*UNSPECIFIED* as the second branch (followed by the filter) that doesn’t
strike me as nice. Perhaps you simplify this with a fold?

In any case, the filter predicate could be (negate unspecified?) instead
of the lambda expression.

Toggle quote (2 lines)
Please don’t use “master” here, because it’s a moving target, so the
line anchors will become out of date. Please use an arbitrary recent
commit instead. Since this is a line comment please use “;;” instead of
“;” (which is used for comments in the margin).

Toggle quote (9 lines)
> +(define (list-of-goimapnotify-accounts? lst)
> + "List is in the form of '((file-name file-like))"
> + (every (lambda (element)
> + (match element
> + ((string ($ <goimapnotify-account>))
> + #t)
> + (_ #f)))
> + lst))

The indentation for the MATCH clauses is too deep. There are other
instances of oddly deep indentation, such as the body of
“(define-configuration/no-serialization home-goimapnotify-configuration
…)”. Are you using Emacs?

Since you’re throwing away the first element you can reduce this to:

(every (compose goimapnotify-account? second) lst)

I haven’t looked at the documentation much, but the capitalization of
“Stdin” and the spurious changes to seemingly empty lines in existing
documentation stood out to me. It would be better to undo the changes
to unrelated documentation in the same file.

--
Ricardo
B
B
Bruno Victal wrote on 20 Nov 2023 18:16
(name . Nils Landt)(address . nils@landt.email)(address . 66557@debbugs.gnu.org)
d279a547-7f5c-4244-8f78-ad0fb98cbe2c@makinata.eu
Hi Nils,

On 2023-10-15 15:01, Nils Landt wrote:
Toggle quote (3 lines)
> This patch adds a home service for generating goimapnotify JSON
> configuration files.

[…]

Toggle quote (37 lines)
> +@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 "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"}.

Not a goimapnotify user but this looks like a daemon application.
I don't like this design much, I think goimapnotify should be launched and managed
using shepherd instead of simply exposing these files to the user.

Toggle quote (4 lines)
> +@item @code{tls} (type: maybe-boolean)
> +
> +Use TLS?

Boolean fields are generally suffixed with '?'. (e.g. tls?)

Toggle quote (11 lines)
> +@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.

I'd prefer to write it as “stdin” (lowercase) or expand it to “standard input”.

Toggle quote (9 lines)
> +@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}.

Same remark as 'tls' option.

Toggle quote (6 lines)
> +@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.

“An executable or script to run after @code{on-new-mail} has ran.”

Toggle quote (4 lines)
> +@table @asis
> +@item @code{reject-unauthorized} (type: maybe-boolean)
> +Skip verifying CA server identify?

Same remark as 'tls' option.

Toggle quote (153 lines)
> @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
> that takes care of proper delivery.
> -
> +
> The service reference is given below.
> -
> +
> @defvar home-msmtp-service-type
> This is the service type for @command{msmtp}. Its value must be a
> @code{home-msmtp-configuration}, as shown below. It provides the
> @file{~/.config/msmtp/config} file.
> -
> +
> As an example, here is how you would configure @code{msmtp} for a single
> account:
> -
> +
> @lisp
> (service home-msmtp-service-type
> (home-msmtp-configuration
> @@ -44739,101 +44876,101 @@ Mail Home Services
> @end defvar
>
> @c %start of fragment
> -
> +
> @deftp {Data Type} home-msmtp-configuration
> Available @code{home-msmtp-configuration} fields are:
> -
> +
> @table @asis
> @item @code{defaults} (type: msmtp-configuration)
> The configuration that will be set as default for all accounts.
> -
> +
> @item @code{accounts} (default: @code{'()}) (type: list-of-msmtp-accounts)
> A list of @code{msmtp-account} records which contain information about
> all your accounts.
> -
> +
> @item @code{default-account} (type: maybe-string)
> Set the default account.
> -
> +
> @item @code{extra-content} (default: @code{""}) (type: string)
> Extra content appended as-is to the configuration file. Run
> @command{man msmtp} for more information about the configuration file
> format.
> -
> +
> @end table
> -
> +
> @end deftp
> -
> +
> @c %end of fragment
> -
> +
> @c %start of fragment
> -
> +
> @deftp {Data Type} msmtp-account
> Available @code{msmtp-account} fields are:
> -
> +
> @table @asis
> @item @code{name} (type: string)
> The unique name of the account.
> -
> +
> @item @code{configuration} (type: msmtp-configuration)
> The configuration for this given account.
> -
> +
> @end table
> -
> +
> @end deftp
> -
> +
> @c %end of fragment
>
> @c %start of fragment
> -
> +
> @deftp {Data Type} msmtp-configuration
> Available @code{msmtp-configuration} fields are:
> -
> +
> @table @asis
> @item @code{auth?} (type: maybe-boolean)
> Enable or disable authentication.
> -
> +
> @item @code{tls?} (type: maybe-boolean)
> Enable or disable TLS (also known as SSL) for secured connections.
> -
> +
> @item @code{tls-starttls?} (type: maybe-boolean)
> Choose the TLS variant: start TLS from within the session (‘on’,
> default), or tunnel the session through TLS (‘off’).
> -
> +
> @item @code{tls-trust-file} (type: maybe-string)
> Activate server certificate verification using a list of trusted
> Certification Authorities (CAs).
> -
> +
> @item @code{log-file} (type: maybe-string)
> Enable logging to the specified file. An empty argument disables
> logging. The file name ‘-’ directs the log information to standard
> output.
> -
> +
> @item @code{host} (type: maybe-string)
> The SMTP server to send the mail to.
> -
> +
> @item @code{port} (type: maybe-integer)
> The port that the SMTP server listens on. The default is 25 ("smtp"),
> unless TLS without STARTTLS is used, in which case it is 465 ("smtps").
> -
> +
> @item @code{user} (type: maybe-string)
> Set the user name for authentication.
> -
> +
> @item @code{from} (type: maybe-string)
> Set the envelope-from address.
> -
> +
> @item @code{password-eval} (type: maybe-string)
> Set the password for authentication to the output (stdout) of the
> command cmd.
> -
> +
> @item @code{extra-content} (default: @code{""}) (type: string)
> Extra content appended as-is to the configuration block. Run
> @command{man msmtp} for more information about the configuration file
> format.
> -
> +
> @end table
> -
> +
> @end deftp
> -
> +
> @c %end of fragment

These are unrelated changes, can you drop these hunks?

Toggle quote (30 lines)
> +(define (goimapnotify-maybe-serialize field-name value serialization-function)
> + (if (maybe-value-set? value)
> + (serialization-function field-name value)
> + ""))
> +
> +(define (goimapnotify-serialize-maybe-string-or-file-like field-name value)
> + (goimapnotify-maybe-serialize field-name value
> + goimapnotify-serialize-string-or-file-like))
> +
> +(define goimapnotify-serialize-string goimapnotify-serialize-field)
> +(define (goimapnotify-serialize-maybe-string field-name value)
> + (goimapnotify-maybe-serialize field-name value goimapnotify-serialize-string))
> +
> +(define (goimapnotify-serialize-maybe-integer field-name value)
> + (goimapnotify-maybe-serialize field-name value goimapnotify-serialize-integer))
> +(define goimapnotify-serialize-integer goimapnotify-serialize-field)
> +
> +(define (goimapnotify-serialize-maybe-boolean field-name value)
> + (goimapnotify-maybe-serialize field-name value goimapnotify-serialize-boolean))
> +(define goimapnotify-serialize-boolean goimapnotify-serialize-field)
> +
> +(define (goimapnotify-serialize-maybe-list-of-strings field-name value)
> + (goimapnotify-maybe-serialize field-name value goimapnotify-serialize-list-of-strings))
> +(define (goimapnotify-serialize-list-of-strings field-name value)
> + (goimapnotify-serialize-field field-name (list->array 1 value)))
> +
> +(define (goimapnotify-serialize-maybe-goimapnotify-tls-options field-name config)
> + (goimapnotify-maybe-serialize field-name config
> + goimapnotify-serialize-goimapnotify-tls-options))

This isn't needed, fields whose maybe-values are unset don't call the
serializing procedures.

Toggle quote (21 lines)
> +(define (goimapnotify-serialize-goimapnotify-tls-options field-name config)
> + (goimapnotify-serialize-field
> + field-name
> + (prepare-configuration-for-json config goimapnotify-tls-options-fields)))
> +
> +(define (prepare-configuration-for-json config fields)
> + "Convert the configuration to the format expected by guile-json.
> + Unset maybe-values do not appear in the configuration file."
> + (filter
> + (lambda (val)
> + (not (unspecified? val)))
> + (map
> + (lambda (field)
> + (let ((value ((configuration-field-getter field) config)))
> + (if (maybe-value-set? value)
> + ((configuration-field-serializer field)
> + (configuration-field-name field)
> + value)
> + *unspecified*)))
> + fields)))

You can use 'serialize-configuration' instead which accounts for the unset maybe-values.

Toggle quote (64 lines)
> +
> +(define-configuration goimapnotify-tls-options
> + (reject-unauthorized
> + (maybe-boolean)
> + "Skip verifying CA server identify?")
> + (prefix goimapnotify-))
> +
> +(define-maybe goimapnotify-tls-options)
> +
> +; See https://gitlab.com/shackra/goimapnotify/-/blob/master/config.go?ref_type=heads#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 onNewMail 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-))

You can omit the parentheses around the field-type.

Toggle quote (9 lines)
> +(define (list-of-goimapnotify-accounts? lst)
> + "List is in the form of '((file-name file-like))"
> + (every (lambda (element)
> + (match element
> + ((string ($ <goimapnotify-account>))
> + #t)
> + (_ #f)))
> + lst))

You can replace this with:
Toggle snippet (4 lines)
(define list-of-goimapnotify-accounts?
(list-of goimapnotify-account?))

Toggle quote (17 lines)
> +(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))))))

This looks misindented, the .dir-locals file automatically handles
this if you are using emacs.

Toggle quote (13 lines)
> +
> +(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.")))

Stylistically, I'd indent this as:

Toggle snippet (7 lines)
(define home-goimapnotify-service-type
(service-type
(name 'home-goimapnotify-service)
(extensions …


My 2¢!

--
Furthermore, I consider that nonfree software must be eradicated.

Cheers,
Bruno.
N
N
Nils Landt wrote on 21 Nov 2023 16:25
(name . Bruno Victal)(address . mirai@makinata.eu)(address . 66557@debbugs.gnu.org)
656336790.399020.1700580321203@office.mailbox.org
Thank you for the feedback, I'll work on implementing it, hopefully on the weekend.

I've already added a few comments / questions below though.

Toggle quote (12 lines)
> Bruno Victal <mirai@makinata.eu> hat am 20.11.2023 18:16 CET ls,
>
> On 2023-10-15 15:01, Nils Landt wrote:
> > This patch adds a home service for generating goimapnotify JSON
> > configuration files.
>
> […]
>
> Not a goimapnotify user but this looks like a daemon application.
> I don't like this design much, I think goimapnotify should be launched and managed
> using shepherd instead of simply exposing these files to the user.

These options can not be given as command line options, they need to be in a config file.
Personally I don't use shepherd, so I won't be contributing shepherd services :)

Toggle quote (6 lines)
> > +@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.
>
> I'd prefer to write it as “stdin” (lowercase) or expand it to “standard input”.

I think this a good change, and I'm completely fine making it. I just want to mention that I copied these docstrings directly from the project's readme. But I see no reason why the author would capitalize it.

Toggle quote (15 lines)
> > +(define (list-of-goimapnotify-accounts? lst)
> > + "List is in the form of '((file-name file-like))"
> > + (every (lambda (element)
> > + (match element
> > + ((string ($ <goimapnotify-account>))
> > + #t)
> > + (_ #f)))
> > + lst))
>
> You can replace this with:
> --8<---------------cut here---------------start------------->8---
> (define list-of-goimapnotify-accounts?
> (list-of goimapnotify-account?))
> --8<---------------cut here---------------end--------------->8---

Wouldn't that fail because it expects '(goimapnotify-account goimapnotify-account[...])?

Toggle quote (3 lines)
> This looks misindented, the .dir-locals file automatically handles
> this if you are using emacs.

I'm using vim. The code is indented with autoindent, as mentioned on https://guix.gnu.org/manual/devel/en/html_node/Vim-and-NeoVim.html. If this doesn't match the expected indentation as implemented in Emacs, I'd be grateful if you could indent this file before merging.
N
N
Nils Landt wrote on 21 Nov 2023 16:30
(name . rekado@elephly.net)(address . rekado@elephly.net)(name . 66557@debbugs.gnu.org)(address . 66557@debbugs.gnu.org)
1205571599.400022.1700580618727@office.mailbox.org
Thank you for your feedback Ricardo. I did not ignore you, but I did not receive a notification email, so I didn't see your message until I received a notification for Brunos reply and checked the website.

I'll (try to) incorporate your feedback as well.
N
N
Nils Landt wrote on 26 Nov 2023 11:31
[PATCH] home: services: Add goimapnotify service.
(address . 66557@debbugs.gnu.org)(name . Nils Landt)(address . nils.landt@nlsoft.de)
dd6cf27935471a6b96dc319fde798f97547f6fa2.1700994663.git.nils@landt.email
From: Nils Landt <nils.landt@nlsoft.de>

* 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 | 232 ++++++++++++++++++++++++++++++++++++-
2 files changed, 370 insertions(+), 1 deletion(-)

Toggle diff (409 lines)
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..b86a787963 100644
--- a/gnu/home/services/mail.scm
+++ b/gnu/home/services/mail.scm
@@ -18,15 +18,44 @@
(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
+ #: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 +249,204 @@ (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)
+(define-maybe integer)
+(define-maybe boolean)
+(define-maybe list-of-strings)
+(define-maybe string-or-file-like)
+
+(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-or-file-like field-name value)
+ (goimapnotify-serialize-string field-name value))
+
+(define (goimapnotify-maybe-serialize field-name value serialization-function)
+ (if (maybe-value-set? value)
+ (serialization-function field-name value)
+ ""))
+
+(define (goimapnotify-serialize-maybe-string-or-file-like field-name value)
+ (goimapnotify-maybe-serialize field-name value
+ goimapnotify-serialize-string-or-file-like))
+
+(define goimapnotify-serialize-string goimapnotify-serialize-field)
+(define (goimapnotify-serialize-maybe-string field-name value)
+ (goimapnotify-maybe-serialize field-name value goimapnotify-serialize-string))
+
+(define (goimapnotify-serialize-maybe-integer field-name value)
+ (goimapnotify-maybe-serialize field-name value goimapnotify-serialize-integer))
+(define goimapnotify-serialize-integer goimapnotify-serialize-field)
+
+(define (goimapnotify-serialize-maybe-boolean field-name value)
+ (goimapnotify-maybe-serialize field-name value goimapnotify-serialize-boolean))
+(define goimapnotify-serialize-boolean goimapnotify-serialize-field)
+
+(define (goimapnotify-serialize-maybe-list-of-strings field-name value)
+ (goimapnotify-maybe-serialize field-name value goimapnotify-serialize-list-of-strings))
+(define (goimapnotify-serialize-list-of-strings field-name value)
+ (goimapnotify-serialize-field field-name (list->array 1 value)))
+
+(define (goimapnotify-serialize-maybe-goimapnotify-tls-options field-name config)
+ (goimapnotify-maybe-serialize field-name config
+ goimapnotify-serialize-goimapnotify-tls-options))
+
+(define (goimapnotify-serialize-goimapnotify-tls-options field-name config)
+ (goimapnotify-serialize-field
+ field-name
+ (prepare-configuration-for-json config goimapnotify-tls-options-fields)))
+
+(define (prepare-configuration-for-json config fields)
+ "Convert the configuration to the format expected by guile-json.
+ Unset maybe-values do not appear in the configuration file."
+ (fold
+ (lambda (field accu)
+ (let ((value ((configuration-field-getter field) config)))
+ (append accu
+ (if (maybe-value-set? value)
+ (list ((configuration-field-serializer field)
+ (configuration-field-name field)
+ value))
+ '()))))
+ '()
+ fields))
+
+(define-configuration goimapnotify-tls-options
+ (reject-unauthorized?
+ maybe-boolean
+ "Skip verifying CA server identify?")
+ (prefix goimapnotify-))
+
+(define-maybe goimapnotify-tls-options)
+
+; 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? lst)
+ "List is in the form of '((file-name file-like))"
+ (every (compose goimapnotify-account? second) lst))
+
+(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)
+ (define* (account->json account-config-and-path)
+ (match
+ account-config-and-path
+ ((path account-config)
+ (let ((prepared-config
+ (prepare-configuration-for-json
+ account-config
+ goimapnotify-account-fields)))
+ `((,path
+ ,(computed-file
+ (string-append
+ "mail-imapnotify-config-"
+ (goimapnotify-account-host account-config))
+ (with-extensions (list guile-json-4)
+ #~(begin
+ (use-modules (json builder))
+
+ (with-output-to-file #$output
+ (lambda ()
+ (scm->json '(#$@prepared-config)
+ #:pretty #t))))))))))))
+
+ (match-record config <home-goimapnotify-configuration>
+ (accounts)
+ (append-map
+ (cut account->json <>)
+ 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
N
N
Nils Landt wrote on 26 Nov 2023 12:14
(name . 66557@debbugs.gnu.org)(address . 66557@debbugs.gnu.org)
1635724009.68147.1700997260830@office.mailbox.org
Hello,

I have pushed a new version.
Compared to the version you reviewed, I made the following changes:
- docs: Stdin -> stdin
- docs: use file-append for "pass" example
- docs: re-add trailing whitespace
- rework filter + map to use fold instead
- update link to upstream config documentation from master to current commit
- ignore first element in list-of-goimapnotify-accounts?
- add question mark suffix to boolean fields (e.g. tls -> tls?)
- fix "on-new-mail" option in docstring
- remove parentheses around configuration field types

Lastly, some comments on review requests I was unable to implement.

Ricardo:
Toggle quote (4 lines)
> So I think it would be better to let these fields accept command lists.
> FILE-APPEND should only join the package value with the file name of the
> executable, but not include any arguments.

I did not understand what this meant. Do you have any examples? I implemented your second suggestion of using file-append for pass instead.

Toggle quote (4 lines)
> Could this be (cons (goimapnotify-format-field field-name) value)
> instead? I don’t think we need this wrapping and unwrapping with G-exp
> syntax.

This results in e.g. ("boxes" . #("INBOX")) , leading to a syntax error in the generated guile script. I'm open to suggestions here.

Bruno:
Toggle quote (3 lines)
> This isn't needed, fields whose maybe-values are unset don't call the
> serializing procedures.

combined with

Toggle quote (2 lines)
> You can use 'serialize-configuration' instead which accounts for the unset maybe-values.

I put some time into this, but I don't see how serialize-configuration, which returns a gexp including string-append, could be used to turn a configuration record into the format required by guile-json.
B
B
Bruno Victal wrote on 28 Nov 2023 21:37
(name . Nils Landt)(address . nils@landt.email)(address . 66557@debbugs.gnu.org)
d5eedb15-077d-4a67-9337-9dea96e0e46a@makinata.eu
Hi Nils,

On 2023-11-21 15:25, Nils Landt wrote:
Toggle quote (19 lines)
>> Bruno Victal <mirai@makinata.eu> hat am 20.11.2023 18:16 CET ls,
>> On 2023-10-15 15:01, Nils Landt wrote:
>>> +(define (list-of-goimapnotify-accounts? lst)
>>> + "List is in the form of '((file-name file-like))"
>>> + (every (lambda (element)
>>> + (match element
>>> + ((string ($ <goimapnotify-account>))
>>> + #t)
>>> + (_ #f)))
>>> + lst))
>>
>> You can replace this with:
>> --8<---------------cut here---------------start------------->8---
>> (define list-of-goimapnotify-accounts?
>> (list-of goimapnotify-account?))
>> --8<---------------cut here---------------end--------------->8---
>
> Wouldn't that fail because it expects '(goimapnotify-account goimapnotify-account[...])?

Right, it should be something like:

Toggle snippet (4 lines)
(define list-of-goimapnotify-accounts?
(list-of (match-lambda ((? string?) (? goimapnotify-account?)))))

--
Furthermore, I consider that nonfree software must be eradicated.

Cheers,
Bruno.
B
B
Bruno Victal wrote on 29 Nov 2023 18:20
(name . Nils Landt)(address . nils@landt.email)(address . 66557@debbugs.gnu.org)
52947fbf-42c2-44e5-adf4-0b1bd66f56c0@makinata.eu
Hi Nils,

On 2023-11-26 11:14, Nils Landt wrote:
Toggle quote (10 lines)
> Bruno:
>> This isn't needed, fields whose maybe-values are unset don't call the
>> serializing procedures.
>
> combined with
>
>> You can use 'serialize-configuration' instead which accounts for the unset maybe-values.
>
> I put some time into this, but I don't see how serialize-configuration, which returns a gexp including string-append, could be used to turn a configuration record into the format required by guile-json.

Right, I missed that you are synthesizing a list for scm->json,
you will need to make use of the lower-level transducers in
(gnu services configuration).
The fstrim-service-type in (gnu services linux) provides a simple
example of its use though for your case you might be looking at
something like:

Toggle snippet (25 lines)
;; note: untested snippet

(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 #$@(list-transduce
(base-transducer account)
rcons
goimapnotify-account-fields)))
#:pretty #t))))))))
accounts)))

--
Furthermore, I consider that nonfree software must be eradicated.

Cheers,
Bruno.
N
N
Nils Landt wrote on 3 Dec 2023 16:53
[PATCH] home: services: Add goimapnotify service.
(address . 66557@debbugs.gnu.org)
1679639d1f2c9792da58baa9f93474b0b0b96956.1701618788.git.nils@landt.email
* 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(-)

Toggle diff (361 lines)
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
N
N
Nils Landt wrote on 3 Dec 2023 16:56
(name . Bruno Victal)(address . mirai@makinata.eu)(address . 66557@debbugs.gnu.org)
562343514.101052.1701619000082@office.mailbox.org
Hey Bruno,

I've just pushed a new version, please have another look.

Toggle quote (10 lines)
> Bruno Victal <mirai@makinata.eu> hat am 29.11.2023 18:20 CET geschrieben:
>
>
> Hi Nils,
>
> On 2023-11-26 11:14, Nils Landt wrote:
> > Bruno:
> >> This isn't needed, fields whose maybe-values are unset don't call the
> >> serializing procedures.

Took me a while to understand I needed to prefix the maybe-values. This then generates (goimapnotify-serialize-maybe-boolean).

Toggle quote (6 lines)
> you will need to make use of the lower-level transducers in
> (gnu services configuration).
> The fstrim-service-type in (gnu services linux) provides a simple
> example of its use though for your case you might be looking at
> something like:

Thanks, I was able to get it work with a minor change! Don't really understand transducers yet, but I've starting reading up on them.

Nils
?
Your comment

Commenting via the web interface is currently disabled.

To comment on this conversation send an email to 66557@debbugs.gnu.org

To respond to this issue using the mumi CLI, first switch to it
mumi current 66557
Then, you may apply the latest patchset in this issue (with sign off)
mumi am -- -s
Or, compose a reply to this issue
mumi compose
Or, send patches to this issue
mumi send-email *.patch