This is an automated email from the git hooks/post-receive script.

guix_mirror_bot pushed a commit to branch master
in repository guix.

The following commit(s) were added to refs/heads/master by this push:
     new 520785e315 gnu: Add soju-service-type.
520785e315 is described below

commit 520785e315eddbe47199ac557e88e60eca3ae97c
Author: Giacomo Leidi <[email protected]>
AuthorDate: Sat Jan 10 17:23:44 2026 +0100

    gnu: Add soju-service-type.
    
    * gnu/services/messaging.scm (%default-soju-shepherd-requirement): New
    variable.
    (soju-ssl-certificate): New configuration record.
    (soju-database): New configuration record.
    (soju-configuration): New configuration record.
    (serialize-soju-configuration,soju-activation,soju-accounts,
    soju-shepherd-services): New procedures.
    (soju-service-type): New service.
    (serialize-ngircd-configuration): Reformat.
    (pounce-configuration): Reformat.
    * doc/guix.texi: Document the new soju service.
    * gnu/tests/messaging.scm: Test the new soju service.
    
    Change-Id: I6223ecac1aaaab76bd75461851ffe4cec0678118
---
 doc/guix.texi              | 214 ++++++++++++++++++++++++++++
 gnu/services/messaging.scm | 346 ++++++++++++++++++++++++++++++++++++++++++++-
 gnu/tests/messaging.scm    | 112 ++++++++++++++-
 3 files changed, 668 insertions(+), 4 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 12e9d3465a..44873afa2a 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -32001,6 +32001,220 @@ Will be created if it doesn't exist.
 @end table
 @end deftp
 
+@subsubheading Soju Service
+
+@cindex IRC (Internet Relay Chat)
+@cindex bouncer, IRC
+@cindex Bounced Network Connection, BNC
+@url{https://soju.im/, soju} is a user-friendly IRC bouncer.  soju connects to
+upstream IRC servers on behalf of the user to provide extra functionality.  
soju
+supports many features such as multiple users, numerous
+@url{https://ircv3.net/, IRCv3} extensions, chat history playback and detached
+channels.  It is well-suited for both small and large deployments.
+
+@defvar soju-service-type
+This is the service type for the soju IRC bouncer.  Its value is a
+@code{soju-configuration} configuration instance, which is documented
+below.
+
+@cindex IRC bouncer configuration for Libera.Chat
+@cindex Libera.Chat, IRC bouncer configuration
+The following example configures soju to act as an IRC bouncer with an 
encrypted
+connection.
+
+@lisp
+(define %soju-certbot-deploy-hook
+  (program-file "soju-certbot-deploy-hook.scm"
+    (with-imported-modules '((gnu services herd))
+      #~(begin
+         (use-modules (gnu services herd)
+          (with-shepherd-action 'soju ('reload) result result))))))
+
+(operating-system
+
+  @dots{}
+
+  (services
+   (list @dots{}
+         (service certbot-service-type
+                  (certbot-configuration
+                   (email "your@@email.org")
+                   (certificates
+                    (list
+                     (certificate-configuration
+                      (domains (list "your.domain.org"))
+                      (deploy-hook %soju-certbot-deploy-hook))))))
+
+         (service soju-service-type
+                  (soju-configuration
+                   (hostname "your.domain.org")
+                   ;; The UNIX socket allows administering the bouncer
+                   ;; from the command line with sojuctl.
+                   (listen '("ircs://" "unix+admin:///var/lib/soju/soju.sock"))
+                   (ssl-certificate
+                    (soju-ssl
+                     (certificate "/etc/certs/your.domain.org/fullchain.pem")
+                     (certificate-key 
"/etc/certs/your.domain.org/privkey.pem")))
+                   (title "Your IRC bouncer"))))))
+@end lisp
+
+@end defvar
+
+@c %start of fragment
+
+@deftp {Data Type} soju-configuration
+Available @code{soju-configuration} fields are:
+
+@table @asis
+@item @code{soju} (default: @code{soju}) (type: package)
+Soju package to use for the service.
+
+@item @code{debug?} (default: @code{#f}) (type: boolean)
+Enable debug logging (this will leak sensitive information such as
+passwords).  This can be overriden at run time with the service command
+@code{server debug}.
+
+@item @code{listen} (default: @code{'(":6697")}) (type: list-of-strings)
+Listening URI.  The following URIs are supported:
+
+@itemize
+
+@item @code{[ircs://][host][:port]} listens with TLS over TCP (default
+port if omitted: 6697)
+@item @code{irc://localhost[:port]} listens with plain-text over TCP (default
+port if omitted: 6667, host must be @code{"localhost"})
+@item @code{irc+insecure://[host][:port]} listens with plain-text over TCP
+(default port if omitted: 6667)
+@item @code{unix://<path>} listens on a Unix domain socket
+@item @code{https://[host][:port]} listens for HTTPS connections (default
+port: 443) and handles the following requests: @code{/socket} for
+WebSocket and @code{/uploads} (and subdirectories) for file uploads
+@item @code{http://localhost[:port]} listens for plain-text HTTP
+connections (default port: 80, host must be @code{"localhost"}) and
+handles requests like @code{https://} does
+@item @code{http+insecure://[host][:port]} listens for plain-text HTTP
+connections (default port: 80) and handles requests like @code{https://}
+does @item @code{http+unix://<path>} listens for plain-text HTTP
+connections on a Unix domain socket and handles requests like
+@code{https://} does
+@item @code{wss://[host][:port]} listens for WebSocket connections over TLS
+(default port: 443)
+@item  @code{ws://localhost[:port]} listens for plain-text WebSocket 
connections
+(default port: 80, host must be @code{"localhost"})
+@item @code{ws+insecure://[host][:port]} listens for plain-text WebSocket
+connections (default port: 80)
+@item @code{ws+unix://<path>} listens for plain-text WebSocket connections on a
+Unix domain socket
+@item @code{ident://[host][:port]} listens for plain-text ident connections
+(default port: 113)
+@item @code{http+prometheus://localhost:<port>} listens for plain-text HTTP
+connections and serves Prometheus metrics (host must be @code{"localhost"})
+@item @code{http+pprof://localhost:<port>} listens for plain-text HTTP
+connections and serves pprof runtime profiling data (host must be
+@code{"localhost"}).  For more information, see
+@uref{https://pkg.go.dev/net/http/pprof,upstream} documentation
+@item @code{unix+admin://[path]} listens on a Unix domain socket for
+administrative connections, such as sojuctl (default path:
+@code{/run/soju/admin})
+
+@end itemize
+
+If the scheme is omitted, @code{ircs} is assumed.  If multiple @code{listen}
+values are specified, soju will listen on each of them.
+
+@item @code{hostname} (type: maybe-string)
+Server hostname, it defaults to the system hostname.  This should be set
+to a fully qualified domain name.
+
+@item @code{title} (type: string)
+Server title.  This will be sent as the @code{ISUPPORT NETWORK} value
+when clients don't select a specific network.
+
+@item @code{ssl-certificate} (type: maybe-soju-ssl)
+Where to find the private key for secure connections.  If set, this field
+will have the service run under root privileges.
+
+@item @code{shepherd-requirement} (default: @code{'(user-processes loopback)}) 
(type: list)
+A list of Shepherd services to use.  Add extra dependencies to
+@code{%default-soju-shepherd-requirement} to extend its value.
+
+@item @code{db} (default: @code{(soju-database)}) (type: soju-database)
+The database where soju will write its state.
+
+@item @code{log-file} (default: @code{"/var/log/soju.log"}) (type: string)
+The name of the file where soju will write its logs.
+
+@item @code{extra-content} (default: @code{'()}) (type: text-config)
+Extra content to append to the configuration as-is.
+
+@end table
+
+@end deftp
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} soju-ssl
+Available @code{soju-ssl} fields are:
+
+@table @asis
+
+@item @code{certificate} (type: string)
+Where to find the certificate for secure connections.
+
+@item @code{key} (type: string)
+Where to find the private key for secure connections.
+
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} soju-database
+Available @code{soju-database} fields are:
+
+@table @asis
+
+@item @code{datadir} (default: @code{"/var/lib/soju"}) (type: string)
+The name of the directory where soju will write its state.
+
+@item @code{driver} (default: @code{'sqlite3}) (type: symbol)
+Set the database driver for user, network and channel storage.  Supported
+drivers:
+
+@itemize
+@item @code{'sqlite3}
+@item @code{'postgres}
+@end itemize
+
+@item @code{source} (type: maybe-string)
+Set the database location for user, network and channel storage.  By
+default, a sqlite3 database is opened in the directory specified in the
+@code{datadir} field.  In general the driver expect the following:
+
+@itemize
+@item @code{'sqlite3} expects source to be a path to
+the SQLite file
+@item @code{'postgres} expects source to be a
+space-separated list of @code{key=value} parameters, e.g.
+@code{"host=/run/postgresql dbname=soju"}.  Note that @code{sslmode
+defaults} to @code{require}.  For more information on connection
+strings, see
+@uref{https://pkg.go.dev/github.com/lib/pq#hdr-Connection_String_Parameters,upstream}
+'s documentation.
+@end itemize
+@end table
+
+@end deftp
+
+
+@c %end of fragment
+
 @node Telephony Services
 @subsection Telephony Services
 
diff --git a/gnu/services/messaging.scm b/gnu/services/messaging.scm
index 1fbb1f1d85..ab89338969 100644
--- a/gnu/services/messaging.scm
+++ b/gnu/services/messaging.scm
@@ -5,6 +5,7 @@
 ;;; Copyright © 2018 Pierre-Antoine Rouby <[email protected]>
 ;;; Copyright © 2025 Maxim Cournoyer <[email protected]>
 ;;; Copyright © 2024 Evgeny Pisemsky <[email protected]>
+;;; Copyright © 2026 Giacomo Leidi <[email protected]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -30,12 +31,15 @@
   #:autoload   (gnu packages rust-apps) (mollysocket)
   #:use-module (gnu packages tls)
   #:use-module (gnu services)
+  #:use-module (gnu services admin)
   #:use-module (gnu services shepherd)
   #:use-module (gnu services configuration)
   #:use-module (gnu system shadow)
   #:autoload   (gnu build linux-container) (%namespaces)
   #:use-module ((gnu system file-systems) #:select (file-system-mapping))
+  #:use-module (guix diagnostics)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
   #:use-module (guix modules)
   #:use-module (guix records)
   #:use-module (guix packages)
@@ -208,7 +212,43 @@
             mollysocket-configuration-allowed-uuids
             mollysocket-configuration-db
             mollysocket-configuration-vapid-key-file
-            mollysocket-service-type))
+            mollysocket-service-type
+
+            %default-soju-shepherd-requirement
+
+            soju-ssl
+            soju-ssl?
+            soju-ssl-fields
+            soju-ssl-certificate
+            soju-ssl-key
+
+            soju-database
+            soju-database?
+            soju-database-fields
+            soju-database-datadir
+            soju-database-driver
+            soju-database-source
+
+            soju-configuration
+            soju-configuration?
+            soju-configuration-fields
+            soju-configuration-soju
+            soju-configuration-debug?
+            soju-configuration-listen
+            soju-configuration-hostname
+            soju-configuration-title
+            soju-configuration-ssl-certificate
+            soju-configuration-log-file
+            soju-configuration-shepherd-requirement
+            soju-configuration-database
+            soju-configuration-extra-content
+
+            soju-configuration->mixed-text-file
+            soju-activation
+            soju-accounts
+            soju-shepherd-services
+
+            soju-service-type))
 
 ;;; Commentary:
 ;;;
@@ -1570,7 +1610,7 @@ values."
            ;; Ensure stdin is not connected to a TTY source to avoid ngircd
            ;; configtest blocking with a confirmation prompt.
            (parameterize ((current-input-port (%make-void-port "r")))
-             (invoke #+ngircd "--config" #$ngircd.conf "--configtest" ))
+             (invoke #+ngircd "--config" #$ngircd.conf "--configtest"))
            (copy-file #$ngircd.conf #$output))))))
 
 (define (ngircd-wrapper config)
@@ -1804,7 +1844,7 @@ reconnecting client.  The size must be a power of two.")
    maybe-string
    "Host to bind the @emph{source} address to when connecting to the server.
 To connect from any address over IPv4 only, use @samp{0.0.0.0}.  To connect
-from any address over IPv6 only, use @samp{::}." )
+from any address over IPv6 only, use @samp{::}.")
 
   (host
    string
@@ -2388,3 +2428,303 @@ if it does not exist.")
                                          mollysocket-activation-service)))
     (default-value (mollysocket-configuration))
     (description "UnifiedPush provider for the Signal client Molly.")))
+
+
+;;;
+;;; Soju
+;;;
+
+(define %default-soju-shepherd-requirement
+  '(user-processes loopback))
+
+(define-configuration soju-ssl
+  (certificate
+   (string)
+   "Where to find the certificate for secure connections."
+   (serializer empty-serializer))
+  (key
+   (string)
+   "Where to find the private key for secure connections."
+   (serializer empty-serializer)))
+
+(define (soju-serialize-soju-ssl name value)
+  #~(string-append "tls "
+                   #$(soju-ssl-certificate value)
+                   " "
+                   #$(soju-ssl-key value)
+                   "\n"))
+
+(define-maybe soju-ssl (prefix soju-))
+
+(define (soju-sanitize-db-driver value)
+  (if (or (eq? value 'sqlite3)
+          (eq? value 'postgres))
+      value
+      (raise
+       (formatted-message
+        (G_ "db-driver can be either 'sqlite3 or 'postgres but ~a was found")
+        value))))
+
+(define (soju-serialize-symbol name value) (symbol->string value))
+
+(define (soju-serialize-string name value)
+  (define quoted-value
+    #~(if (string-contains #$value " ")
+          (string-append "\"" #$value "\"")
+          #$value))
+  #~(string-append (symbol->string '#$name) " " #$quoted-value "\n"))
+
+(define soju-serialize-package serialize-package)
+
+(define-maybe string (prefix soju-))
+
+(define-configuration soju-database
+  (datadir
+   (string "/var/lib/soju")
+   "The name of the directory where soju will write its state.")
+  (driver
+   (symbol 'sqlite3)
+   "Set the database driver for user, network and channel storage.
+
+Supported drivers:
+
+@itemize
+@item @code{'sqlite3}
+@item @code{'postgres}
+@end itemize"
+   (sanitizer soju-sanitize-db-driver)
+   (serializer soju-serialize-symbol))
+  (source
+   (maybe-string)
+   "Set the database location for user, network and channel storage.  By
+default, a sqlite3 database is opened in the directory specified in the
+@code{datadir} field.
+
+In general the driver expect the following:
+
+@itemize
+@item @code{'sqlite3} expects source to be a path to the SQLite file
+@item @code{'postgres} expects source to be a space-separated list of
+@code{key=value} parameters, e.g. @code{\"host=/run/postgresql dbname=soju\"}.
+Note that @code{sslmode defaults} to @code{require}.  For more information on
+connection strings, see
+@url{https://pkg.go.dev/github.com/lib/pq#hdr-Connection_String_Parameters,
+upstream}'s documentation.
+@end itemize"
+   (serializer soju-serialize-maybe-string)))
+
+(define (soju-db-source driver source datadir)
+  (if (not (string=? "" source))
+      source
+      (if (string=? driver "sqlite3")
+          (string-append datadir "/soju.db")
+          (raise
+           (G_ "db-source can't be empty when db-driver is set to 'postgres.
+Make sure to pass a connection string")))))
+
+(define (soju-serialize-soju-database name config)
+  (define fields
+    (filter-configuration-fields
+     soju-database-fields
+     '(driver source)))
+  (define getters
+    (map configuration-field-getter fields))
+  (define names
+    (map configuration-field-name fields))
+  (define serializers
+    (map configuration-field-serializer fields))
+  (define values
+    (map (match-lambda ((serializer name getter)
+                        (serializer name (getter config))))
+         (zip serializers names getters)))
+
+  #~(string-append "db " #$(first values)
+                   " " #$(soju-db-source
+                          (first values)
+                          (second values)
+                          (soju-database-datadir config))
+                       "\n"))
+
+(define (soju-serialize-list-of-strings name value)
+  #~(string-append
+     (string-join (map (lambda (l) (string-append "listen " l))
+                       (list #$@value))
+                  "\n")
+     "\n"))
+
+
+(define soju-serialize-text-config serialize-text-config)
+
+(define-configuration soju-configuration
+  (soju
+   (package soju)
+   "Soju package to use for the service."
+   (serializer empty-serializer))
+  (debug?
+   (boolean #f)
+   "Enable debug logging (this will leak sensitive information such as
+passwords).  This can be overriden at run time with the service command
+@code{server debug}."
+   (serializer empty-serializer))
+  (listen
+   (list-of-strings '(":6697"))
+   "Listening URI.  The following URIs are supported:
+
+@itemize
+
+@item @code{[ircs://][host][:port]} listens with TLS over TCP (default port if
+omitted: 6697)
+@item @code{irc://localhost[:port]} listens with plain-text over TCP (default
+port if omitted: 6667, host must be @code{\"localhost\"})
+@item @code{irc+insecure://[host][:port]} listens with plain-text over TCP
+(default port if omitted: 6667)
+@item @code{unix://<path>} listens on a Unix domain socket
+@item @code{https://[host][:port]} listens for HTTPS connections (default port:
+443) and handles the following requests: @code{/socket} for WebSocket and
+@code{/uploads} (and subdirectories) for file uploads
+@item @code{http://localhost[:port]} listens for plain-text HTTP connections
+(default port: 80, host must be @code{\"localhost\"}) and handles requests like
+@code{https://} does
+@item @code{http+insecure://[host][:port]} listens for plain-text HTTP
+connections (default port: 80) and handles requests like @code{https://} does
+@item @code{http+unix://<path>} listens for plain-text HTTP connections on a
+Unix domain socket and handles requests like @code{https://} does
+@item @code{wss://[host][:port]} listens for WebSocket connections over TLS
+(default port: 443)
+@item @code{ws://localhost[:port]} listens for plain-text WebSocket connections
+(default port: 80, host must be @code{\"localhost\"})
+@item @code{ws+insecure://[host][:port]} listens for plain-text WebSocket
+connections (default port: 80)
+@item @code{ws+unix://<path>} listens for plain-text WebSocket connections on a
+Unix domain socket
+@item @code{ident://[host][:port]} listens for plain-text ident connections
+(default port: 113)
+@item @code{http+prometheus://localhost:<port>} listens for plain-text HTTP
+connections and serves Prometheus metrics (host must be @code{\"localhost\"})
+@item @code{http+pprof://localhost:<port>} listens for plain-text HTTP
+connections and serves pprof runtime profiling data (host must be
+@code{\"localhost\"}). For more information, see
+@url{https://pkg.go.dev/net/http/pprof,upstream} documentation
+@item @code{unix+admin://[path]} listens on a Unix domain socket for
+administrative connections, such as sojuctl (default path:
+@code{/run/soju/admin})
+
+@end itemize
+
+If the scheme is omitted, @code{ircs} is assumed.  If multiple @code{listen}
+values are specified, soju will listen on each of them.")
+  (hostname
+   (maybe-string)
+   "Server hostname, it defaults to the system hostname.  This should be set to
+a fully qualified domain name.")
+  (title
+   (string)
+   "Server title. This will be sent as the @code{ISUPPORT NETWORK} value when
+clients don't select a specific network.")
+  (ssl-certificate
+   (maybe-soju-ssl)
+   "Where to find the private key for secure connections.  If set, this field
+will have the service run under root privileges.")
+  (shepherd-requirement
+   (list %default-soju-shepherd-requirement)
+   "A list of Shepherd services to use.  Add extra dependencies to
+@code{%default-soju-shepherd-requirement} to extend its value."
+   (serializer empty-serializer))
+  (log-file
+   (string "/var/log/soju.log")
+   "The name of the file where soju will write its logs."
+   (serializer empty-serializer))
+  (db
+   (soju-database (soju-database))
+   "The database where soju will write its state.")
+  (extra-content
+   (text-config '())
+   "Extra content to append to the configuration as-is.")
+  (prefix soju-))
+
+(define (soju-needs-privileges? config)
+  ;; NOTE: Certificates on the Guix System are only readable by root. In case a
+  ;; certificate and a private key are passed no unprivileged users will be
+  ;; added to the system.
+  (maybe-value-set?
+   (soju-configuration-ssl-certificate config)))
+
+(define (serialize-soju-configuration config)
+  (mixed-text-file "soju.conf"
+   (serialize-configuration
+    config
+    (filter-configuration-fields
+     soju-configuration-fields
+     '(soju debug? shepherd-requirement log-file)
+     #t))))
+
+(define (soju-activation config)
+  (let ((datadir (soju-database-datadir
+                  (soju-configuration-db config)))
+        (user-name
+         (if (soju-needs-privileges? config) "root" "soju")))
+    #~(let* ((user (getpwnam #$user-name))
+             (uid (passwd:uid user))
+             (gid (passwd:gid user))
+             (datadir #$datadir))
+        ;; Setup datadir
+        (mkdir-p datadir)
+        (chown datadir uid gid)
+        (for-each (lambda (f) (chown f uid gid))
+                  (find-files datadir ".*")))))
+
+(define (soju-accounts config)
+  (if (soju-needs-privileges? config)
+      '()
+      (list (user-group (name "soju") (system? #t))
+            (user-account
+              (name "soju")
+              (group "soju")
+              (system? #t)
+              (comment "soju server user")
+              (home-directory "/var/empty")
+              (shell (file-append shadow "/sbin/nologin"))))))
+
+(define (soju-shepherd-services config)
+  (match-record config <soju-configuration>
+                (soju debug? log-file shepherd-requirement)
+    (let ((user-name (if (soju-needs-privileges? config) "root" "soju"))
+          (soju-binary (file-append soju "/bin/soju"))
+          (config-file (serialize-soju-configuration config)))
+      (list (shepherd-service
+               (provision '(soju))
+               (documentation "Run the soju daemon.")
+               (requirement shepherd-requirement)
+               (start #~(make-forkexec-constructor
+                           (list #$soju-binary
+                                  #$@(if debug? '("-debug") '())
+                                  "-config" #$config-file)
+                           #:log-file #$log-file
+                           #:user #$user-name
+                           #:group #$user-name))
+               (stop #~(make-kill-destructor))
+               (actions
+                (list
+                 (shepherd-action
+                   (name 'reload)
+                   (documentation "Reload soju configuration file and restart
+it.  It is useful for situations where the same soju configuration file can
+point to different things after a reload, such as renewed TLS certificates.")
+                   (procedure
+                    #~(lambda (process . args)
+                        (kill (process-id process) SIGHUP))))
+                 (shepherd-configuration-action config-file))))))))
+
+(define soju-service-type
+  (service-type (name 'soju)
+                (extensions
+                 (list (service-extension shepherd-root-service-type
+                                          soju-shepherd-services)
+                       (service-extension log-rotation-service-type
+                                          (compose
+                                           list soju-configuration-log-file))
+                       (service-extension activation-service-type
+                                          soju-activation)
+                       (service-extension account-service-type
+                                          soju-accounts)))
+                (description "Run the soju IRC bouncer.")))
diff --git a/gnu/tests/messaging.scm b/gnu/tests/messaging.scm
index 83ccca8891..559dc81a74 100644
--- a/gnu/tests/messaging.scm
+++ b/gnu/tests/messaging.scm
@@ -4,6 +4,7 @@
 ;;; Copyright © 2018 Efraim Flashner <[email protected]>
 ;;; Copyright © 2025 Maxim Cournoyer <[email protected]>
 ;;; Copyright © 2025 Evgeny Pisemsky <[email protected]>
+;;; Copyright © 2026 Giacomo Leidi <[email protected]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -44,7 +45,8 @@
             %test-ngircd
             %test-pounce
             %test-quassel
-            %test-mosquitto))
+            %test-mosquitto
+            %test-soju))
 
 (define (run-xmpp-test name xmpp-service pid-file create-account)
   "Run a test of an OS running XMPP-SERVICE, which writes its PID to PID-FILE."
@@ -669,3 +671,111 @@ OPENSSL:localhost:7000,verify=0 &")
    (name "mosquitto")
    (description "Test a running Mosquitto MQTT broker.")
    (value (run-mosquitto-test))))
+
+
+;;;
+;;; soju.
+;;;
+
+(define %soju-os
+  (operating-system
+    (inherit %simple-os)
+    (packages (cons* soju %base-packages))
+    (services
+     (cons*
+      (service dhcpcd-service-type)
+      (service soju-service-type
+               (soju-configuration
+                (debug? #t)
+                (listen '("irc://localhost" 
"unix+admin:///var/lib/soju/soju.sock"))
+                (title "soju IRC bouncer")
+                (extra-content
+                 (list (plain-file "soju.conf" "hostname test.example.org")))))
+      %base-services))))
+
+(define (run-soju-test)
+  (define vm
+    (virtual-machine
+     (operating-system
+       (marionette-operating-system
+        %soju-os
+        #:imported-modules (source-module-closure
+                            '((gnu services herd)))))))
+
+  (define test
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (srfi srfi-64)
+                       (gnu build marionette))
+
+          (define marionette
+            (make-marionette (list #$vm)))
+
+          (test-runner-current (system-test-runner #$output))
+          (test-begin "soju")
+
+          (marionette-eval
+            '(begin
+               (use-modules (gnu services herd))
+               (wait-for-service 'user-processes))
+            marionette)
+
+          (test-equal "soju configuration file is well formed"
+            "listen irc://localhost
+listen unix+admin:///var/lib/soju/soju.sock
+title \"soju IRC bouncer\"
+db sqlite3 /var/lib/soju/soju.db
+hostname test.example.org
+"
+            (marionette-eval
+              '(begin
+                 (use-modules (ice-9 popen)
+                              (ice-9 rdelim)
+                              (ice-9 textual-ports))
+                 (let* ((port (open-input-pipe "herd configuration soju"))
+                        (soju.conf (string-trim-both (read-line port))))
+                   (close-pipe port)
+                   (call-with-input-file soju.conf get-string-all)))
+              marionette))
+
+          (test-assert "soju service runs"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'soju))
+             marionette))
+
+          (test-assert "soju listens on TCP port 6667"
+            (wait-for-tcp-port 6667 marionette))
+
+          (test-equal "sojuctl can create a user"
+            "fishinthecalculator (admin): 0 networks"
+            (marionette-eval
+              '(begin
+                 (use-modules (ice-9 popen)
+                              (ice-9 rdelim))
+                 (system (string-join
+                          '("sojuctl" "-config" "$(herd configuration soju)"
+                            "user" "create" "-admin"
+                            "-username" "fishinthecalculator"
+                            "-password" "1234")
+                          " "))
+                 (let* ((port (open-input-pipe (string-join
+                                                '("sojuctl" "-config"
+                                                  "$(herd configuration soju)"
+                                                  "user" "status")
+                                                " ")))
+                        (msg (read-line port)))
+                   (close-pipe port)
+                   msg))
+              marionette))
+
+          (test-end))))
+
+  (gexp->derivation "soju-test" test))
+
+(define %test-soju
+  (system-test
+   (name "soju")
+   (description "Run a soju IRC bouncer.")
+   (value (run-soju-test))))

Reply via email to