summaryrefslogtreecommitdiff
path: root/gnu
diff options
context:
space:
mode:
authorGiacomo Leidi <therewasa@fishinthecalculator.me>2026-01-10 17:23:44 +0100
committerGiacomo Leidi <therewasa@fishinthecalculator.me>2026-03-09 23:17:24 +0100
commit520785e315eddbe47199ac557e88e60eca3ae97c (patch)
tree5af120ab54fdc523729e13cde06d13f197358710 /gnu
parent1a109f379835315c0a4ea23b1ff52d0e77d7fede (diff)
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
Diffstat (limited to 'gnu')
-rw-r--r--gnu/services/messaging.scm346
-rw-r--r--gnu/tests/messaging.scm112
2 files changed, 454 insertions, 4 deletions
diff --git a/gnu/services/messaging.scm b/gnu/services/messaging.scm
index 1fbb1f1d857..ab89338969c 100644
--- a/gnu/services/messaging.scm
+++ b/gnu/services/messaging.scm
@@ -5,6 +5,7 @@
5;;; Copyright © 2018 Pierre-Antoine Rouby <contact@parouby.fr> 5;;; Copyright © 2018 Pierre-Antoine Rouby <contact@parouby.fr>
6;;; Copyright © 2025 Maxim Cournoyer <maxim@guixotic.coop> 6;;; Copyright © 2025 Maxim Cournoyer <maxim@guixotic.coop>
7;;; Copyright © 2024 Evgeny Pisemsky <mail@pisemsky.site> 7;;; Copyright © 2024 Evgeny Pisemsky <mail@pisemsky.site>
8;;; Copyright © 2026 Giacomo Leidi <therewasa@fishinthecalculator.me>
8;;; 9;;;
9;;; This file is part of GNU Guix. 10;;; This file is part of GNU Guix.
10;;; 11;;;
@@ -30,12 +31,15 @@
30 #:autoload (gnu packages rust-apps) (mollysocket) 31 #:autoload (gnu packages rust-apps) (mollysocket)
31 #:use-module (gnu packages tls) 32 #:use-module (gnu packages tls)
32 #:use-module (gnu services) 33 #:use-module (gnu services)
34 #:use-module (gnu services admin)
33 #:use-module (gnu services shepherd) 35 #:use-module (gnu services shepherd)
34 #:use-module (gnu services configuration) 36 #:use-module (gnu services configuration)
35 #:use-module (gnu system shadow) 37 #:use-module (gnu system shadow)
36 #:autoload (gnu build linux-container) (%namespaces) 38 #:autoload (gnu build linux-container) (%namespaces)
37 #:use-module ((gnu system file-systems) #:select (file-system-mapping)) 39 #:use-module ((gnu system file-systems) #:select (file-system-mapping))
40 #:use-module (guix diagnostics)
38 #:use-module (guix gexp) 41 #:use-module (guix gexp)
42 #:use-module (guix i18n)
39 #:use-module (guix modules) 43 #:use-module (guix modules)
40 #:use-module (guix records) 44 #:use-module (guix records)
41 #:use-module (guix packages) 45 #:use-module (guix packages)
@@ -208,7 +212,43 @@
208 mollysocket-configuration-allowed-uuids 212 mollysocket-configuration-allowed-uuids
209 mollysocket-configuration-db 213 mollysocket-configuration-db
210 mollysocket-configuration-vapid-key-file 214 mollysocket-configuration-vapid-key-file
211 mollysocket-service-type)) 215 mollysocket-service-type
216
217 %default-soju-shepherd-requirement
218
219 soju-ssl
220 soju-ssl?
221 soju-ssl-fields
222 soju-ssl-certificate
223 soju-ssl-key
224
225 soju-database
226 soju-database?
227 soju-database-fields
228 soju-database-datadir
229 soju-database-driver
230 soju-database-source
231
232 soju-configuration
233 soju-configuration?
234 soju-configuration-fields
235 soju-configuration-soju
236 soju-configuration-debug?
237 soju-configuration-listen
238 soju-configuration-hostname
239 soju-configuration-title
240 soju-configuration-ssl-certificate
241 soju-configuration-log-file
242 soju-configuration-shepherd-requirement
243 soju-configuration-database
244 soju-configuration-extra-content
245
246 soju-configuration->mixed-text-file
247 soju-activation
248 soju-accounts
249 soju-shepherd-services
250
251 soju-service-type))
212 252
213;;; Commentary: 253;;; Commentary:
214;;; 254;;;
@@ -1570,7 +1610,7 @@ values."
1570 ;; Ensure stdin is not connected to a TTY source to avoid ngircd 1610 ;; Ensure stdin is not connected to a TTY source to avoid ngircd
1571 ;; configtest blocking with a confirmation prompt. 1611 ;; configtest blocking with a confirmation prompt.
1572 (parameterize ((current-input-port (%make-void-port "r"))) 1612 (parameterize ((current-input-port (%make-void-port "r")))
1573 (invoke #+ngircd "--config" #$ngircd.conf "--configtest" )) 1613 (invoke #+ngircd "--config" #$ngircd.conf "--configtest"))
1574 (copy-file #$ngircd.conf #$output)))))) 1614 (copy-file #$ngircd.conf #$output))))))
1575 1615
1576(define (ngircd-wrapper config) 1616(define (ngircd-wrapper config)
@@ -1804,7 +1844,7 @@ reconnecting client. The size must be a power of two.")
1804 maybe-string 1844 maybe-string
1805 "Host to bind the @emph{source} address to when connecting to the server. 1845 "Host to bind the @emph{source} address to when connecting to the server.
1806To connect from any address over IPv4 only, use @samp{0.0.0.0}. To connect 1846To connect from any address over IPv4 only, use @samp{0.0.0.0}. To connect
1807from any address over IPv6 only, use @samp{::}." ) 1847from any address over IPv6 only, use @samp{::}.")
1808 1848
1809 (host 1849 (host
1810 string 1850 string
@@ -2388,3 +2428,303 @@ if it does not exist.")
2388 mollysocket-activation-service))) 2428 mollysocket-activation-service)))
2389 (default-value (mollysocket-configuration)) 2429 (default-value (mollysocket-configuration))
2390 (description "UnifiedPush provider for the Signal client Molly."))) 2430 (description "UnifiedPush provider for the Signal client Molly.")))
2431
2432
2433;;;
2434;;; Soju
2435;;;
2436
2437(define %default-soju-shepherd-requirement
2438 '(user-processes loopback))
2439
2440(define-configuration soju-ssl
2441 (certificate
2442 (string)
2443 "Where to find the certificate for secure connections."
2444 (serializer empty-serializer))
2445 (key
2446 (string)
2447 "Where to find the private key for secure connections."
2448 (serializer empty-serializer)))
2449
2450(define (soju-serialize-soju-ssl name value)
2451 #~(string-append "tls "
2452 #$(soju-ssl-certificate value)
2453 " "
2454 #$(soju-ssl-key value)
2455 "\n"))
2456
2457(define-maybe soju-ssl (prefix soju-))
2458
2459(define (soju-sanitize-db-driver value)
2460 (if (or (eq? value 'sqlite3)
2461 (eq? value 'postgres))
2462 value
2463 (raise
2464 (formatted-message
2465 (G_ "db-driver can be either 'sqlite3 or 'postgres but ~a was found")
2466 value))))
2467
2468(define (soju-serialize-symbol name value) (symbol->string value))
2469
2470(define (soju-serialize-string name value)
2471 (define quoted-value
2472 #~(if (string-contains #$value " ")
2473 (string-append "\"" #$value "\"")
2474 #$value))
2475 #~(string-append (symbol->string '#$name) " " #$quoted-value "\n"))
2476
2477(define soju-serialize-package serialize-package)
2478
2479(define-maybe string (prefix soju-))
2480
2481(define-configuration soju-database
2482 (datadir
2483 (string "/var/lib/soju")
2484 "The name of the directory where soju will write its state.")
2485 (driver
2486 (symbol 'sqlite3)
2487 "Set the database driver for user, network and channel storage.
2488
2489Supported drivers:
2490
2491@itemize
2492@item @code{'sqlite3}
2493@item @code{'postgres}
2494@end itemize"
2495 (sanitizer soju-sanitize-db-driver)
2496 (serializer soju-serialize-symbol))
2497 (source
2498 (maybe-string)
2499 "Set the database location for user, network and channel storage. By
2500default, a sqlite3 database is opened in the directory specified in the
2501@code{datadir} field.
2502
2503In general the driver expect the following:
2504
2505@itemize
2506@item @code{'sqlite3} expects source to be a path to the SQLite file
2507@item @code{'postgres} expects source to be a space-separated list of
2508@code{key=value} parameters, e.g. @code{\"host=/run/postgresql dbname=soju\"}.
2509Note that @code{sslmode defaults} to @code{require}. For more information on
2510connection strings, see
2511@url{https://pkg.go.dev/github.com/lib/pq#hdr-Connection_String_Parameters,
2512upstream}'s documentation.
2513@end itemize"
2514 (serializer soju-serialize-maybe-string)))
2515
2516(define (soju-db-source driver source datadir)
2517 (if (not (string=? "" source))
2518 source
2519 (if (string=? driver "sqlite3")
2520 (string-append datadir "/soju.db")
2521 (raise
2522 (G_ "db-source can't be empty when db-driver is set to 'postgres.
2523Make sure to pass a connection string")))))
2524
2525(define (soju-serialize-soju-database name config)
2526 (define fields
2527 (filter-configuration-fields
2528 soju-database-fields
2529 '(driver source)))
2530 (define getters
2531 (map configuration-field-getter fields))
2532 (define names
2533 (map configuration-field-name fields))
2534 (define serializers
2535 (map configuration-field-serializer fields))
2536 (define values
2537 (map (match-lambda ((serializer name getter)
2538 (serializer name (getter config))))
2539 (zip serializers names getters)))
2540
2541 #~(string-append "db " #$(first values)
2542 " " #$(soju-db-source
2543 (first values)
2544 (second values)
2545 (soju-database-datadir config))
2546 "\n"))
2547
2548(define (soju-serialize-list-of-strings name value)
2549 #~(string-append
2550 (string-join (map (lambda (l) (string-append "listen " l))
2551 (list #$@value))
2552 "\n")
2553 "\n"))
2554
2555
2556(define soju-serialize-text-config serialize-text-config)
2557
2558(define-configuration soju-configuration
2559 (soju
2560 (package soju)
2561 "Soju package to use for the service."
2562 (serializer empty-serializer))
2563 (debug?
2564 (boolean #f)
2565 "Enable debug logging (this will leak sensitive information such as
2566passwords). This can be overriden at run time with the service command
2567@code{server debug}."
2568 (serializer empty-serializer))
2569 (listen
2570 (list-of-strings '(":6697"))
2571 "Listening URI. The following URIs are supported:
2572
2573@itemize
2574
2575@item @code{[ircs://][host][:port]} listens with TLS over TCP (default port if
2576omitted: 6697)
2577@item @code{irc://localhost[:port]} listens with plain-text over TCP (default
2578port if omitted: 6667, host must be @code{\"localhost\"})
2579@item @code{irc+insecure://[host][:port]} listens with plain-text over TCP
2580(default port if omitted: 6667)
2581@item @code{unix://<path>} listens on a Unix domain socket
2582@item @code{https://[host][:port]} listens for HTTPS connections (default port:
2583443) and handles the following requests: @code{/socket} for WebSocket and
2584@code{/uploads} (and subdirectories) for file uploads
2585@item @code{http://localhost[:port]} listens for plain-text HTTP connections
2586(default port: 80, host must be @code{\"localhost\"}) and handles requests like
2587@code{https://} does
2588@item @code{http+insecure://[host][:port]} listens for plain-text HTTP
2589connections (default port: 80) and handles requests like @code{https://} does
2590@item @code{http+unix://<path>} listens for plain-text HTTP connections on a
2591Unix domain socket and handles requests like @code{https://} does
2592@item @code{wss://[host][:port]} listens for WebSocket connections over TLS
2593(default port: 443)
2594@item @code{ws://localhost[:port]} listens for plain-text WebSocket connections
2595(default port: 80, host must be @code{\"localhost\"})
2596@item @code{ws+insecure://[host][:port]} listens for plain-text WebSocket
2597connections (default port: 80)
2598@item @code{ws+unix://<path>} listens for plain-text WebSocket connections on a
2599Unix domain socket
2600@item @code{ident://[host][:port]} listens for plain-text ident connections
2601(default port: 113)
2602@item @code{http+prometheus://localhost:<port>} listens for plain-text HTTP
2603connections and serves Prometheus metrics (host must be @code{\"localhost\"})
2604@item @code{http+pprof://localhost:<port>} listens for plain-text HTTP
2605connections and serves pprof runtime profiling data (host must be
2606@code{\"localhost\"}). For more information, see
2607@url{https://pkg.go.dev/net/http/pprof,upstream} documentation
2608@item @code{unix+admin://[path]} listens on a Unix domain socket for
2609administrative connections, such as sojuctl (default path:
2610@code{/run/soju/admin})
2611
2612@end itemize
2613
2614If the scheme is omitted, @code{ircs} is assumed. If multiple @code{listen}
2615values are specified, soju will listen on each of them.")
2616 (hostname
2617 (maybe-string)
2618 "Server hostname, it defaults to the system hostname. This should be set to
2619a fully qualified domain name.")
2620 (title
2621 (string)
2622 "Server title. This will be sent as the @code{ISUPPORT NETWORK} value when
2623clients don't select a specific network.")
2624 (ssl-certificate
2625 (maybe-soju-ssl)
2626 "Where to find the private key for secure connections. If set, this field
2627will have the service run under root privileges.")
2628 (shepherd-requirement
2629 (list %default-soju-shepherd-requirement)
2630 "A list of Shepherd services to use. Add extra dependencies to
2631@code{%default-soju-shepherd-requirement} to extend its value."
2632 (serializer empty-serializer))
2633 (log-file
2634 (string "/var/log/soju.log")
2635 "The name of the file where soju will write its logs."
2636 (serializer empty-serializer))
2637 (db
2638 (soju-database (soju-database))
2639 "The database where soju will write its state.")
2640 (extra-content
2641 (text-config '())
2642 "Extra content to append to the configuration as-is.")
2643 (prefix soju-))
2644
2645(define (soju-needs-privileges? config)
2646 ;; NOTE: Certificates on the Guix System are only readable by root. In case a
2647 ;; certificate and a private key are passed no unprivileged users will be
2648 ;; added to the system.
2649 (maybe-value-set?
2650 (soju-configuration-ssl-certificate config)))
2651
2652(define (serialize-soju-configuration config)
2653 (mixed-text-file "soju.conf"
2654 (serialize-configuration
2655 config
2656 (filter-configuration-fields
2657 soju-configuration-fields
2658 '(soju debug? shepherd-requirement log-file)
2659 #t))))
2660
2661(define (soju-activation config)
2662 (let ((datadir (soju-database-datadir
2663 (soju-configuration-db config)))
2664 (user-name
2665 (if (soju-needs-privileges? config) "root" "soju")))
2666 #~(let* ((user (getpwnam #$user-name))
2667 (uid (passwd:uid user))
2668 (gid (passwd:gid user))
2669 (datadir #$datadir))
2670 ;; Setup datadir
2671 (mkdir-p datadir)
2672 (chown datadir uid gid)
2673 (for-each (lambda (f) (chown f uid gid))
2674 (find-files datadir ".*")))))
2675
2676(define (soju-accounts config)
2677 (if (soju-needs-privileges? config)
2678 '()
2679 (list (user-group (name "soju") (system? #t))
2680 (user-account
2681 (name "soju")
2682 (group "soju")
2683 (system? #t)
2684 (comment "soju server user")
2685 (home-directory "/var/empty")
2686 (shell (file-append shadow "/sbin/nologin"))))))
2687
2688(define (soju-shepherd-services config)
2689 (match-record config <soju-configuration>
2690 (soju debug? log-file shepherd-requirement)
2691 (let ((user-name (if (soju-needs-privileges? config) "root" "soju"))
2692 (soju-binary (file-append soju "/bin/soju"))
2693 (config-file (serialize-soju-configuration config)))
2694 (list (shepherd-service
2695 (provision '(soju))
2696 (documentation "Run the soju daemon.")
2697 (requirement shepherd-requirement)
2698 (start #~(make-forkexec-constructor
2699 (list #$soju-binary
2700 #$@(if debug? '("-debug") '())
2701 "-config" #$config-file)
2702 #:log-file #$log-file
2703 #:user #$user-name
2704 #:group #$user-name))
2705 (stop #~(make-kill-destructor))
2706 (actions
2707 (list
2708 (shepherd-action
2709 (name 'reload)
2710 (documentation "Reload soju configuration file and restart
2711it. It is useful for situations where the same soju configuration file can
2712point to different things after a reload, such as renewed TLS certificates.")
2713 (procedure
2714 #~(lambda (process . args)
2715 (kill (process-id process) SIGHUP))))
2716 (shepherd-configuration-action config-file))))))))
2717
2718(define soju-service-type
2719 (service-type (name 'soju)
2720 (extensions
2721 (list (service-extension shepherd-root-service-type
2722 soju-shepherd-services)
2723 (service-extension log-rotation-service-type
2724 (compose
2725 list soju-configuration-log-file))
2726 (service-extension activation-service-type
2727 soju-activation)
2728 (service-extension account-service-type
2729 soju-accounts)))
2730 (description "Run the soju IRC bouncer.")))
diff --git a/gnu/tests/messaging.scm b/gnu/tests/messaging.scm
index 83ccca88918..559dc81a747 100644
--- a/gnu/tests/messaging.scm
+++ b/gnu/tests/messaging.scm
@@ -4,6 +4,7 @@
4;;; Copyright © 2018 Efraim Flashner <efraim@flashner.co.il> 4;;; Copyright © 2018 Efraim Flashner <efraim@flashner.co.il>
5;;; Copyright © 2025 Maxim Cournoyer <maxim@guixotic.coop> 5;;; Copyright © 2025 Maxim Cournoyer <maxim@guixotic.coop>
6;;; Copyright © 2025 Evgeny Pisemsky <mail@pisemsky.site> 6;;; Copyright © 2025 Evgeny Pisemsky <mail@pisemsky.site>
7;;; Copyright © 2026 Giacomo Leidi <therewasa@fishinthecalculator.me>
7;;; 8;;;
8;;; This file is part of GNU Guix. 9;;; This file is part of GNU Guix.
9;;; 10;;;
@@ -44,7 +45,8 @@
44 %test-ngircd 45 %test-ngircd
45 %test-pounce 46 %test-pounce
46 %test-quassel 47 %test-quassel
47 %test-mosquitto)) 48 %test-mosquitto
49 %test-soju))
48 50
49(define (run-xmpp-test name xmpp-service pid-file create-account) 51(define (run-xmpp-test name xmpp-service pid-file create-account)
50 "Run a test of an OS running XMPP-SERVICE, which writes its PID to PID-FILE." 52 "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 &")
669 (name "mosquitto") 671 (name "mosquitto")
670 (description "Test a running Mosquitto MQTT broker.") 672 (description "Test a running Mosquitto MQTT broker.")
671 (value (run-mosquitto-test)))) 673 (value (run-mosquitto-test))))
674
675
676;;;
677;;; soju.
678;;;
679
680(define %soju-os
681 (operating-system
682 (inherit %simple-os)
683 (packages (cons* soju %base-packages))
684 (services
685 (cons*
686 (service dhcpcd-service-type)
687 (service soju-service-type
688 (soju-configuration
689 (debug? #t)
690 (listen '("irc://localhost" "unix+admin:///var/lib/soju/soju.sock"))
691 (title "soju IRC bouncer")
692 (extra-content
693 (list (plain-file "soju.conf" "hostname test.example.org")))))
694 %base-services))))
695
696(define (run-soju-test)
697 (define vm
698 (virtual-machine
699 (operating-system
700 (marionette-operating-system
701 %soju-os
702 #:imported-modules (source-module-closure
703 '((gnu services herd)))))))
704
705 (define test
706 (with-imported-modules '((gnu build marionette))
707 #~(begin
708 (use-modules (srfi srfi-64)
709 (gnu build marionette))
710
711 (define marionette
712 (make-marionette (list #$vm)))
713
714 (test-runner-current (system-test-runner #$output))
715 (test-begin "soju")
716
717 (marionette-eval
718 '(begin
719 (use-modules (gnu services herd))
720 (wait-for-service 'user-processes))
721 marionette)
722
723 (test-equal "soju configuration file is well formed"
724 "listen irc://localhost
725listen unix+admin:///var/lib/soju/soju.sock
726title \"soju IRC bouncer\"
727db sqlite3 /var/lib/soju/soju.db
728hostname test.example.org
729"
730 (marionette-eval
731 '(begin
732 (use-modules (ice-9 popen)
733 (ice-9 rdelim)
734 (ice-9 textual-ports))
735 (let* ((port (open-input-pipe "herd configuration soju"))
736 (soju.conf (string-trim-both (read-line port))))
737 (close-pipe port)
738 (call-with-input-file soju.conf get-string-all)))
739 marionette))
740
741 (test-assert "soju service runs"
742 (marionette-eval
743 '(begin
744 (use-modules (gnu services herd))
745 (wait-for-service 'soju))
746 marionette))
747
748 (test-assert "soju listens on TCP port 6667"
749 (wait-for-tcp-port 6667 marionette))
750
751 (test-equal "sojuctl can create a user"
752 "fishinthecalculator (admin): 0 networks"
753 (marionette-eval
754 '(begin
755 (use-modules (ice-9 popen)
756 (ice-9 rdelim))
757 (system (string-join
758 '("sojuctl" "-config" "$(herd configuration soju)"
759 "user" "create" "-admin"
760 "-username" "fishinthecalculator"
761 "-password" "1234")
762 " "))
763 (let* ((port (open-input-pipe (string-join
764 '("sojuctl" "-config"
765 "$(herd configuration soju)"
766 "user" "status")
767 " ")))
768 (msg (read-line port)))
769 (close-pipe port)
770 msg))
771 marionette))
772
773 (test-end))))
774
775 (gexp->derivation "soju-test" test))
776
777(define %test-soju
778 (system-test
779 (name "soju")
780 (description "Run a soju IRC bouncer.")
781 (value (run-soju-test))))