~ruther/guix-local

b681e339fa37f2a26763458ee56b31af1d6a7ec5 — Thomas Ieong 3 years ago 7ce0c79
services: Add rspamd-service-type.

* gnu/services/mail.scm (rspamd-service-type): New variable.
* gnu/tests/mail.scm (%test-rspamd): New variable.
* doc/guix.texi: Document it.

Co-authored-by: Saku Laesvuori <saku@laesvuori.fi>
Change-Id: I7196643f087ffe9fc91aab231b69d5ed8dc9d198
Signed-off-by: Ludovic Courtès <ludo@gnu.org>
3 files changed, 327 insertions(+), 2 deletions(-)

M doc/guix.texi
M gnu/services/mail.scm
M gnu/tests/mail.scm
M doc/guix.texi => doc/guix.texi +62 -0
@@ 120,6 120,8 @@ Copyright @copyright{} 2023 Zheng Junjie@*
Copyright @copyright{} 2023 Brian Cully@*
Copyright @copyright{} 2023 Felix Lechner@*
Copyright @copyright{} 2023 Foundation Devices, Inc.@*
Copyright @copyright{} 2023 Thomas Ieong@*
Copyright @copyright{} 2023 Saku Laesvuori@*

Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3 or


@@ 27639,6 27641,66 @@ on TCP port 5232 of @code{localhost} and use the @code{htpasswd} file at
@end table
@end deftp

@subsubheading Rspamd Service
@cindex email
@cindex spam

@defvar rspamd-service-type
This is the type of the @uref{https://rspamd.com/, Rspamd} filtering
system whose value should be a @code{rspamd-configuration}.
@end defvar

@c %start of fragment

@deftp {Data Type} rspamd-configuration
Available @code{rspamd-configuration} fields are:

@table @asis
@item @code{package} (default: @code{rspamd}) (type: file-like)
The package that provides rspamd.

@item @code{config-file} (default: @code{%default-rspamd-config-file}) (type: file-like)
File-like object of the configuration file to use.  By default all
workers are enabled except fuzzy and they are binded to their usual
ports, e.g localhost:11334, localhost:11333 and so on

@item @code{local.d-files} (default: @code{()}) (type: directory-tree)
Configuration files in local.d, provided as a list of two element lists
where the first element is the filename and the second one is a
file-like object.  Settings in these files will be merged with the
defaults.

@item @code{override.d-files} (default: @code{()}) (type: directory-tree)
Configuration files in override.d, provided as a list of two element
lists where the first element is the filename and the second one is a
file-like object.  Settings in these files will override the defaults.

@item @code{user} (default: @code{%default-rspamd-account}) (type: user-account)
The user to run rspamd as.

@item @code{group} (default: @code{%default-rspamd-group}) (type: user-group)
The group to run rspamd as.

@item @code{debug?} (default: @code{#f}) (type: boolean)
Force debug output.

@item @code{insecure?} (default: @code{#f}) (type: boolean)
Ignore running workers as privileged users.

@item @code{skip-template?} (default: @code{#f}) (type: boolean)
Do not apply Jinja templates.

@item @code{shepherd-requirements} (default: @code{(loopback)}) (type: list-of-symbols)
This is a list of symbols naming Shepherd services that this service
will depend on.

@end table

@end deftp


@c %end of fragment

@node Messaging Services
@subsection Messaging Services


M gnu/services/mail.scm => gnu/services/mail.scm +199 -1
@@ 5,6 5,8 @@
;;; Copyright © 2017, 2020 Tobias Geerinckx-Rice <me@tobias.gr>
;;; Copyright © 2019 Kristofer Buffington <kristoferbuffington@gmail.com>
;;; Copyright © 2020 Jonathan Brielmaier <jonathan.brielmaier@web.de>
;;; Copyright © 2023 Thomas Ieong <th.ieong@free.fr>
;;; Copyright © 2023 Saku Laesvuori <saku@laesvuori.fi>
;;;
;;; This file is part of GNU Guix.
;;;


@@ 80,7 82,13 @@
            radicale-configuration
            radicale-configuration?
            radicale-service-type
            %default-radicale-config-file))
            %default-radicale-config-file

            rspamd-configuration
            rspamd-service-type
            %default-rspamd-account
            %default-rspamd-config-file
            %default-rspamd-group))

;;; Commentary:
;;;


@@ 1987,3 1995,193 @@ hosts = localhost:5232"))
          (service-extension account-service-type (const %radicale-accounts))
          (service-extension activation-service-type radicale-activation)))
   (default-value (radicale-configuration))))

;;;
;;; Rspamd.
;;;

(define (directory-tree? xs)
  (match xs
    ((((? string?) (? file-like?)) ...) #t)
    (_ #f)))

(define-configuration/no-serialization rspamd-configuration
  (package
   (file-like rspamd)
   "The package that provides rspamd.")
  (config-file
   (file-like %default-rspamd-config-file)
   "File-like object of the configuration file to use.  By default
all workers are enabled except fuzzy and they are binded
to their usual ports, e.g localhost:11334, localhost:11333 and so on")
  (local.d-files
   (directory-tree '())
   "Configuration files in local.d, provided as a list of two element lists where
the first element is the filename and the second one is a file-like object.  Settings
in these files will be merged with the defaults.")
  (override.d-files
   (directory-tree '())
   "Configuration files in override.d, provided as a list of two element lists where
the first element is the filename and the second one is a file-like object.  Settings
in these files will override the defaults.")
  (user
   (user-account %default-rspamd-account)
   "The user to run rspamd as.")
  (group
   (user-group %default-rspamd-group)
   "The group to run rspamd as.")
  (debug?
   (boolean #f)
   "Force debug output.")
  (insecure?
   (boolean #f)
   "Ignore running workers as privileged users.")
  (skip-template?
   (boolean #f)
   "Do not apply Jinja templates.")
  (shepherd-requirements
   (list-of-symbols '(loopback))
   "This is a list of symbols naming Shepherd services that this service
will depend on."))

(define %default-rspamd-account
  (user-account
      (name "rspamd")
      (group "rspamd")
      (system? #t)
      (comment "Rspamd daemon")
      (home-directory "/var/empty")
      (shell (file-append shadow "/sbin/nologin"))))

(define %default-rspamd-group
  (user-group
    (name "rspamd")
    (system? #t)))

(define %default-rspamd-config-file
  (plain-file "rspamd.conf" "
.include \"$CONFDIR/common.conf\"

options {
    pidfile = \"$RUNDIR/rspamd.pid\";
    .include \"$CONFDIR/options.inc\"
    .include(try=true; priority=1,duplicate=merge) \"$LOCAL_CONFDIR/local.d/options.inc\"
    .include(try=true; priority=10) \"$LOCAL_CONFDIR/override.d/options.inc\"
}

logging {
    type = \"file\";
    filename = \"$LOGDIR/rspamd.log\";
    .include \"$CONFDIR/logging.inc\"
    .include(try=true; priority=1,duplicate=merge) \"$LOCAL_CONFDIR/local.d/logging.inc\"
    .include(try=true; priority=10) \"$LOCAL_CONFDIR/override.d/logging.inc\"
}

worker \"normal\" {
    bind_socket = \"localhost:11333\";
    .include \"$CONFDIR/worker-normal.inc\"
    .include(try=true; priority=1,duplicate=merge) \"$LOCAL_CONFDIR/local.d/worker-normal.inc\"
    .include(try=true; priority=10) \"$LOCAL_CONFDIR/override.d/worker-normal.inc\"
}

worker \"controller\" {
    bind_socket = \"localhost:11334\";
    .include \"$CONFDIR/worker-controller.inc\"
    .include(try=true; priority=1,duplicate=merge) \"$LOCAL_CONFDIR/local.d/worker-controller.inc\"
    .include(try=true; priority=10) \"$LOCAL_CONFDIR/override.d/worker-controller.inc\"
}

worker \"rspamd_proxy\" {
    bind_socket = \"localhost:11332\";
    .include \"$CONFDIR/worker-proxy.inc\"
    .include(try=true; priority=1,duplicate=merge) \"$LOCAL_CONFDIR/local.d/worker-proxy.inc\"
    .include(try=true; priority=10) \"$LOCAL_CONFDIR/override.d/worker-proxy.inc\"
}

# Local fuzzy storage is disabled by default

worker \"fuzzy\" {
    bind_socket = \"localhost:11335\";
    count = -1; # Disable by default
    .include \"$CONFDIR/worker-fuzzy.inc\"
    .include(try=true; priority=1,duplicate=merge) \"$LOCAL_CONFDIR/local.d/worker-fuzzy.inc\"
    .include(try=true; priority=10) \"$LOCAL_CONFDIR/override.d/worker-fuzzy.inc\"
}
"))

(define (rspamd-accounts config)
  (match-record config <rspamd-configuration>
    (user group)
    (list group user)))

(define (rspamd-shepherd-service config)
  (match-record config <rspamd-configuration>
    (package config-file user group debug? insecure? skip-template?
     local.d-files override.d-files shepherd-requirements)
    (list
     (shepherd-service
      (provision '(rspamd))
      (documentation "Run the rspamd daemon.")
      (requirement shepherd-requirements)
      (start (let ((rspamd (file-append package "/bin/rspamd"))
                   (local-confdir
                     (file-union
                      "rspamd-local-confdir"
                      `(("local.d" ,(file-union "local.d" local.d-files))
                        ("override.d" ,(file-union "override.d" override.d-files))))))
               (with-imported-modules (source-module-closure '((gnu build activation)))
                 #~(begin
                     (use-modules (gnu build activation)) ; for mkdir-p/perms
                     (let ((user (getpwnam #$(user-account-name user))))
                       (mkdir-p/perms "/var/run/rspamd" user #o755)
                       (mkdir-p/perms "/var/log/rspamd" user #o755)
                       (mkdir-p/perms "/var/lib/rspamd" user #o755))
                     (make-forkexec-constructor
                      (list #$rspamd "--config" #$config-file
                            "--var" (string-append "LOCAL_CONFDIR=" #$local-confdir)
                            "--no-fork"
                            #$@(if debug?
                                 '("--debug")
                                 '())
                            #$@(if insecure?
                                 '("--insecure")
                                 '())
                            #$@(if skip-template?
                                 '("--skip-template")
                                 '()))
                      #:user #$(user-account-name user)
                      #:group #$(user-group-name group))))))
      (stop #~(make-kill-destructor))
      (actions
       (list
        (shepherd-configuration-action config-file)
        (shepherd-action
         (name 'reload)
         (documentation "Reload rspamd.")
         (procedure
          #~(lambda (pid)
              (if pid
                (begin
                  (kill pid SIGHUP)
                  (display "Service rspamd has been reloaded"))
                (format #t "Service rspamd is not running.")))))
        (shepherd-action
         (name 'reopen)
         (documentation "Reopen log files.")
         (procedure
          #~(lambda (pid)
              (if pid
                (begin
                  (kill pid SIGUSR1)
                  (display "Reopening the logs for rspamd"))
                (format #t "Service rspamd is not running.")))))))))))

(define rspamd-service-type
  (service-type
   (name 'rspamd)
   (description "Run the rapid spam filtering system.")
   (extensions
    (list
     (service-extension shepherd-root-service-type rspamd-shepherd-service)
     (service-extension account-service-type rspamd-accounts)))
   (default-value (rspamd-configuration))))

M gnu/tests/mail.scm => gnu/tests/mail.scm +66 -1
@@ 6,6 6,7 @@
;;; Copyright © 2018 Clément Lassieur <clement@lassieur.org>
;;; Copyright © 2019 Christopher Baines <mail@cbaines.net>
;;; Copyright © 2019, 2020 Tobias Geerinckx-Rice <me@tobias.gr>
;;; Copyright © 2023 Thomas Ieong <th.ieong@free.fr>
;;;
;;; This file is part of GNU Guix.
;;;


@@ 40,7 41,8 @@
  #:export (%test-opensmtpd
            %test-exim
            %test-dovecot
            %test-getmail))
            %test-getmail
            %test-rspamd))

(define %opensmtpd-os
  (simple-operating-system


@@ 579,3 581,66 @@ Subject: Hello Nice to meet you!")
   (name "getmail")
   (description "Connect to a running Getmail server.")
   (value (run-getmail-test))))

(define %rspamd-os
  (simple-operating-system
   (service rspamd-service-type)))

(define (run-rspamd-test)
  "Return a test of an OS running Rspamd service."

  (define vm
    (virtual-machine
      (marionette-operating-system
        %rspamd-os
        #:imported-modules '((gnu services herd)))))

  (define test
    (with-imported-modules '((gnu build marionette))
      #~(begin
          (use-modules (srfi srfi-64)
                       (gnu build marionette))

          (define marionette
            (make-marionette '(#$vm)))

          (test-runner-current (system-test-runner #$output))
          (test-begin "rspamd")

          (test-assert "service is running"
            (marionette-eval
             '(begin
                (use-modules (gnu services herd))
                (start-service 'rspamd))
             marionette))

          (test-assert "rspamd socket ready"
            (wait-for-unix-socket
             "/var/lib/rspamd/rspamd.sock"
             marionette))

          (test-assert "rspamd log file"
            (wait-for-file "/var/log/rspamd/rspamd.log" marionette))

          ;; Check that we can access the web ui

          (test-equal "http-get"
            200
            (marionette-eval
              '(begin
                 (use-modules (web client)
                              (web response))
                 ;; HEAD returns 500 internal server error, so use GET even though
                 ;; only the headers are relevant
                 (response-code (http-get "http://localhost:11334")))
              marionette))

          (test-end))))

  (gexp->derivation "rspamd-test" test))

(define %test-rspamd
  (system-test
   (name "rspamd")
   (description "Basic rspamd service test.")
   (value (run-rspamd-test))))