Files
guix-tribes/tribes/services/tribes.scm

284 lines
11 KiB
Scheme

(define-module (tribes services tribes)
#:use-module (gnu services)
#:use-module (gnu services base)
#:use-module (gnu services databases)
#:use-module (gnu services shepherd)
#:use-module (gnu packages admin)
#:use-module (gnu system shadow)
#:use-module (guix gexp)
#:use-module (guix records)
#:use-module (ice-9 match)
#:use-module (srfi srfi-13)
#:export (tribes-configuration
tribes-configuration?
tribes-configuration-package
tribes-configuration-user
tribes-configuration-group
tribes-configuration-working-directory
tribes-configuration-plugin-directory
tribes-configuration-host
tribes-configuration-scheme
tribes-configuration-port
tribes-configuration-relay-url
tribes-configuration-host-manifest
tribes-configuration-admin-pubkeys
tribes-configuration-sync-overlap-seconds
tribes-configuration-database-user
tribes-configuration-database-name
tribes-configuration-parrhesia-database-name
tribes-configuration-database-host
tribes-configuration-secret-key-base-file
tribes-configuration-token-signing-secret-file
tribes-configuration-dns-cluster-query
tribes-configuration-extra-environment-variables
tribes-configuration-log-file
tribes-service-type))
(define-record-type* <tribes-configuration>
tribes-configuration make-tribes-configuration
tribes-configuration?
(package tribes-configuration-package
(default #f))
(user tribes-configuration-user
(default "tribes"))
(group tribes-configuration-group
(default "tribes"))
(working-directory tribes-configuration-working-directory
(default "/var/lib/tribes"))
(plugin-directory tribes-configuration-plugin-directory
(default "/var/lib/tribes/plugins"))
(host tribes-configuration-host
(default "localhost"))
(scheme tribes-configuration-scheme
(default "http"))
(port tribes-configuration-port
(default 4000))
(relay-url tribes-configuration-relay-url
(default #f))
(host-manifest tribes-configuration-host-manifest
(default #f))
(admin-pubkeys tribes-configuration-admin-pubkeys
(default '()))
(sync-overlap-seconds tribes-configuration-sync-overlap-seconds
(default 300))
(database-user tribes-configuration-database-user
(default "tribes"))
(database-name tribes-configuration-database-name
(default "tribes"))
(parrhesia-database-name tribes-configuration-parrhesia-database-name
(default "parrhesia"))
(database-host tribes-configuration-database-host
(default "/var/run/postgresql"))
(secret-key-base-file tribes-configuration-secret-key-base-file
(default "/var/lib/tribes/secrets/secret_key_base"))
(token-signing-secret-file tribes-configuration-token-signing-secret-file
(default "/var/lib/tribes/secrets/token_signing_secret"))
(dns-cluster-query tribes-configuration-dns-cluster-query
(default #f))
(extra-environment-variables tribes-configuration-extra-environment-variables
(default '()))
(log-file tribes-configuration-log-file
(default "/var/log/tribes/tribes.log")))
(define (tribes-accounts config)
(list
(user-group
(name (tribes-configuration-group config))
(system? #t))
(user-account
(name (tribes-configuration-user config))
(group (tribes-configuration-group config))
(system? #t)
(comment "Tribes service user")
(home-directory (tribes-configuration-working-directory config))
(shell (file-append shadow "/sbin/nologin")))))
(define (tribes-relay-url config)
(or (tribes-configuration-relay-url config)
(let* ((scheme (tribes-configuration-scheme config))
(host (tribes-configuration-host config))
(port (tribes-configuration-port config))
(ws-scheme (if (string=? scheme "https") "wss" "ws"))
(default-port?
(or (and (string=? scheme "https") (= port 443))
(and (string=? scheme "http") (= port 80)))))
(if default-port?
(string-append ws-scheme "://" host "/nostr/relay")
(string-append ws-scheme "://" host ":" (number->string port)
"/nostr/relay")))))
(define (tribes-database-url config database-name)
(let ((user (tribes-configuration-database-user config))
(host (tribes-configuration-database-host config)))
(if (string-prefix? "/" host)
(string-append "ecto://"
user
"@localhost/"
database-name
"?socket_dir="
host)
(string-append "ecto://"
user
"@"
host
"/"
database-name))))
(define (tribes-launcher config command args)
(define package
(tribes-configuration-package config))
(define env-setters
(append
(list
#~(setenv "HOME" #$(tribes-configuration-working-directory config))
#~(setenv "PHX_SERVER" "true")
#~(setenv "PORT" #$(number->string (tribes-configuration-port config)))
#~(setenv "PHX_HOST" #$(tribes-configuration-host config))
#~(setenv "DATABASE_URL"
#$(tribes-database-url
config
(tribes-configuration-database-name config)))
#~(setenv "PARRHESIA_DATABASE_URL"
#$(tribes-database-url
config
(tribes-configuration-parrhesia-database-name config)))
#~(setenv "PARRHESIA_RELAY_URL" #$(tribes-relay-url config))
#~(setenv "TRIBES_PLUGIN_DIR" #$(tribes-configuration-plugin-directory config))
#~(setenv "TRIBES_SYNC_OVERLAP_SECONDS"
#$(number->string
(tribes-configuration-sync-overlap-seconds config)))
#~(setenv "TRIBES_ADMIN_PUBKEYS"
#$(string-join
(tribes-configuration-admin-pubkeys config)
","))
#~(setenv "SSL_CERT_DIR" "/etc/ssl/certs")
#~(setenv "SSL_CERT_FILE" "/etc/ssl/certs/ca-certificates.crt"))
(if (tribes-configuration-host-manifest config)
(list #~(setenv "TRIBES_HOST_MANIFEST"
#$(tribes-configuration-host-manifest config)))
'())
(if (tribes-configuration-dns-cluster-query config)
(list #~(setenv "DNS_CLUSTER_QUERY"
#$(tribes-configuration-dns-cluster-query config)))
'())
(map (lambda (entry)
#~(let* ((entry #$entry)
(parts (string-split entry #\=))
(name (car parts))
(value (string-join (cdr parts) "=")))
(setenv name value)))
(tribes-configuration-extra-environment-variables config))))
(program-file
(string-append "tribes-" command)
#~(begin
(use-modules (ice-9 textual-ports)
(srfi srfi-13))
(define (read-secret path)
(string-trim-right (call-with-input-file path get-string-all)))
(define secret-key-file
#$(tribes-configuration-secret-key-base-file config))
(define token-file
#$(tribes-configuration-token-signing-secret-file config))
(unless (file-exists? secret-key-file)
(format (current-error-port)
"missing Tribes secret file: ~a~%"
secret-key-file)
(exit 1))
(unless (file-exists? token-file)
(format (current-error-port)
"missing Tribes token-signing secret file: ~a~%"
token-file)
(exit 1))
(setenv "SECRET_KEY_BASE" (read-secret secret-key-file))
(setenv "TOKEN_SIGNING_SECRET" (read-secret token-file))
#$@env-setters
(apply execl
#$(file-append package "/bin/tribes")
"tribes"
(cons #$command '#$args)))))
(define (tribes-activation config)
#~(begin
(use-modules (guix build utils)
(ice-9 match)
(srfi srfi-1))
(let* ((user (getpwnam #$(tribes-configuration-user config)))
(uid (passwd:uid user))
(gid (passwd:gid user))
(dirs (list #$(tribes-configuration-working-directory config)
#$(tribes-configuration-plugin-directory config)
(dirname #$(tribes-configuration-log-file config))
(dirname #$(tribes-configuration-secret-key-base-file config))
(dirname #$(tribes-configuration-token-signing-secret-file config)))))
(for-each
(lambda (dir)
(mkdir-p dir)
(chown dir uid gid))
dirs))))
(define (tribes-migrations-shepherd-service config)
(let ((launcher (tribes-launcher
config
"eval"
'("Tribes.Release.migrate_with_storage_up()"))))
(list
(shepherd-service
(documentation "Run Tribes database migrations.")
(provision '(tribes-migrations))
(requirement '(postgres user-processes))
(one-shot? #t)
(start
#~(lambda _
(zero? (spawn-command
(list #$launcher)
#:user #$(tribes-configuration-user config)
#:group #$(tribes-configuration-group config)))))
(respawn? #f)))))
(define (tribes-shepherd-service config)
(let ((launcher (tribes-launcher config "start" '())))
(list
(shepherd-service
(documentation "Run the Tribes application service.")
(provision '(tribes))
(requirement '(tribes-migrations networking user-processes))
(start
#~(make-forkexec-constructor
(list #$launcher)
#:user #$(tribes-configuration-user config)
#:group #$(tribes-configuration-group config)
#:log-file #$(tribes-configuration-log-file config)))
(stop #~(make-kill-destructor))
(respawn? #f)))))
(define (tribes-root-shepherd-services config)
(append (tribes-migrations-shepherd-service config)
(tribes-shepherd-service config)))
(define (tribes-profile-packages config)
(match (tribes-configuration-package config)
(#f '())
(package (list package))))
(define tribes-service-type
(service-type
(name 'tribes)
(extensions
(list (service-extension account-service-type
tribes-accounts)
(service-extension activation-service-type
tribes-activation)
(service-extension shepherd-root-service-type
tribes-root-shepherd-services)
(service-extension profile-service-type
tribes-profile-packages)))
(default-value (tribes-configuration))
(description
"Run a Tribes release with PostgreSQL-backed storage, Parrhesia, and
cluster runtime configuration sourced from files and service settings.")))