~abcdw/rde-devel

system: Add synapse-service-type and mautrix-whatsapp-service-type. v1 PROPOSED

Miguel Moreno: 2
 system: Add synapse-service-type and mautrix-whatsapp-service-type.
 rde: Add feature-synapse.
Miguel Ángel Moreno: 1
 system: Add synapse-service-type and mautrix-whatsapp-service-type.

 3 files changed, 1014 insertions(+), 0 deletions(-)
Thank you for working on this, appreciate it.
Next
Hi Andrew,

Amended the case name transforms as mentioned in the YAML serializer
thread.  This <https://issues.guix.gnu.org/62495> patch has been
recently merged upstream, so the only withstanding patches needed for
this patch series are <https://issues.guix.gnu.org/62284> and
<https://issues.guix.gnu.org/62389>.  Would you mind having a look at
them, please?

Over the last months, I've come across many people in the help-guix/IRC
channels that are interested in the YAML serializer and the Synapse
system service, so we should get the ball rolling on these.
Hi Andrew,

Friendly ping on this :)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~abcdw/rde-devel/patches/40029/mbox | git am -3
Learn more about email & git

[PATCH 1/2] system: Add synapse-service-type and mautrix-whatsapp-service-type. Export this patch

---
 src/rde/system/services/matrix.scm | 462 +++++++++++++++++++++++++++++
 1 file changed, 462 insertions(+)
 create mode 100644 src/rde/system/services/matrix.scm

diff --git a/src/rde/system/services/matrix.scm b/src/rde/system/services/matrix.scm
new file mode 100644
index 00000000..d153b1a7
--- /dev/null
+++ b/src/rde/system/services/matrix.scm
@@ -0,0 +1,462 @@
(define-module (rde system services matrix)
  #:use-module (rde features predicates)
  #:use-module (rde serializers yaml)
  #:use-module (gnu services)
  #:use-module (gnu services shepherd)
  #:use-module (gnu services configuration)
  #:use-module (gnu services databases)
  #:use-module (gnu packages admin)
  #:use-module (gnu packages matrix)
  #:use-module (gnu system accounts)
  #:use-module (gnu system shadow)
  #:use-module (guix gexp)
  #:use-module (srfi srfi-1)
  #:use-module (ice-9 match)
  #:export (synapse-configuration
            synapse-configuration?
            synapse-service-type
            synapse-extension
            mautrix-whatsapp-configuration
            mautrix-whatsapp-configuration?
            mautrix-whatsapp-service-type))

(define-maybe/no-serialization string)

(define-configuration/no-serialization synapse-configuration
  (synapse
   (file-like synapse)
    "The @code{synapse} package to use.")
  (server-name
   (string "localhost")
   "The public-facing domain of the server.  This is used by remote servers to
look up the server address and will appear at the end of usernames and room
addresses created on this server.  The @code{server_name} cannot be changed
later so it's important to configure this before you start Synapse.  It
should be all lowercase and may contain an explicit port.")
  (public-base-url
   (string "")
   "The public-facing base URL that clients use to access this homeserver.")
  (enable-registration?
   (boolean #f)
   "Whether to enable registration for new users.")
  (shared-secret
   (string "")
   "If set, it allows registration of standard or admin accounts by anyone
who has the shared secret, even if registration is otherwise disabled.")
  (secret-key
   maybe-string
   "A secret which is used to sign access tokens.  If none is specified, the
@code{registration_shared_secret} is used, if one is given; otherwise, a
secret key is derived from the signing key.")
  (postgresql-db?
   (boolean #f)
   "Whether to use a PostgreSQL database for storage.")
  (postgresql-db-password
   maybe-string
   "The password for the PostgreSQL database user.  Do note this will be
exposed under @file{/gnu/store} in plain sight.")
  (max-upload-size
   (string "50M")
   "The largest allowed upload size in bytes.")
  (data-directory
   (string "/var/lib/matrix-synapse")
   "Indicates the path where data such as the media store and database files
should be stored.")
  (config-directory
   (string "/var/lib/matrix-synapse")
   "Specifies where additional configuration files such as signing keys and
log configuration should be stored.")
  (trusted-key-servers
   (yaml-config '())
   "The trusted servers to download signing keys from.")
  (report-stats?
   (boolean #f)
   "Indicates whether or not to report anonymized homeserver usage
statistics.")
  (extra-config
   (yaml-config '())
   "Alist, vector, gexp, or file-like objects to write to a Synapse homeserver
configuration to be placed under @file{homeserver.yaml}. See more settings in
@uref{https://matrix-org.github.io/synapse/latest/usage/configuration/homeserver_sample_config.html}."))

(define (synapse-shepherd-service config)
  "Returns a Shepherd service for Synapse."
  (list
   (shepherd-service
    (provision '(synapse))
    (requirement (if (synapse-configuration-postgresql-db? config)
                     '(postgres)
                     '()))
    (start #~(make-forkexec-constructor
              (list (string-append #$(synapse-configuration-synapse config)
                                   "/bin/synapse_homeserver")
                    "-c"
                    #$(synapse-file config)
                    "--config-directory"
                    #$(synapse-configuration-config-directory config)
                    "--data-directory"
                    #$(synapse-configuration-data-directory config))
              #:log-file "/var/log/matrix-synapse.log"
              #:environment-variables
              (list "SSL_CERT_DIR=/etc/ssl/certs"
                    "SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt")))
    (stop #~(make-kill-destructor)))))

(define add-synapse-configuration
  (match-lambda
    (($ <synapse-configuration> _ server-name public-base-url
                                enable-registration? shared-secret
                                secret-key postgresql-db?
                                postgresql-db-password max-upload-size
                                data-directory config-directory
                                trusted-key-servers report-stats?
                                extra-config)
     (serialize-yaml-config
      `((pid-file . ,(string-append data-directory "/homeserver.pid"))
        (server-name . ,server-name)
        (public-base-url . ,public-base-url)
        (media-store-path . ,(string-append data-directory "/media_store"))
        (max-upload-size . ,max-upload-size)
        (registration-shared-secret . ,shared-secret)
        (macaroon-secret-key . ,(or secret-key shared-secret))
        (signing-key-path . ,(string-append config-directory
                                            "/homeserver.signing.key"))
        (enable-registration . ,enable-registration?)
        (report-stats . ,report-stats?)
        (database . ,(if postgresql-db?
                         `((name . psycopg2)
                           (allow-unsafe-locale . #t)
                           (args . ((user . "matrix-synapse")
                                    (database . "matrix-synapse")
                                    (host . localhost)
                                    (password . ,postgresql-db-password)
                                    (port . 5432)
                                    (cp-min . 5)
                                    (cp-max . 10))))
                         `((name . sqlite3)
                           (args . ((database . ,(string-append
                                                  data-directory
                                                  "/homeserver.db")))))))
        (listeners . #(((port . 8008)
                        (tls . #f)
                        (type . http)
                        (x-forwarded . true)
                        (bind-addresses . #("::1" "127.0.0.1"))
                        (resources . #(((names . (client federation))
                                        (compress . #f)))))))
        (trusted-key-servers . ,(if (null? trusted-key-servers)
                                    #(((server-name . "matrix.org")))
                                    trusted-key-servers))
        ,@extra-config)))))

(define %synapse-accounts
  (list
   (user-group
    (name "matrix-synapse")
    (system? #t))
   (user-account
    (name "matrix-synapse")
    (group "matrix-synapse")
    (system? #t)
    (home-directory "/var/empty")
    (shell (file-append shadow "/sbin/nologin")))))

(define (synapse-file config)
  (mixed-text-file
   "homeserver.yaml"
   (add-synapse-configuration config)))

(define (add-synapse-configuration config)
  `(("matrix-synapse/homeserver.yaml"
     ,(synapse-file config))))

(define (synapse-postgresql-service config)
  (if (synapse-configuration-postgresql-db? config)
    (list
     (postgresql-role
      (name "matrix-synapse")
      (create-database? #t)
      (collation "C")
      (ctype "C")
      (template "template0")))
    '()))

(define (synapse-activation-service config)
  #~(begin
      (use-modules (guix build utils))

      (define %user (getpw "matrix-synapse"))
      (define data-dir #$(synapse-configuration-data-directory config))
      (define config-dir #$(synapse-configuration-config-directory config))
      (define signing-key-path
        #$(string-append (synapse-configuration-config-directory config)
                         "/homeserver.signing.key"))
      (define (generate-signing-key)
        (unless (stat signing-key-path #f)
          (system* #$(file-append (synapse-configuration-synapse config)
                                  "/bin/generate_signing_key")
                   "-o"
                   signing-key-path)))

      (mkdir-p data-dir)
      (chown data-dir (passwd:uid %user) (passwd:gid %user))
      (chmod data-dir #o700)
      (mkdir-p config-dir)
      (chown config-dir (passwd:uid %user) (passwd:gid %user))
      (chmod config-dir #o700)
      (generate-signing-key)
      (chown signing-key-path (passwd:uid %user) (passwd:gid %user))
      (chmod signing-key-path #o600)))

(define-configuration/no-serialization synapse-extension
  (extra-config
   (yaml-config '())
   "See @code{synapse-service-type} for more information."))

(define (synapse-extension-service original-config extension-configs)
  (synapse-configuration
   (inherit original-config)
   (extra-config
    (append (synapse-configuration-extra-config original-config)
            (append-map synapse-extension-extra-config extension-configs)))))

(define (generate-synapse-documentation)
  (generate-documentation
   `((synapse-configuration
      ,synapse-configuration-fields))
   'synapse-configuration))

(define synapse-service-type
  (service-type
   (name 'synapse)
   (extensions
    (list
     (service-extension postgresql-role-service-type
                        synapse-postgresql-service)
     (service-extension etc-service-type
                        add-synapse-configuration)
     (service-extension account-service-type
                        (const %synapse-accounts))
     (service-extension activation-service-type
                        synapse-activation-service)
     (service-extension shepherd-root-service-type
                        synapse-shepherd-service)))
   (compose identity)
   (extend synapse-extension-service)
   (default-value (synapse-configuration))
   (description "Configure and run the Synapse Matrix flagship homeserver.")))

(define-configuration/no-serialization mautrix-whatsapp-configuration
  (mautrix-whatsapp
   (file-like mautrix-whatsapp)
    "The @code{mautrix-whatsapp} package to use.")
  (address
   (string "http://localhost:8008")
   "The address this appservice can use to connect to the homeserver.")
  (domain
   (string "")
   "The domain of the homeserver (for MXIDs, etc).")
  (postgresql-db?
   (boolean #f)
   "Whether to use PostgreSQL as the database type.")
  (postgresql-db-password
   maybe-string
   "The password for the PostgreSQL database user.  Note this will be
exposed under @file{/gnu/store} in plain sight.")
  (encryption?
   (boolean #f)
   "Whether to add end-to-bridge encryption support.")
  (data-directory
   (string "/var/lib/mautrix-whatsapp")
   "The path where data such as the registration and database files should
be stored.")
  (log-directory
   (string "/var/log/mautrix-whatsapp")
   "The directory for @code{mautrix-whatsapp} log files.")
  (permissions
   (alist '())
   "The permissions for using the bridge. Permitted values include:
@itemize
@item relay - Talk through the relaybot (if enabled), no access otherwise
@item user - Access to use the bridge to chat with a WhatsApp account
@item admin - User level and some additional administration tools
@end itemize
Permitted keys include:
@itemize
@item * - All Matrix users
@item domain - All users on that homeserver
@item mxid - Specific user
@end itemize")
  (extra-config
   (yaml-config '())
   "Alist, vector, gexp, or file-like objects to write to a
@code{mautrix-whatsapp} bridge configuration to be placed under
@file{config.yaml}.  See more settings in
@uref{https://github.com/mautrix/whatsapp/blob/master/example-config.yaml}."))

(define %mautrix-whatsapp-accounts
  (list
   (user-group
    (name "mautrix-whatsapp")
    (system? #t))
   (user-account
    (name "mautrix-whatsapp")
    (group "mautrix-whatsapp")
    (system? #t)
    (home-directory "/var/empty")
    (shell (file-append shadow "/sbin/nologin")))))

(define (mautrix-whatsapp-postgresql-service config)
  (if (mautrix-whatsapp-configuration-postgresql-db? config)
      (list
       (postgresql-role
        (name "mautrix-whatsapp")
        (create-database? #t)
        (collation "C")
        (ctype "C")
        (template "template0")))
      '()))

(define add-mautrix-whatsapp-configuration
  (match-lambda
    (($ <mautrix-whatsapp-configuration> _ address domain postgresql-db?
                                         postgresql-db-password
                                         encryption? data-directory
                                         log-directory permissions
                                         extra-config)
     (serialize-yaml-config
      `((homeserver . ((address . ,address)
                       (domain . ,domain)))
        ,(cons 'appservice
               `((address . "http://localhost:29318")
                 (hostname . "0.0.0.0")
                 (port . 29318)
                 ,(cons
                   'database
                   (if postgresql-db?
                       `((type . postgres)
                         ,(cons
                           'uri
                           (string-append
                            "postgres://mautrix-whatsapp:"
                            postgresql-db-password
                            "@localhost/mautrix-whatsapp?sslmode=disable")))
                       `((type . sqlite3)
                         (uri . ,(string-append
                                  data-directory
                                  "/mautrix-whatsapp.db")))))
                 (id . whatsapp)
                 (bot . ((username . whatsappbot)
                         (displayname . "WhatsApp bridge bot")))
                 (as-token . "")
                 (hs-token . "")))
        ,(cons 'bridge
               `((username-template . "whatsapp_{{.}}")
                 ,(cons
                   'displayname-template
                   (string-append
                    "{{if .BusinessName}}{{.BusinessName}}"
                    "{{else if .PushName}}{{.PushName}}"
                    "{{else}}{{.JID}}{{end}} (WA)"))
                 (command-prefix . "!wa")
                 (permissions . (("*" . relay)
                                 ,@permissions))
                 (relay . ((enabled . #t)
                           (admin-only . #t)))
                 (encryption . ((allow . ,encryption?)
                                (default . ,encryption?)))))
        (logging . ((directory . ,(string-append log-directory "/logs"))
                    (file-name-format . "{{.Date}}-{{.Index}}.log")
                    (file-date-format . "2006-01-02")
                    (file-mode . 0384)
                    (timestamp-format . "Jan _2, 2006 15:04:05")
                    (print-level . debug)))
        ,@extra-config)))))

(define (mautrix-whatsapp-synapse-service config)
  (synapse-extension
   (extra-config
    `((app-service-config-files
       . #(,(string-append
             (mautrix-whatsapp-configuration-data-directory config)
             "/registration.yaml")))))))

(define (mautrix-whatsapp-activation-service config)
  (define mautrix-whatsapp-file
    (mixed-text-file
     "config.yaml"
     #~(add-mautrix-whatsapp-configuration config)))

  #~(begin
      (use-modules (guix build utils))

      (define %user (getpw "mautrix-whatsapp"))
      (define data-dir
        #$(mautrix-whatsapp-configuration-data-directory config))
      (define log-dir #$(mautrix-whatsapp-configuration-log-directory config))
      (define registration-file (string-append data-dir "/registration.yaml"))
      (define config-file (string-append data-dir "/config.yaml"))
      (define (generate-registration-file)
        (unless (stat registration-file #f)
          (copy-file #$mautrix-whatsapp-file config-file)
          (system* #$(file-append
                      (mautrix-whatsapp-configuration-mautrix-whatsapp config)
                      "/bin/mautrix-whatsapp")
                   "--generate-registration"
                   "--config=" config-file
                   "--registration=" registration-file)))

      (mkdir-p data-dir)
      (chown data-dir (passwd:uid %user) (passwd:gid %user))
      (chmod data-dir #o700)
      (mkdir-p log-dir)
      (chown log-dir (passwd:uid %user) (passwd:gid %user))
      (chmod log-dir #o700)
      (generate-registration-file)
      (chmod registration-file #o640)
      (chown config-file (passwd:uid %user) (passwd:gid %user))))

(define (mautrix-whatsapp-shepherd-service config)
  (list
   (shepherd-service
    (provision '(mautrix-whatsapp))
    (requirement '(synapse))
    (start #~(make-forkexec-constructor
              (list
               (string-append
                #$(mautrix-whatsapp-configuration-mautrix-whatsapp config)
                "/bin/mautrix-whatsapp")
               (string-append
                "--config="
                #$(mautrix-whatsapp-configuration-data-directory config)
                "/config.yaml")
               (string-append
                "--registration="
                #$(mautrix-whatsapp-configuration-data-directory config)
                "/registration.yaml"))
              #:user "mautrix-whatsapp"
              #:group "mautrix-whatsapp"
              #:log-file "/var/log/mautrix-whatsapp/mautrix-whatsapp.log"))
    (stop #~(make-kill-destructor)))))

(define mautrix-whatsapp-service-type
  (service-type
   (name 'mautrix-whatsapp)
   (extensions
    (list
     (service-extension account-service-type
                        (const %mautrix-whatsapp-accounts))
     (service-extension postgresql-role-service-type
                        mautrix-whatsapp-postgresql-service)
     (service-extension synapse-service-type
                        mautrix-whatsapp-synapse-service)
     (service-extension activation-service-type
                        mautrix-whatsapp-activation-service)
     (service-extension shepherd-root-service-type
                        mautrix-whatsapp-shepherd-service)))
   (default-value (mautrix-whatsapp-configuration))
   (description "Configure the Matrix-WhatsApp puppetting bridge.  Note that
changing any of the values in @code{mautrix-whatsapp-configuration} requires
regeneration of the registration settings, which you may do by removing
@file{/var/lib/mautrix-whatsapp/registration.yaml} and restarting the
service.")))
-- 
2.39.2
-- 
Best regards,
Miguel Moreno

[PATCH v2] system: Add synapse-service-type and mautrix-whatsapp-service-type. Export this patch

---
* Use original casing for property names
 src/rde/system/services/matrix.scm | 462 +++++++++++++++++++++++++++++
 1 file changed, 462 insertions(+)
 create mode 100644 src/rde/system/services/matrix.scm

diff --git a/src/rde/system/services/matrix.scm b/src/rde/system/services/matrix.scm
new file mode 100644
index 00000000..5556ec9a
--- /dev/null
+++ b/src/rde/system/services/matrix.scm
@@ -0,0 +1,462 @@
(define-module (rde system services matrix)
  #:use-module (rde features predicates)
  #:use-module (rde serializers yaml)
  #:use-module (gnu services)
  #:use-module (gnu services shepherd)
  #:use-module (gnu services configuration)
  #:use-module (gnu services databases)
  #:use-module (gnu packages admin)
  #:use-module (gnu packages matrix)
  #:use-module (gnu system accounts)
  #:use-module (gnu system shadow)
  #:use-module (guix gexp)
  #:use-module (srfi srfi-1)
  #:use-module (ice-9 match)
  #:export (synapse-configuration
            synapse-configuration?
            synapse-service-type
            synapse-extension
            mautrix-whatsapp-configuration
            mautrix-whatsapp-configuration?
            mautrix-whatsapp-service-type))

(define-maybe/no-serialization string)

(define-configuration/no-serialization synapse-configuration
  (synapse
   (file-like synapse)
    "The @code{synapse} package to use.")
  (server-name
   (string "localhost")
   "The public-facing domain of the server.  This is used by remote servers to
look up the server address and will appear at the end of usernames and room
addresses created on this server.  The @code{server_name} cannot be changed
later so it's important to configure this before you start Synapse.  It
should be all lowercase and may contain an explicit port.")
  (public-base-url
   (string "")
   "The public-facing base URL that clients use to access this homeserver.")
  (enable-registration?
   (boolean #f)
   "Whether to enable registration for new users.")
  (shared-secret
   (string "")
   "If set, it allows registration of standard or admin accounts by anyone
who has the shared secret, even if registration is otherwise disabled.")
  (secret-key
   maybe-string
   "A secret which is used to sign access tokens.  If none is specified, the
@code{registration_shared_secret} is used, if one is given; otherwise, a
secret key is derived from the signing key.")
  (postgresql-db?
   (boolean #f)
   "Whether to use a PostgreSQL database for storage.")
  (postgresql-db-password
   maybe-string
   "The password for the PostgreSQL database user.  Do note this will be
exposed under @file{/gnu/store} in plain sight.")
  (max-upload-size
   (string "50M")
   "The largest allowed upload size in bytes.")
  (data-directory
   (string "/var/lib/matrix-synapse")
   "Indicates the path where data such as the media store and database files
should be stored.")
  (config-directory
   (string "/var/lib/matrix-synapse")
   "Specifies where additional configuration files such as signing keys and
log configuration should be stored.")
  (trusted-key-servers
   (yaml-config '())
   "The trusted servers to download signing keys from.")
  (report-stats?
   (boolean #f)
   "Indicates whether or not to report anonymized homeserver usage
statistics.")
  (extra-config
   (yaml-config '())
   "Alist, vector, gexp, or file-like objects to write to a Synapse homeserver
configuration to be placed under @file{homeserver.yaml}. See more settings in
@uref{https://matrix-org.github.io/synapse/latest/usage/configuration/homeserver_sample_config.html}."))

(define (synapse-shepherd-service config)
  "Returns a Shepherd service for Synapse."
  (list
   (shepherd-service
    (provision '(synapse))
    (requirement (if (synapse-configuration-postgresql-db? config)
                     '(postgres)
                     '()))
    (start #~(make-forkexec-constructor
              (list (string-append #$(synapse-configuration-synapse config)
                                   "/bin/synapse_homeserver")
                    "-c"
                    #$(synapse-file config)
                    "--config-directory"
                    #$(synapse-configuration-config-directory config)
                    "--data-directory"
                    #$(synapse-configuration-data-directory config))
              #:log-file "/var/log/matrix-synapse.log"
              #:environment-variables
              (list "SSL_CERT_DIR=/etc/ssl/certs"
                    "SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt")))
    (stop #~(make-kill-destructor)))))

(define add-synapse-configuration
  (match-lambda
    (($ <synapse-configuration> _ server-name public-base-url
                                enable-registration? shared-secret
                                secret-key postgresql-db?
                                postgresql-db-password max-upload-size
                                data-directory config-directory
                                trusted-key-servers report-stats?
                                extra-config)
     (yaml-serialize
      `((pid_file . ,(string-append data-directory "/homeserver.pid"))
        (server_name . ,server-name)
        (public_base_url . ,public-base-url)
        (media_store_path . ,(string-append data-directory "/media_store"))
        (max_upload_size . ,max-upload-size)
        (registration_shared_secret . ,shared-secret)
        (macaroon_secret_key . ,(if (maybe-value-set? secret-key)
                                    secret-key
                                    shared-secret))
        (signing_key_path . ,(string-append config-directory
                                            "/homeserver.signing.key"))
        (enable_registration . ,enable-registration?)
        (report_stats . ,report-stats?)
        (database . ,(if postgresql-db?
                         `((name . psycopg2)
                           (allow_unsafe_locale . #t)
                           (args . ((user . "matrix-synapse")
                                    (database . "matrix-synapse")
                                    (host . localhost)
                                    (password . ,postgresql-db-password)
                                    (port . 5432)
                                    (cp_min . 5)
                                    (cp_max . 10))))
                         `((name . sqlite3)
                           (args . ((database . ,(string-append
                                                  data-directory
                                                  "/homeserver.db")))))))
        (listeners . #(((port . 8008)
                        (tls . #f)
                        (type . http)
                        (x_forwarded . true)
                        (bind_addresses . #("::1" "127.0.0.1"))
                        (resources . #(((names . (client federation))
                                        (compress . #f)))))))
        (trusted_key_servers . ,(if (null? trusted-key-servers)
                                    #(((server_name . "matrix.org")))
                                    trusted-key-servers))
        ,@extra-config)))))

(define %synapse-accounts
  (list
   (user-group
    (name "matrix-synapse")
    (system? #t))
   (user-account
    (name "matrix-synapse")
    (group "matrix-synapse")
    (system? #t)
    (home-directory "/var/empty")
    (shell (file-append shadow "/sbin/nologin")))))

(define (synapse-file config)
  (mixed-text-file "homeserver.yaml"
                   (add-synapse-configuration config)))

(define (synapse-etc-service config)
  `(("matrix-synapse/homeserver.yaml"
     ,(synapse-file config))))

(define (synapse-postgresql-service config)
  (if (synapse-configuration-postgresql-db? config)
    (list
     (postgresql-role
      (name "matrix-synapse")
      (create-database? #t)
      (collation "C")
      (ctype "C")
      (template "template0")))
    '()))

(define (synapse-activation-service config)
  #~(begin
      (use-modules (guix build utils))

      (define %user (getpw "matrix-synapse"))
      (define data-dir #$(synapse-configuration-data-directory config))
      (define config-dir #$(synapse-configuration-config-directory config))
      (define signing-key-path
        #$(string-append (synapse-configuration-config-directory config)
                         "/homeserver.signing.key"))
      (define (generate-signing-key)
        (unless (stat signing-key-path #f)
          (system* #$(file-append (synapse-configuration-synapse config)
                                  "/bin/generate_signing_key")
                   "-o"
                   signing-key-path)))

      (mkdir-p data-dir)
      (chown data-dir (passwd:uid %user) (passwd:gid %user))
      (chmod data-dir #o700)
      (mkdir-p config-dir)
      (chown config-dir (passwd:uid %user) (passwd:gid %user))
      (chmod config-dir #o700)
      (generate-signing-key)
      (chown signing-key-path (passwd:uid %user) (passwd:gid %user))
      (chmod signing-key-path #o600)))

(define-configuration/no-serialization synapse-extension
  (extra-config
   (yaml-config '())
   "See @code{synapse-service-type} for more information."))

(define (synapse-extension-service original-config extension-configs)
  (synapse-configuration
   (inherit original-config)
   (extra-config
    (append (synapse-configuration-extra-config original-config)
            (append-map synapse-extension-extra-config extension-configs)))))

(define (generate-synapse-documentation)
  (generate-documentation
   `((synapse-configuration
      ,synapse-configuration-fields))
   'synapse-configuration))

(define synapse-service-type
  (service-type
   (name 'synapse)
   (extensions
    (list
     (service-extension postgresql-role-service-type
                        synapse-postgresql-service)
     (service-extension etc-service-type
                        synapse-etc-service)
     (service-extension account-service-type
                        (const %synapse-accounts))
     (service-extension activation-service-type
                        synapse-activation-service)
     (service-extension shepherd-root-service-type
                        synapse-shepherd-service)))
   (compose identity)
   (extend synapse-extension-service)
   (default-value (synapse-configuration))
   (description "Configure and run the Synapse Matrix flagship homeserver.")))

(define-configuration/no-serialization mautrix-whatsapp-configuration
  (mautrix-whatsapp
   (file-like mautrix-whatsapp)
    "The @code{mautrix-whatsapp} package to use.")
  (address
   (string "http://localhost:8008")
   "The address this appservice can use to connect to the homeserver.")
  (domain
   (string "")
   "The domain of the homeserver (for MXIDs, etc).")
  (postgresql-db?
   (boolean #f)
   "Whether to use PostgreSQL as the database type.")
  (postgresql-db-password
   maybe-string
   "The password for the PostgreSQL database user.  Note this will be
exposed under @file{/gnu/store} in plain sight.")
  (encryption?
   (boolean #f)
   "Whether to add end-to-bridge encryption support.")
  (data-directory
   (string "/var/lib/mautrix-whatsapp")
   "The path where data such as the registration and database files should
be stored.")
  (log-directory
   (string "/var/log/mautrix-whatsapp")
   "The directory for @code{mautrix-whatsapp} log files.")
  (permissions
   (alist '())
   "The permissions for using the bridge. Permitted values include:
@itemize
@item relay - Talk through the relaybot (if enabled), no access otherwise
@item user - Access to use the bridge to chat with a WhatsApp account
@item admin - User level and some additional administration tools
@end itemize
Permitted keys include:
@itemize
@item * - All Matrix users
@item domain - All users on that homeserver
@item mxid - Specific user
@end itemize")
  (extra-config
   (yaml-config '())
   "Alist, vector, gexp, or file-like objects to write to a
@code{mautrix-whatsapp} bridge configuration to be placed under
@file{config.yaml}.  See more settings in
@uref{https://github.com/mautrix/whatsapp/blob/master/example-config.yaml}."))

(define %mautrix-whatsapp-accounts
  (list
   (user-group
    (name "mautrix-whatsapp")
    (system? #t))
   (user-account
    (name "mautrix-whatsapp")
    (group "mautrix-whatsapp")
    (system? #t)
    (home-directory "/var/empty")
    (shell (file-append shadow "/sbin/nologin")))))

(define (mautrix-whatsapp-postgresql-service config)
  (if (mautrix-whatsapp-configuration-postgresql-db? config)
      (list
       (postgresql-role
        (name "mautrix-whatsapp")
        (create-database? #t)
        (collation "C")
        (ctype "C")
        (template "template0")))
      '()))

(define add-mautrix-whatsapp-configuration
  (match-lambda
    (($ <mautrix-whatsapp-configuration> _ address domain postgresql-db?
                                         postgresql-db-password
                                         encryption? data-directory
                                         log-directory permissions
                                         extra-config)
     (yaml-serialize
      `((homeserver . ((address . ,address)
                       (domain . ,domain)))
        ,(cons 'appservice
               `((address . "http://localhost:29318")
                 (hostname . "0.0.0.0")
                 (port . 29318)
                 ,(cons
                   'database
                   (if postgresql-db?
                       `((type . postgres)
                         ,(cons
                           'uri
                           (string-append
                            "postgres://mautrix-whatsapp:"
                            postgresql-db-password
                            "@localhost/mautrix-whatsapp?sslmode=disable")))
                       `((type . sqlite3)
                         (uri . ,(string-append
                                  data-directory
                                  "/mautrix-whatsapp.db")))))
                 (id . whatsapp)
                 (bot . ((username . whatsappbot)
                         (displayname . "WhatsApp bridge bot")))
                 (as_token . "")
                 (hs_token . "")))
        ,(cons 'bridge
               `((username_template . "whatsapp_{{.}}")
                 ,(cons
                   'displayname_template
                   (string-append
                    "{{if .BusinessName}}{{.BusinessName}}"
                    "{{else if .PushName}}{{.PushName}}"
                    "{{else}}{{.JID}}{{end}} (WA)"))
                 (command_prefix . "!wa")
                 (permissions . (("*" . relay)
                                 ,@permissions))
                 (relay . ((enabled . #t)
                           (admin_only . #t)))
                 (encryption . ((allow . ,encryption?)
                                (default . ,encryption?)))))
        (logging . ((directory . ,(string-append log-directory "/logs"))
                    (file_name_format . "{{.Date}}-{{.Index}}.log")
                    (file_date_format . "2006-01-02")
                    (file_mode . 0384)
                    (timestamp_format . "Jan _2, 2006 15:04:05")
                    (print_level . debug)))
        ,@extra-config)))))

(define (mautrix-whatsapp-synapse-service config)
  (synapse-extension
   (extra-config
    `((app_service_config_files
       . #(,(string-append
             (mautrix-whatsapp-configuration-data-directory config)
             "/registration.yaml")))))))

(define (mautrix-whatsapp-activation-service config)
  (define mautrix-whatsapp-file
    (mixed-text-file "config.yaml"
                     (add-mautrix-whatsapp-configuration config)))

  #~(begin
      (use-modules (guix build utils))

      (define %user (getpw "mautrix-whatsapp"))
      (define data-dir
        #$(mautrix-whatsapp-configuration-data-directory config))
      (define log-dir #$(mautrix-whatsapp-configuration-log-directory config))
      (define registration-file (string-append data-dir "/registration.yaml"))
      (define config-file (string-append data-dir "/config.yaml"))
      (define (generate-registration-file)
        (unless (stat registration-file #f)
          (copy-file #$mautrix-whatsapp-file config-file)
          (system* #$(file-append
                      (mautrix-whatsapp-configuration-mautrix-whatsapp config)
                      "/bin/mautrix-whatsapp")
                   "--generate-registration"
                   "--config=" config-file
                   "--registration=" registration-file)))

      (mkdir-p data-dir)
      (chown data-dir (passwd:uid %user) (passwd:gid %user))
      (chmod data-dir #o700)
      (mkdir-p log-dir)
      (chown log-dir (passwd:uid %user) (passwd:gid %user))
      (chmod log-dir #o700)
      (generate-registration-file)
      (chmod registration-file #o640)
      (chown config-file (passwd:uid %user) (passwd:gid %user))))

(define (mautrix-whatsapp-shepherd-service config)
  (list
   (shepherd-service
    (provision '(mautrix-whatsapp))
    (requirement '(synapse))
    (start #~(make-forkexec-constructor
              (list
               (string-append
                #$(mautrix-whatsapp-configuration-mautrix-whatsapp config)
                "/bin/mautrix-whatsapp")
               (string-append
                "--config="
                #$(mautrix-whatsapp-configuration-data-directory config)
                "/config.yaml")
               (string-append
                "--registration="
                #$(mautrix-whatsapp-configuration-data-directory config)
                "/registration.yaml"))
              #:user "mautrix-whatsapp"
              #:group "mautrix-whatsapp"
              #:log-file "/var/log/mautrix-whatsapp/mautrix-whatsapp.log"))
    (stop #~(make-kill-destructor)))))

(define mautrix-whatsapp-service-type
  (service-type
   (name 'mautrix-whatsapp)
   (extensions
    (list
     (service-extension account-service-type
                        (const %mautrix-whatsapp-accounts))
     (service-extension postgresql-role-service-type
                        mautrix-whatsapp-postgresql-service)
     (service-extension synapse-service-type
                        mautrix-whatsapp-synapse-service)
     (service-extension activation-service-type
                        mautrix-whatsapp-activation-service)
     (service-extension shepherd-root-service-type
                        mautrix-whatsapp-shepherd-service)))
   (default-value (mautrix-whatsapp-configuration))
   (description "Configure the Matrix-WhatsApp puppetting bridge.  Note that
changing any of the values in @code{mautrix-whatsapp-configuration} requires
regeneration of the registration settings, which you may do by removing
@file{/var/lib/mautrix-whatsapp/registration.yaml} and restarting the
service.")))
-- 
2.41.0

-- 
Best regards,
Miguel Ángel Moreno
Thank you for working on this, appreciate it.
Hi Andrew,

Amended the case name transforms as mentioned in the YAML serializer
thread.  This <https://issues.guix.gnu.org/62495> patch has been
recently merged upstream, so the only withstanding patches needed for
this patch series are <https://issues.guix.gnu.org/62284> and
<https://issues.guix.gnu.org/62389>.  Would you mind having a look at
them, please?

Over the last months, I've come across many people in the help-guix/IRC
channels that are interested in the YAML serializer and the Synapse
system service, so we should get the ball rolling on these.

[PATCH 2/2] rde: Add feature-synapse. Export this patch

---
 src/rde/features/matrix.scm | 90 +++++++++++++++++++++++++++++++++++++
 1 file changed, 90 insertions(+)
 create mode 100644 src/rde/features/matrix.scm

diff --git a/src/rde/features/matrix.scm b/src/rde/features/matrix.scm
new file mode 100644
index 00000000..9200d9cb
--- /dev/null
+++ b/src/rde/features/matrix.scm
@@ -0,0 +1,90 @@
(define-module (rde features matrix)
  #:use-module (rde features)
  #:use-module (rde features emacs)
  #:use-module (rde features predicates)
  #:use-module (rde features web)
  #:use-module (rde home services matrix)
  #:use-module (rde system services matrix)
  #:use-module (gnu home services)
  #:use-module (gnu services)
  #:use-module (gnu services certbot)
  #:use-module (gnu services configuration)
  #:use-module (gnu services web)
  #:use-module (gnu packages emacs-xyz)
  #:use-module (gnu packages matrix)
  #:use-module (guix gexp)
  #:use-module (srfi srfi-1)
  #:export (feature-synapse))

(define* (feature-synapse
          #:key
          (whatsapp-bridge? #f))
  "Set up a live instance of the Synapse Matrix flagship home server.
If WHATSAPP-BRIDGE?, configure the @code{mautrix-whatsapp} puppetting
bridge to relay messages to/from WhatsApp."
  (ensure-pred boolean? whatsapp-bridge?)

  (define (get-system-services config)
    "Return system services related to Synapse."
    (require-value 'matrix-settings config)
    (define homeserver (get-value 'matrix-homeserver config))
    (define server-name
      (string-drop homeserver (+ 1 (string-index-right homeserver #\/))))
    (define domain
      (string-drop server-name (+ 1 (string-index server-name #\.))))
    (define letsencrypt-dir
      (and domain (string-append "/etc/letsencrypt/live/matrix." domain)))

    (append
     (list
      (service synapse-service-type
               (get-value 'synapse-configuration config)))
     (if whatsapp-bridge?
         (list
          (service mautrix-whatsapp-service-type
                   (get-value 'mautrix-whatsapp-configuration config)))
         '())
     (if (get-value 'nginx config)
         (list
          (simple-service
           'add-synapse-nginx-configuration
           nginx-service-type
           (list
            (nginx-server-configuration
             (listen '("443 ssl http2"
                       "[::]:443 ssl http2"
                       "8448 ssl http2 default_server"
                       "[::]:8448 ssl http2 default_server"))
             (server-name (list server-name))
             (ssl-certificate
              (string-append letsencrypt-dir "/fullchain.pem"))
             (ssl-certificate-key
              (string-append letsencrypt-dir "/privkey.pem"))
             (locations
              (list
               (nginx-location-configuration
                (uri "~ ^(/_matrix|/_synapse/client)")
                (body
                 (list "proxy_pass http://localhost:8008;"
                       "proxy_set_header X-Forwarded-For $remote_addr;"
                       "proxy_set_header X-Forwarded-Proto $scheme;"
                       "proxy_set_header Host $host;"
                       "client_max_body_size 50M;")))
               %letsencrypt-acme-challenge))))))
         '())
     (if (get-value 'certbot config)
         (list
          (simple-service
           'add-synapse-ssl
           certbot-service-type
           (list
            (certificate-configuration
             (domains (list server-name))
             (deploy-hook %nginx-deploy-hook)))))
         '())))

  (feature
   (name 'synapse)
   (values `((synapse . #t)
             (matrix-whatsapp-bridge? . ,whatsapp-bridge?)))
   (system-services-getter get-system-services)))
-- 
2.39.2

-- 
Best regards,
Miguel Moreno