services: radicale: Use define-configuration.

* doc/guix.texi (radicale-configuration): Update documentation to reflect new
configuration, add new symbols.
* gnu/services/mail.scm (%default-radicale-config-file): Delete.
(radicale-auth-configuration, radicale-auth-configuration?)
(radicale-encoding-configuration, radicale-encoding-configuration?)
(radicale-logging-configuration, radicale-logging-configuration?)
(radicale-rights-configuration, radicale-rights-configuration?)
(radicale-server-configuration, radicale-server-configuration?)
(radicale-storage-configuration, radicale-storage-configuration?):
New configuration types and corresponding predicates.
(radicale-configuration, radicale-configuration?): Use define-configuration.
(radicale-activation, radicale-shepherd-service): Update to new
configuration format.
(radicale-activation): Use user-defined values for service files.
(radicale-service-type): Capitalize "Radicale" in description.

Change-Id: Ic88b8ff2750e3d658f6c7cee02d33417aa8ee6d2
Signed-off-by: Liliana Marie Prikler <liliana.prikler@gmail.com>
This commit is contained in:
Juliana Sims 2024-03-11 21:14:06 -04:00 committed by Liliana Marie Prikler
parent 617f1e5320
commit b49e556b9e
No known key found for this signature in database
GPG key ID: 442A84B8C70E2F87
2 changed files with 511 additions and 45 deletions

View file

@ -28248,23 +28248,195 @@ Mailutils Manual}, for details.
@cindex CardDAV
@defvar radicale-service-type
This is the type of the @uref{https://radicale.org, Radicale} CalDAV/CardDAV
server whose value should be a @code{radicale-configuration}.
This is the type of the @uref{https://radicale.org, Radicale}
CalDAV/CardDAV server whose value should be a
@code{radicale-configuration}. The default configuration matches the
@uref{https://radicale.org/v3.html#configuration, upstream
documentation}.
@end defvar
@deftp {Data Type} radicale-configuration
Data type representing the configuration of @command{radicale}.
Available @code{radicale-configuration} fields are:
@table @asis
@item @code{package} (default: @code{radicale})
The package that provides @command{radicale}.
@item @code{package} (default: @code{radicale}) (type: package)
Package that provides @command{radicale}.
@item @code{config-file} (default: @code{%default-radicale-config-file})
File-like object of the configuration file to use, by default it will listen
on TCP port 5232 of @code{localhost} and use the @code{htpasswd} file at
@file{/var/lib/radicale/users} with no (@code{plain}) encryption.
@item @code{auth} (default: @code{'()}) (type: radicale-auth-configuration)
Configuration for auth-related variables.
@deftp {Data Type} radicale-auth-configuration
Data type representing the @code{auth} section of a @command{radicale}
configuration file. Available @code{radicale-auth-configuration} fields
are:
@table @asis
@item @code{type} (default: @code{'none}) (type: symbol)
The method to verify usernames and passwords. Options are @code{none},
@code{htpasswd}, @code{remote-user}, and @code{http-x-remote-user}.
This value is tied to @code{htpasswd-filename} and
@code{htpasswd-encryption}.
@item @code{htpasswd-filename} (default: @code{"/etc/radicale/users"}) (type: file-name)
Path to the htpasswd file. Use htpasswd or similar to generate this
file.
@item @code{htpasswd-encryption} (default: @code{'md5}) (type: symbol)
Encryption method used in the htpasswd file. Options are @code{plain},
@code{bcrypt}, and @code{md5}.
@item @code{delay} (default: @code{1}) (type: non-negative-integer)
Average delay after failed login attempts in seconds.
@item @code{realm} (default: @code{"Radicale - Password Required"}) (type: string)
Message displayed in the client when a password is needed.
@end table
@end deftp
@item @code{encoding} (default: @code{'()}) (type: radicale-encoding-configuration)
Configuration for encoding-related variables.
@deftp {Data Type} radicale-encoding-configuration
Data type representing the @code{encoding} section of a
@command{radicale} configuration file. Available
@code{radicale-encoding-configuration} fields are:
@table @asis
@item @code{request} (default: @code{'utf-8}) (type: symbol)
Encoding for responding requests.
@item @code{stock} (default: @code{'utf-8}) (type: symbol)
Encoding for storing local collections.
@end table
@end deftp
@item @code{headers-file} (default: none) (type: file-like)
Custom HTTP headers.
@item @code{logging} (default: @code{'()}) (type: radicale-logging-configuration)
Configuration for logging-related variables.
@deftp {Data Type} radicale-logging-configuration
Data type representing the @code{logging} section of a
@command{radicale} configuration file. Available
@code{radicale-logging-configuration} fields are:
@table @asis
@item @code{level} (default: @code{'warning}) (type: symbol)
Set the logging level. One of @code{debug}, @code{info},
@code{warning}, @code{error}, or @code{critical}.
@item @code{mask-passwords?} (default: @code{#t}) (type: boolean)
Whether to include passwords in logs.
@end table
@end deftp
@item @code{rights} (default: @code{'()}) (type: radicale-rights-configuration)
Configuration for rights-related variables. This should be a
@code{radicale-rights-configuration}.
@deftp {Data Type} radicale-rights-configuration
Data type representing the @code{rights} section of a @command{radicale}
configuration file. Available @code{radicale-rights-configuration}
fields are:
@table @asis
@item @code{type} (default: @code{'owner-only}) (type: symbol)
Backend used to check collection access rights. The recommended backend
is @code{owner-only}. If access to calendars and address books outside
the home directory of users is granted, clients won't detect these
collections and will not show them to the user. Choosing any other
method is only useful if you access calendars and address books directly
via URL. Options are @code{authenticate}, @code{owner-only},
@code{owner-write}, and @code{from-file}.
@item @code{file} (default: @code{""}) (type: file-name)
File for the rights backend @code{from-file}.
@end table
@end deftp
@item @code{server} (default: @code{'()}) (type: radicale-server-configuration)
Configuration for server-related variables. Ignored if WSGI is used.
@deftp {Data Type} radicale-server-configuration
Data type representing the @code{server} section of a @command{radicale}
configuration file. Available @code{radicale-server-configuration}
fields are:
@table @asis
@item @code{hosts} (default: @code{(list "localhost:5232")}) (type: list-of-ip-addresses)
List of IP addresses that the server will bind to.
@item @code{max-connections} (default: @code{8}) (type: non-negative-integer)
Maximum number of parallel connections. Set to 0 to disable the limit.
@item @code{max-content-length} (default: @code{100000000}) (type: non-negative-integer)
Maximum size of the request body in bytes.
@item @code{timeout} (default: @code{30}) (type: non-negative-integer)
Socket timeout in seconds.
@item @code{ssl?} (default: @code{#f}) (type: boolean)
Whether to enable transport layer encryption.
@item @code{certificate} (default: @code{"/etc/ssl/radicale.cert.pem"}) (type: file-name)
Path of the SSL certificate.
@item @code{key} (default: @code{"/etc/ssl/radicale.key.pem"}) (type: file-name)
Path to the private key for SSL. Only effective if @code{ssl?} is
@code{#t}.
@item @code{certificate-authority} (default: @code{""}) (type: file-name)
Path to CA certificate for validating client certificates. This can be
used to secure TCP traffic between Radicale and a reverse proxy. If you
want to authenticate users with client-side certificates, you also have
to write an authentication plugin that extracts the username from the
certificate.
@end table
@end deftp
@item @code{storage} (default: @code{'()}) (type: radicale-storage-configuration)
Configuration for storage-related variables.
@deftp {Data Type} radicale-storage-configuration
Data type representing the @code{storage} section of a
@command{radicale} configuration file. Available
@code{radicale-storage-configuration} fields are:
@table @asis
@item @code{type} (default: @code{'multifilesystem}) (type: symbol)
Backend used to store data. Options are @code{multifilesystem} and
@code{multifilesystem-nolock}.
@item @code{filesystem-folder} (default: @code{"/var/lib/radicale/collections"}) (type: file-name)
Folder for storing local collections. Created if not present.
@item @code{max-sync-token-age} (default: @code{2592000}) (type: non-negative-integer)
Delete sync-tokens that are older than the specified time in seconds.
@item @code{hook} (default: @code{""}) (type: string)
Command run after changes to storage.
@end table
@end deftp
@item @code{web-interface?} (default: @code{#t}) (type: boolean)
Whether to use Radicale's built-in web interface.
@end table
@end deftp
@subsubheading Rspamd Service

View file

@ -7,6 +7,7 @@
;;; Copyright © 2020 Jonathan Brielmaier <jonathan.brielmaier@web.de>
;;; Copyright © 2023 Thomas Ieong <th.ieong@free.fr>
;;; Copyright © 2023 Saku Laesvuori <saku@laesvuori.fi>
;;; Copyright © 2024 Juliana Sims <juli@incana.org>
;;;
;;; This file is part of GNU Guix.
;;;
@ -38,10 +39,12 @@ (define-module (gnu services mail)
#:use-module (gnu packages dav)
#:use-module (gnu packages tls)
#:use-module (guix deprecation)
#:use-module ((guix diagnostics) #:select (source-properties->location))
#:use-module (guix modules)
#:use-module (guix records)
#:use-module (guix packages)
#:use-module (guix gexp)
#:use-module (ice-9 curried-definitions)
#:use-module (ice-9 match)
#:use-module (ice-9 format)
#:use-module (srfi srfi-1)
@ -79,10 +82,21 @@ (define-module (gnu services mail)
imap4d-service-type
%default-imap4d-config-file
radicale-auth-configuration
radicale-auth-configuration?
radicale-encoding-configuration
radicale-encoding-configuration?
radicale-logging-configuration
radicale-logging-configuration?
radicale-rights-configuration
radicale-rights-configuration?
radicale-server-configuration
radicale-server-configuration?
radicale-storage-configuration
radicale-storage-configuration?
radicale-configuration
radicale-configuration?
radicale-service-type
%default-radicale-config-file
rspamd-configuration
rspamd-service-type
@ -1929,23 +1943,258 @@ (define imap4d-service-type
;;; Radicale.
;;;
(define-record-type* <radicale-configuration>
radicale-configuration make-radicale-configuration
radicale-configuration?
(package radicale-configuration-package
(default radicale))
(config-file radicale-configuration-config-file
(default %default-radicale-config-file)))
;; Maybe types
(define %default-radicale-config-file
(plain-file "radicale.conf" "
[auth]
type = htpasswd
htpasswd_filename = /var/lib/radicale/users
htpasswd_encryption = plain
(define (comma-separated-ip-list? lst)
(every (lambda (s)
(or (string-prefix? "localhost" s)
((@@ (gnu services vpn) ipv4-address?) s)
((@@ (gnu services vpn) ipv6-address?) s)))
lst))
[server]
hosts = localhost:5232"))
(define-maybe boolean (prefix radicale-))
(define-maybe comma-separated-ip-list (prefix radicale-))
(define-maybe file-name (prefix radicale-))
(define-maybe non-negative-integer (prefix radicale-))
(define-maybe string (prefix radicale-))
(define-maybe symbol (prefix radicale-))
;; Serializers and sanitizers
(define (radicale-serialize-field field-name value)
;; XXX We quote the un-gexp form here because otherwise symbol-literals are
;; treated as variables. We can get away with this because all of our other
;; field value types are primitives by the time they get here so are printed
;; the same whether or not they are quoted.
#~(format #f "~a = ~a\n" #$(uglify-field-name field-name) '#$value))
(define (radicale-serialize-boolean field-name value?)
(radicale-serialize-field field-name (if value? "True" "False")))
(define (radicale-serialize-comma-separated-ip-list field-name value)
(radicale-serialize-field field-name (string-join value ", ")))
(define radicale-serialize-file-name radicale-serialize-field)
(define radicale-serialize-non-negative-integer radicale-serialize-field)
(define radicale-serialize-string radicale-serialize-field)
(define radicale-serialize-symbol radicale-serialize-field)
(define ((sanitize-delimited-symbols syms location field) value)
(cond
((not (maybe-value-set? value))
value)
((member value syms)
(string->symbol (uglify-field-name value)))
(else
(configuration-field-error (source-properties->location location)
field
value))))
;; Section configuration types
(define-configuration radicale-auth-configuration
(type
maybe-symbol
"The method to verify usernames and passwords. Options are @code{none},
@code{htpasswd}, @code{remote-user}, and @code{http-x-remote-user}.
This value is tied to @code{htpasswd-filename} and @code{htpasswd-encryption}."
(sanitizer
(sanitize-delimited-symbols '(none htpasswd remote-user http-x-remote-user)
(current-source-location)
'type)))
(htpasswd-filename
maybe-file-name
"Path to the htpasswd file. Use htpasswd or similar to generate this file.")
(htpasswd-encryption
maybe-symbol
"Encryption method used in the htpasswd file. Options are @code{plain},
@code{bcrypt}, and @code{md5}."
(sanitizer
(sanitize-delimited-symbols '(plain bcrypt md5)
(current-source-location)
'htpasswd-encryption)))
(delay
maybe-non-negative-integer
"Average delay after failed login attempts in seconds.")
(realm
maybe-string
"Message displayed in the client when a password is needed.")
(prefix radicale-))
(define-configuration radicale-encoding-configuration
(request
maybe-symbol
"Encoding for responding requests.")
(stock
maybe-symbol
"Encoding for storing local collections.")
(prefix radicale-))
(define-configuration radicale-logging-configuration
(level
maybe-symbol
"Set the logging level. One of @code{debug}, @code{info}, @code{warning},
@code{error}, or @code{critical}."
(sanitizer (sanitize-delimited-symbols '(debug info warning error critical)
(current-source-location)
'level)))
(mask-passwords?
maybe-boolean
"Whether to include passwords in logs.")
(prefix radicale-))
(define-configuration radicale-rights-configuration
(type
maybe-symbol
"Backend used to check collection access rights. The recommended backend is
@code{owner-only}. If access to calendars and address books outside the home
directory of users is granted, clients won't detect these collections and will
not show them to the user. Choosing any other method is only useful if you
access calendars and address books directly via URL. Options are
@code{authenticate}, @code{owner-only}, @code{owner-write}, and
@code{from-file}."
(sanitizer
(sanitize-delimited-symbols '(authenticate owner-only owner-write from-file)
(current-source-location)
'type)))
(file
maybe-file-name
"File for the rights backend @code{from-file}.")
(prefix radicale-))
(define-configuration radicale-server-configuration
(hosts
maybe-comma-separated-ip-list
"List of IP addresses that the server will bind to.")
(max-connections
maybe-non-negative-integer
"Maximum number of parallel connections. Set to 0 to disable the limit.")
(max-content-length
maybe-non-negative-integer
"Maximum size of the request body in byetes.")
(timeout
maybe-non-negative-integer
"Socket timeout in seconds.")
(ssl?
maybe-boolean
"Whether to enable transport layer encryption.")
(certificate
maybe-file-name
"Path of the SSL certificate.")
(key
maybe-file-name
"Path to the private key for SSL. Only effective if @code{ssl?} is
@code{#t}.")
(certificate-authority
maybe-file-name
"Path to CA certificate for validating client certificates. This can be used
to secure TCP traffic between Radicale and a reverse proxy. If you want to
authenticate users with client-side certificates, you also have to write an
authentication plugin that extracts the username from the certificate.")
(prefix radicale-))
(define-configuration radicale-storage-configuration
(type
maybe-symbol
"Backend used to store data. Options are @code{multifilesystem} and
@code{multifilesystem-nolock}."
(sanitizer
(sanitize-delimited-symbols '(multifilesystem multifilesystem-nolock)
(current-source-location)
'type)))
(filesystem-folder
maybe-file-name
"Folder for storing local collections. Created if not present.")
(max-sync-token-age
maybe-non-negative-integer
"Delete sync-tokens that are older than the specified time in seconds.")
(hook
maybe-string
"Command run after changes to storage.")
(prefix radicale-))
;; Helpers for using section configurations in the main configuration
;; XXX These indirections are necessary to avoid creating semantic ambiguity
(define auth-config? radicale-auth-configuration?)
(define encoding-config? radicale-encoding-configuration?)
(define headers-file? file-like?)
(define logging-config? radicale-logging-configuration?)
(define rights-config? radicale-rights-configuration?)
(define server-config? radicale-server-configuration?)
(define storage-config? radicale-storage-configuration?)
(define-maybe auth-config)
(define-maybe encoding-config)
(define-maybe headers-file)
(define-maybe logging-config)
(define-maybe rights-config)
(define-maybe server-config)
(define-maybe storage-config)
(define ((serialize-radicale-section fields) name cfg)
#~(format #f "[~a]\n~a\n" '#$name #$(serialize-configuration cfg fields)))
(define serialize-auth-config
(serialize-radicale-section radicale-auth-configuration-fields))
(define serialize-encoding-config
(serialize-radicale-section radicale-encoding-configuration-fields))
(define serialize-logging-config
(serialize-radicale-section radicale-logging-configuration-fields))
(define serialize-rights-config
(serialize-radicale-section radicale-rights-configuration-fields))
(define serialize-server-config
(serialize-radicale-section radicale-server-configuration-fields))
(define serialize-storage-config
(serialize-radicale-section radicale-storage-configuration-fields))
(define (serialize-radicale-configuration cfg)
(mixed-text-file
"radicale.conf"
(serialize-configuration cfg radicale-configuration-fields)))
(define-configuration radicale-configuration
;; Only fields whose default value does not match upstream are not maybe-types
(package
(file-like radicale)
"Package that provides @command{radicale}.")
(auth
maybe-auth-config
"Configuration for auth-related variables.")
(encoding
maybe-encoding-config
"Configuration for encoding-related variables.")
(headers-file
maybe-headers-file
"Custom HTTP headers."
(serializer
(lambda (field-name value)
#~(begin
(use-modules (ice-9 rdelim))
(format #f "[headers]\n~a\n\n"
(with-input-from-file #$value read-string))))))
(logging
maybe-logging-config
"Configuration for logging-related variables.")
(rights
maybe-rights-config
"Configuration for rights-related variables.")
(server
maybe-server-config
"Configuration for server-related variables. Ignored if WSGI is used.")
(storage
maybe-storage-config
"Configuration for storage-related variables.")
(web-interface?
maybe-boolean
"Whether to use Radicale's built-in web interface."
(serializer
(lambda (_ use?)
#~(format #f "[web]\ntype = ~a\n\n" #$(if use? "internal" "none"))))))
(define %radicale-accounts
(list (user-group
@ -1959,43 +2208,88 @@ (define %radicale-accounts
(home-directory "/var/empty")
(shell (file-append shadow "/sbin/nologin")))))
(define radicale-shepherd-service
(match-lambda
(($ <radicale-configuration> package config-file)
(list (shepherd-service
(provision '(radicale))
(documentation "Run the radicale daemon.")
(requirement '(networking))
(start #~(make-forkexec-constructor
(list #$(file-append package "/bin/radicale")
"-C" #$config-file)
#:user "radicale"
#:group "radicale"))
(stop #~(make-kill-destructor)))))))
(define (radicale-shepherd-service cfg)
(list (shepherd-service
(provision '(radicale))
(documentation "Run the radicale daemon.")
(requirement '(networking))
(start #~(make-forkexec-constructor
(list #$(file-append (radicale-configuration-package cfg)
"/bin/radicale")
"-C" #$(serialize-radicale-configuration cfg))
#:user "radicale"
#:group "radicale"))
(stop #~(make-kill-destructor)))))
(define radicale-activation
(match-lambda
(($ <radicale-configuration> package config-file)
(($ <radicale-configuration> _ auth-config _ _ _ _ _ storage-config _)
;; Get values for the collections directory
;; See https://radicale.org/v3.html#running-as-a-service
(define filesystem-folder-val
(if (maybe-value-set? storage-config)
(radicale-storage-configuration-filesystem-folder storage-config)
storage-config))
(define collections-dir
(if (maybe-value-set? filesystem-folder-val)
filesystem-folder-val
"/var/lib/radicale/collections"))
(define collections-parent-dir (dirname collections-dir))
;; Get values for the password file directory
(define auth-value-set? (maybe-value-set? auth-config))
;; If auth's type is 'none or unset, that means there is no authentication
;; and we don't need to setup files for it
(define auth?
(and auth-value-set?
(not (eq? (radicale-auth-configuration-type auth-config) 'none))))
(define password-file-val
(if auth-value-set?
(radicale-auth-configuration-htpasswd-filename auth-config)
auth-config))
(define password-file-dir
(if (maybe-value-set? password-file-val)
(dirname password-file-val)
"/etc/radicale"))
(with-imported-modules '((guix build utils))
#~(begin
(use-modules (guix build utils))
(let ((uid (passwd:uid (getpw "radicale")))
(gid (group:gid (getgr "radicale"))))
(mkdir-p "/var/lib/radicale/collections")
(chown "/var/lib/radicale" uid gid)
(chown "/var/lib/radicale/collections" uid gid)
(chmod "/var/lib/radicale" #o700)))))))
(let ((user (getpwnam "radicale")))
;; Collections directory perms
(mkdir-p/perms #$collections-dir user #o700)
;; Password file perms
(when #$auth?
;; In theory, the password file and thus this directory should already
;; exist because the user has to make them by hand
(mkdir-p/perms #$password-file-dir user #o700))))))))
(define radicale-service-type
(service-type
(name 'radicale)
(description "Run radicale, a small CalDAV and CardDAV server.")
(description "Run Radicale, a small CalDAV and CardDAV server.")
(extensions
(list (service-extension shepherd-root-service-type radicale-shepherd-service)
(service-extension account-service-type (const %radicale-accounts))
(service-extension activation-service-type radicale-activation)))
(default-value (radicale-configuration))))
(define (generate-radicale-documentation)
(generate-documentation
`((radicale-configuration
,radicale-configuration-fields
(auth radicale-auth-configuration)
(encoding radicale-encoding-configuration)
(logging radicale-logging-configuration)
(rights radicale-rights-configuration)
(server radicale-server-configuration)
(storage radicale-storage-configuration))
(radicale-auth-configuration ,radicale-auth-configuration-fields)
(radicale-encoding-configuration ,radicale-encoding-configuration-fields)
(radicale-logging-configuration ,radicale-logging-configuration-fields)
(radicale-rights-configuration ,radicale-rights-configuration-fields)
(radicale-server-configuration ,radicale-server-configuration-fields)
(radicale-storage-configuration ,radicale-storage-configuration-fields))
'radicale-configuration))
;;;
;;; Rspamd.
;;;