Add Tribes edge services with Vinyl, Hitch, and Lego

This commit is contained in:
2026-04-01 05:34:55 +02:00
parent fe5fabcea1
commit 2d3f53684f
7 changed files with 876 additions and 17 deletions

View File

@@ -3,7 +3,8 @@
#:use-module (guix profiles)
#:use-module (tribes packages otp)
#:use-module (tribes packages source)
#:use-module (tribes packages terminals))
#:use-module (tribes packages terminals)
#:use-module (tribes packages web))
(define %tribes-node-specifications
'("nss-certs"
@@ -37,6 +38,9 @@
elixir-otp28
elixir-hex-otp28
ghostty-terminfo
hitch
vinyl
lego
(tribes-node-package)))))
(make-tribes-node-manifest)

136
tribes/services/hitch.scm Normal file
View File

@@ -0,0 +1,136 @@
(define-module (tribes services hitch)
#:use-module (gnu packages admin)
#:use-module (gnu services)
#:use-module (gnu services shepherd)
#:use-module (gnu system shadow)
#:use-module (guix gexp)
#:use-module (guix records)
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
#:use-module (tribes packages web)
#:export (hitch-configuration
hitch-configuration?
hitch-configuration-package
hitch-configuration-backend
hitch-configuration-frontends
hitch-configuration-pem-files
hitch-configuration-ciphers
hitch-configuration-user
hitch-configuration-group
hitch-configuration-pid-file
hitch-configuration-ocsp-dir
hitch-configuration-extra-config
hitch-service-type))
(define-record-type* <hitch-configuration>
hitch-configuration make-hitch-configuration
hitch-configuration?
(package hitch-configuration-package
(default hitch))
(backend hitch-configuration-backend
(default "[127.0.0.1]:6081"))
(frontends hitch-configuration-frontends
(default '("[0.0.0.0]:443" "[::]:443")))
(pem-files hitch-configuration-pem-files
(default '()))
(ciphers hitch-configuration-ciphers
(default "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"))
(user hitch-configuration-user
(default "hitch"))
(group hitch-configuration-group
(default "hitch"))
(pid-file hitch-configuration-pid-file
(default "/var/run/hitch/hitch.pid"))
(ocsp-dir hitch-configuration-ocsp-dir
(default "/var/cache/hitch/ocsp"))
(extra-config hitch-configuration-extra-config
(default '())))
(define %hitch-accounts
(list
(user-group
(name "hitch")
(system? #t))
(user-account
(name "hitch")
(group "hitch")
(system? #t)
(comment "Hitch TLS proxy user")
(home-directory "/var/empty")
(shell (file-append shadow "/sbin/nologin")))))
(define (hitch-config-file config)
(match config
(($ <hitch-configuration> _ backend frontends pem-files ciphers user group
pid-file ocsp-dir extra-config)
(plain-file
"hitch.conf"
(string-append
"backend = \"" backend "\"\n"
(string-concatenate
(map (lambda (frontend)
(string-append "frontend = \"" frontend "\"\n"))
frontends))
(string-concatenate
(map (lambda (pem-file)
(string-append "pem-file = \"" pem-file "\"\n"))
pem-files))
"ciphers = \"" ciphers "\"\n"
"user = \"" user "\"\n"
"group = \"" group "\"\n"
"pidfile = \"" pid-file "\"\n"
"ocsp-dir = \"" ocsp-dir "\"\n"
(string-concatenate
(map (lambda (line)
(string-append line "\n"))
extra-config)))))))
(define (hitch-activation config)
#~(begin
(use-modules (guix build utils))
(mkdir-p "/var/run/hitch")
(mkdir-p #$(hitch-configuration-ocsp-dir config))))
(define (hitch-shepherd-services config)
(let ((config-file (hitch-config-file config))
(pid-file (hitch-configuration-pid-file config))
(package (hitch-configuration-package config)))
(list
(shepherd-service
(documentation "Run the Hitch TLS proxy.")
(provision '(hitch))
(requirement '(user-processes networking))
(start
#~(make-forkexec-constructor
(list #$(file-append package "/sbin/hitch")
"--daemon"
"--config"
#$config-file)
#:pid-file #$pid-file))
(stop #~(make-kill-destructor))
(actions
(list
(shepherd-action
(name 'reload)
(documentation "Reload Hitch certificates and listeners.")
(procedure
#~(lambda _
(use-modules (ice-9 posix))
(let ((pid (call-with-input-file #$pid-file read)))
(kill pid SIGHUP)
#t))))))))))
(define hitch-service-type
(service-type
(name 'hitch)
(extensions
(list (service-extension account-service-type
(const %hitch-accounts))
(service-extension activation-service-type
hitch-activation)
(service-extension shepherd-root-service-type
hitch-shepherd-services)
(service-extension profile-service-type
(compose list hitch-configuration-package))))
(default-value (hitch-configuration))
(description "Run the Hitch TLS proxy.")))

315
tribes/services/lego.scm Normal file
View File

@@ -0,0 +1,315 @@
(define-module (tribes services lego)
#:use-module (gnu packages admin)
#:use-module (gnu packages tls)
#:use-module (gnu services)
#:use-module (gnu services herd)
#:use-module (gnu services shepherd)
#:use-module (guix gexp)
#:use-module (guix records)
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-13)
#:use-module (tribes packages web)
#:export (lego-certificate-configuration
lego-certificate-configuration?
lego-certificate-configuration-name
lego-certificate-configuration-subjects
lego-certificate-configuration-email
lego-certificate-configuration-server
lego-certificate-configuration-profile
lego-certificate-configuration-listen-http
lego-certificate-configuration-webroot
lego-certificate-configuration-key-type
lego-certificate-configuration-renew-days
lego-certificate-configuration-reload-services
lego-certificate-directory
lego-certificate-full-pem
lego-configuration
lego-configuration?
lego-configuration-package
lego-configuration-renew-hours
lego-configuration-renew-minutes
lego-configuration-certificates
lego-service-type))
(define-record-type* <lego-certificate-configuration>
lego-certificate-configuration make-lego-certificate-configuration
lego-certificate-configuration?
(name lego-certificate-configuration-name)
(subjects lego-certificate-configuration-subjects)
(email lego-certificate-configuration-email
(default #f))
(server lego-certificate-configuration-server
(default "https://acme-v02.api.letsencrypt.org/directory"))
(profile lego-certificate-configuration-profile
(default #f))
(listen-http lego-certificate-configuration-listen-http
(default #f))
(webroot lego-certificate-configuration-webroot
(default #f))
(key-type lego-certificate-configuration-key-type
(default "ec256"))
(renew-days lego-certificate-configuration-renew-days
(default #f))
(reload-services lego-certificate-configuration-reload-services
(default '())))
(define-record-type* <lego-configuration>
lego-configuration make-lego-configuration
lego-configuration?
(package lego-configuration-package
(default lego))
(renew-hours lego-configuration-renew-hours
(default '(0 12)))
(renew-minutes lego-configuration-renew-minutes
(default '(17)))
(certificates lego-configuration-certificates
(default '())))
(define (lego-certificate-directory certificate)
(string-append "/var/lib/lego/"
(lego-certificate-configuration-name certificate)))
(define (lego-certificate-full-pem certificate)
(string-append (lego-certificate-directory certificate) "/full.pem"))
(define (subject->san-entry subject)
(if (and (not (string-any char-alphabetic? subject))
(string-any (lambda (chr)
(or (char-numeric? chr)
(char=? chr #\.)
(char=? chr #\:)))
subject))
(string-append "IP:" subject)
(string-append "DNS:" subject)))
(define (certificate-key-name certificate)
(string-replace-substring
(car (lego-certificate-configuration-subjects certificate))
"*"
"_"))
(define (lego-common-arguments certificate)
(let ((listen-http (lego-certificate-configuration-listen-http certificate))
(webroot (lego-certificate-configuration-webroot certificate))
(subjects (lego-certificate-configuration-subjects certificate))
(email (lego-certificate-configuration-email certificate))
(server (lego-certificate-configuration-server certificate))
(key-type (lego-certificate-configuration-key-type certificate))
(state-dir (lego-certificate-directory certificate)))
(append
(list "--accept-tos"
"--path" state-dir)
(if email
(list "--email" email)
'())
(if (and listen-http webroot)
(error "lego certificate cannot use both listen-http and webroot"
(lego-certificate-configuration-name certificate))
'())
(cond
(listen-http
(list "--http" "--http.port" listen-http))
(webroot
(list "--http" "--http.webroot" webroot))
(else
(list "--http")))
(if server
(list "--server" server)
'())
(list "--key-type" key-type)
(append-map (lambda (subject)
(list "-d" subject))
subjects))))
(define (lego-self-signed-activation-gexp certificate)
(let* ((state-dir (lego-certificate-directory certificate))
(primary-subject (car (lego-certificate-configuration-subjects certificate)))
(san-string (string-append
"subjectAltName="
(string-join
(map subject->san-entry
(lego-certificate-configuration-subjects certificate))
",")))
(initial-cert (string-append state-dir "/initial.pem"))
(fullchain (string-append state-dir "/fullchain.pem"))
(chain (string-append state-dir "/chain.pem"))
(cert-output (string-append state-dir "/cert.pem"))
(key-output (string-append state-dir "/key.pem"))
(full-pem (string-append state-dir "/full.pem")))
#~(begin
(use-modules (guix build utils)
(ice-9 textual-ports))
(mkdir-p #$state-dir)
(when #$(lego-certificate-configuration-webroot certificate)
(mkdir-p (string-append
#$(lego-certificate-configuration-webroot certificate)
"/.well-known/acme-challenge")))
(unless (file-exists? #$fullchain)
(invoke #$(file-append openssl "/bin/openssl")
"req"
"-x509"
"-newkey" "rsa:2048"
"-keyout" #$key-output
"-out" #$initial-cert
"-sha256"
"-days" "1"
"-nodes"
"-subj" #$(string-append "/CN=" primary-subject)
"-addext" #$san-string)
(copy-file #$initial-cert #$fullchain)
(copy-file #$initial-cert #$chain)
(copy-file #$initial-cert #$cert-output)
(call-with-output-file #$full-pem
(lambda (port)
(display (call-with-input-file #$key-output get-string-all) port)
(display (call-with-input-file #$fullchain get-string-all) port)))))))
(define (lego-certificate-program config certificate)
(let* ((state-dir (lego-certificate-directory certificate))
(certificate-dir (string-append state-dir "/certificates"))
(key-name (certificate-key-name certificate))
(certificate-file (string-append certificate-dir "/" key-name ".crt"))
(issuer-file (string-append certificate-dir "/" key-name ".issuer.crt"))
(private-key (string-append certificate-dir "/" key-name ".key"))
(fullchain (string-append state-dir "/fullchain.pem"))
(chain (string-append state-dir "/chain.pem"))
(cert-output (string-append state-dir "/cert.pem"))
(key-output (string-append state-dir "/key.pem"))
(full-pem (string-append state-dir "/full.pem"))
(run-arguments
(append (lego-common-arguments certificate)
(list "run")
(if (lego-certificate-configuration-profile certificate)
(list (string-append
"--profile="
(lego-certificate-configuration-profile certificate)))
'())))
(renew-arguments
(append (lego-common-arguments certificate)
(list "renew" "--no-random-sleep")
(if (lego-certificate-configuration-profile certificate)
(list (string-append
"--profile="
(lego-certificate-configuration-profile certificate)))
'())
(if (lego-certificate-configuration-renew-days certificate)
(list "--days"
(number->string
(lego-certificate-configuration-renew-days certificate)))
(list "--dynamic")))))
(program-file
(string-append "lego-" (lego-certificate-configuration-name certificate))
(with-imported-modules '((gnu services herd)
(guix build utils))
#~(begin
(use-modules (gnu services herd)
(guix build utils)
(ice-9 textual-ports))
(define (file-contents path)
(and (file-exists? path)
(call-with-input-file path get-string-all)))
(define (fullchain-changed?)
(let ((old (file-contents #$fullchain))
(new (file-contents #$certificate-file)))
(not (equal? old new))))
(mkdir-p #$state-dir)
(let ((lego #$(file-append
(lego-configuration-package config)
"/bin/lego"))
(run-args '#$run-arguments)
(renew-args '#$renew-arguments))
(if (file-exists? #$certificate-file)
(apply invoke lego renew-args)
(apply invoke lego run-args)))
(when (and (file-exists? #$certificate-file)
(fullchain-changed?))
(copy-file #$certificate-file #$fullchain)
(copy-file #$private-key #$key-output)
(if (file-exists? #$issuer-file)
(copy-file #$issuer-file #$chain)
(copy-file #$certificate-file #$chain))
(copy-file #$certificate-file #$cert-output)
(call-with-output-file #$full-pem
(lambda (port)
(display (call-with-input-file #$key-output get-string-all) port)
(display (call-with-input-file #$fullchain get-string-all) port)))
#$@(map (lambda (service)
#~(with-shepherd-action '#$service ('reload) result result))
(lego-certificate-configuration-reload-services certificate))))))))
(define (lego-certificate-service-symbol prefix certificate)
(string->symbol
(string-append prefix
"-"
(lego-certificate-configuration-name certificate))))
(define (lego-renewal-services config)
(append-map
(lambda (certificate)
(let ((program (lego-certificate-program config certificate)))
(list
(shepherd-service
(documentation
(string-append "Renew the ACME certificate for "
(lego-certificate-configuration-name certificate)
" on a timer."))
(provision
(list (lego-certificate-service-symbol "lego-renewal"
certificate)))
(requirement '(user-processes networking))
(modules '((shepherd service timer)))
(start
#~(let ((minutes '#$(lego-configuration-renew-minutes config))
(hours '#$(lego-configuration-renew-hours config)))
(make-timer-constructor
(calendar-event
#:minutes minutes
#:hours hours)
(command (list #$program))
#:wait-for-termination? #t)))
(stop #~(make-timer-destructor))
(actions
(list shepherd-trigger-action
(shepherd-configuration-action program))))
(shepherd-service
(documentation
(string-append "Attempt ACME renewal for "
(lego-certificate-configuration-name certificate)
" at boot."))
(provision
(list (lego-certificate-service-symbol "lego-bootstrap"
certificate)))
(requirement '(user-processes networking))
(one-shot? #t)
(start #~(lambda _
(zero? (system* #$program))))
(respawn? #f)))))
(lego-configuration-certificates config)))
(define (lego-activation config)
(let ((certificates (lego-configuration-certificates config)))
(with-imported-modules '((guix build utils))
#~(begin
(use-modules (guix build utils)
(ice-9 textual-ports))
(mkdir-p "/var/lib/lego")
#$@(map lego-self-signed-activation-gexp certificates)))))
(define lego-service-type
(service-type
(name 'lego)
(extensions
(list (service-extension activation-service-type
lego-activation)
(service-extension shepherd-root-service-type
lego-renewal-services)
(service-extension profile-service-type
(compose list lego-configuration-package))))
(default-value (lego-configuration))
(description "Issue and renew ACME certificates with Lego.")))

View File

@@ -22,6 +22,8 @@
tribes-configuration-working-directory
tribes-configuration-plugin-directory
tribes-configuration-host
tribes-configuration-listen-address
tribes-configuration-listen-port
tribes-configuration-scheme
tribes-configuration-port
tribes-configuration-relay-url
@@ -54,6 +56,10 @@
(default "/var/lib/tribes/plugins"))
(host tribes-configuration-host
(default "localhost"))
(listen-address tribes-configuration-listen-address
(default #f))
(listen-port tribes-configuration-listen-port
(default 4000))
(scheme tribes-configuration-scheme
(default "http"))
(port tribes-configuration-port
@@ -137,7 +143,8 @@
(list
#~(setenv "HOME" #$(tribes-configuration-working-directory config))
#~(setenv "PHX_SERVER" "true")
#~(setenv "PORT" #$(number->string (tribes-configuration-port config)))
#~(setenv "PORT" #$(number->string
(tribes-configuration-listen-port config)))
#~(setenv "PHX_HOST" #$(tribes-configuration-host config))
#~(setenv "DATABASE_URL"
#$(tribes-database-url
@@ -158,6 +165,10 @@
","))
#~(setenv "SSL_CERT_DIR" "/etc/ssl/certs")
#~(setenv "SSL_CERT_FILE" "/etc/ssl/certs/ca-certificates.crt"))
(if (tribes-configuration-listen-address config)
(list #~(setenv "BIND_ADDRESS"
#$(tribes-configuration-listen-address config)))
'())
(if (tribes-configuration-host-manifest config)
(list #~(setenv "TRIBES_HOST_MANIFEST"
#$(tribes-configuration-host-manifest config)))

129
tribes/services/vinyl.scm Normal file
View File

@@ -0,0 +1,129 @@
(define-module (tribes services vinyl)
#:use-module (gnu packages admin)
#:use-module (gnu services)
#:use-module (gnu services shepherd)
#:use-module (gnu system shadow)
#:use-module (guix gexp)
#:use-module (guix records)
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
#:use-module (tribes packages web)
#:export (vinyl-configuration
vinyl-configuration?
vinyl-configuration-package
vinyl-configuration-name
vinyl-configuration-backend
vinyl-configuration-vcl
vinyl-configuration-listen
vinyl-configuration-storage
vinyl-configuration-parameters
vinyl-configuration-extra-options
vinyl-service-type))
(define-record-type* <vinyl-configuration>
vinyl-configuration make-vinyl-configuration
vinyl-configuration?
(package vinyl-configuration-package
(default vinyl))
(name vinyl-configuration-name
(default "main"))
(backend vinyl-configuration-backend
(default "127.0.0.1:4000"))
(vcl vinyl-configuration-vcl
(default #f))
(listen vinyl-configuration-listen
(default '("0.0.0.0:6081" "[::]:6081")))
(storage vinyl-configuration-storage
(default '("malloc,256M")))
(parameters vinyl-configuration-parameters
(default '()))
(extra-options vinyl-configuration-extra-options
(default '())))
(define %vinyl-accounts
(list
(user-group
(name "vinyl")
(system? #t))
(user-account
(name "vinyl")
(group "vinyl")
(system? #t)
(comment "Vinyl cache user")
(home-directory "/var/vinyl")
(shell (file-append shadow "/sbin/nologin")))))
(define (vinyl-state-directory name)
(if (string-prefix? "/" name)
name
(string-append "/var/vinyl/" name)))
(define (vinyl-activation-gexp config)
#~(begin
(use-modules (guix build utils))
(mkdir-p "/var/vinyl")
(mkdir-p #$(vinyl-state-directory
(vinyl-configuration-name config)))))
(define (vinyl-activation configs)
#~(begin
#$@(map vinyl-activation-gexp configs)))
(define (vinyl-shepherd-services configs)
(append-map
(lambda (config)
(match config
(($ <vinyl-configuration> package name backend vcl listen storage
parameters extra-options)
(let ((pid-file (string-append (vinyl-state-directory name) "/_.pid")))
(list
(shepherd-service
(documentation (string-append "Run the Vinyl cache service (" name ")."))
(provision (list (string->symbol (string-append "vinyl-" name))))
(requirement '(user-processes networking))
(start
#~(make-forkexec-constructor
(list #$(file-append package "/sbin/vinyld")
"-n" #$name
#$@(if vcl
#~("-f" #$vcl)
#~("-b" #$backend))
#$@(append-map (lambda (address)
(list "-a" address))
listen)
#$@(append-map (lambda (storage-spec)
(list "-s" storage-spec))
storage)
#$@(append-map (lambda (parameter)
(list "-p"
(format #f "~a=~a"
(car parameter)
(cdr parameter))))
parameters)
#$@extra-options)
;; Vinyl drops privileges on its own after binding the listeners,
;; so keep the Shepherd service itself unprivileged here.
#:pid-file #$pid-file))
(stop #~(make-kill-destructor))))))))
configs))
(define (vinyl-profile-packages configs)
(delete-duplicates
(map vinyl-configuration-package configs)))
(define vinyl-service-type
(service-type
(name 'vinyl)
(extensions
(list (service-extension account-service-type
(const %vinyl-accounts))
(service-extension activation-service-type
vinyl-activation)
(service-extension shepherd-root-service-type
vinyl-shepherd-services)
(service-extension profile-service-type
vinyl-profile-packages)))
(compose concatenate)
(extend append)
(default-value '())
(description "Run the Vinyl HTTP accelerator and reverse proxy.")))

View File

@@ -19,6 +19,14 @@
(error "invalid integer environment variable" name value))
default)))
(define (getenv/bool name default)
(let ((value (getenv name)))
(if value
(if (member (string-downcase value) '("1" "true" "yes" "on"))
#t
#f)
default)))
(define (comma-list value)
(if (or (not value) (string-null? value))
'()
@@ -26,6 +34,16 @@
(filter (lambda (item) (not (string-null? item)))
(string-split value #\,)))))
(define (safe-name value)
(list->string
(map (lambda (chr)
(if (or (char-alphabetic? chr)
(char-numeric? chr)
(char=? chr #\-))
chr
#\-))
(string->list value))))
(define (tribes-installer-package)
(let ((source-directory (getenv "TRIBES_SOURCE_DIRECTORY")))
(if source-directory
@@ -47,9 +65,15 @@
"Return an installed NBDE operating-system extended with PostgreSQL and the
Tribes service. By default it uses the pinned upstream package; setting
TRIBES_SOURCE_DIRECTORY switches to a development source override."
(let* ((service-user (getenv/default "TRIBES_SERVICE_USER" "tribes"))
(let* ((edge-enabled? (getenv/bool "TRIBES_ENABLE_EDGE" #f))
(service-user (getenv/default "TRIBES_SERVICE_USER" "tribes"))
(service-group (getenv/default "TRIBES_SERVICE_GROUP" service-user))
(database-user (getenv/default "TRIBES_DATABASE_USER" service-user))
(public-host (getenv/default "TRIBES_PUBLIC_HOST" host-name))
(public-port (getenv/integer "TRIBES_HTTP_PORT"
(if edge-enabled? 443 4000)))
(listen-port (getenv/integer "TRIBES_LISTEN_PORT"
(if edge-enabled? 4000 public-port)))
(package (tribes-installer-package))
(tribes-config
(tribes-configuration
@@ -60,9 +84,14 @@ TRIBES_SOURCE_DIRECTORY switches to a development source override."
(getenv/default "TRIBES_WORKING_DIRECTORY" "/var/lib/tribes"))
(plugin-directory
(getenv/default "TRIBES_PLUGIN_DIRECTORY" "/var/lib/tribes/plugins"))
(host (getenv/default "TRIBES_PUBLIC_HOST" host-name))
(scheme (getenv/default "TRIBES_SCHEME" "http"))
(port (getenv/integer "TRIBES_HTTP_PORT" 4000))
(host public-host)
(listen-address
(getenv/default "TRIBES_LISTEN_ADDRESS"
(and edge-enabled? "127.0.0.1")))
(listen-port listen-port)
(scheme (getenv/default "TRIBES_SCHEME"
(if edge-enabled? "https" "http")))
(port public-port)
(relay-url (getenv "TRIBES_RELAY_URL"))
(host-manifest (getenv "TRIBES_HOST_MANIFEST"))
(admin-pubkeys
@@ -87,11 +116,39 @@ TRIBES_SOURCE_DIRECTORY switches to a development source override."
(comma-list (getenv "TRIBES_EXTRA_ENV")))
(log-file
(getenv/default "TRIBES_LOG_FILE" "/var/log/tribes/tribes.log"))))
(edge-config
(and edge-enabled?
(tribes-edge-configuration
(certificate-name
(getenv/default "TRIBES_ACME_CERTIFICATE_NAME"
(safe-name public-host)))
(certificate-subjects
(let ((subjects (comma-list (getenv "TRIBES_ACME_SUBJECTS"))))
(if (null? subjects)
(list public-host)
subjects)))
(certificate-email
(or (getenv "TRIBES_ACME_EMAIL")
(error "TRIBES_ACME_EMAIL is required when TRIBES_ENABLE_EDGE is true")))
(certificate-profile
(getenv/default "TRIBES_ACME_PROFILE" "shortlived"))
(renew-days (getenv/integer "TRIBES_ACME_RENEW_DAYS" 4))
(http-port (getenv/integer "TRIBES_EDGE_HTTP_PORT" 80))
(https-port (getenv/integer "TRIBES_EDGE_HTTPS_PORT" 443))
(challenge-address
(getenv/default "TRIBES_ACME_LISTEN_ADDRESS" "127.0.0.1"))
(challenge-port
(getenv/integer "TRIBES_ACME_LISTEN_PORT" 8080))
(cache-address
(getenv/default "TRIBES_EDGE_CACHE_ADDRESS" "127.0.0.1"))
(cache-port
(getenv/integer "TRIBES_EDGE_CACHE_PORT" 6081)))))
(node-config
(tribes-node-configuration
(postgresql (postgresql-configuration
(postgresql postgresql)))
(tribes tribes-config))))
(tribes tribes-config)
(edge edge-config))))
(nbde-installed-operating-system
#:host-name host-name
#:bootloader bootloader

View File

@@ -2,14 +2,62 @@
#:use-module (gnu packages databases)
#:use-module (gnu services)
#:use-module (gnu services databases)
#:use-module (guix gexp)
#:use-module (guix records)
#:use-module (srfi srfi-13)
#:use-module (tribes services hitch)
#:use-module (tribes services lego)
#:use-module (tribes services tribes)
#:export (tribes-node-configuration
#:use-module (tribes services vinyl)
#:export (tribes-edge-configuration
tribes-edge-configuration?
tribes-edge-configuration-certificate-name
tribes-edge-configuration-certificate-subjects
tribes-edge-configuration-certificate-email
tribes-edge-configuration-certificate-profile
tribes-edge-configuration-renew-days
tribes-edge-configuration-http-port
tribes-edge-configuration-https-port
tribes-edge-configuration-challenge-address
tribes-edge-configuration-challenge-port
tribes-edge-configuration-cache-address
tribes-edge-configuration-cache-port
tribes-edge-configuration-cache-storage
tribes-node-configuration
tribes-node-configuration?
tribes-node-configuration-postgresql
tribes-node-configuration-tribes
tribes-node-configuration-edge
tribes-node-services))
(define-record-type* <tribes-edge-configuration>
tribes-edge-configuration make-tribes-edge-configuration
tribes-edge-configuration?
(certificate-name tribes-edge-configuration-certificate-name
(default "tribes"))
(certificate-subjects tribes-edge-configuration-certificate-subjects
(default '()))
(certificate-email tribes-edge-configuration-certificate-email
(default #f))
(certificate-profile tribes-edge-configuration-certificate-profile
(default "shortlived"))
(renew-days tribes-edge-configuration-renew-days
(default 4))
(http-port tribes-edge-configuration-http-port
(default 80))
(https-port tribes-edge-configuration-https-port
(default 443))
(challenge-address tribes-edge-configuration-challenge-address
(default "127.0.0.1"))
(challenge-port tribes-edge-configuration-challenge-port
(default 8080))
(cache-address tribes-edge-configuration-cache-address
(default "127.0.0.1"))
(cache-port tribes-edge-configuration-cache-port
(default 6081))
(cache-storage tribes-edge-configuration-cache-storage
(default '("malloc,256M"))))
(define-record-type* <tribes-node-configuration>
tribes-node-configuration make-tribes-node-configuration
tribes-node-configuration?
@@ -17,7 +65,9 @@
(default (postgresql-configuration
(postgresql postgresql))))
(tribes tribes-node-configuration-tribes
(default (tribes-configuration))))
(default (tribes-configuration)))
(edge tribes-node-configuration-edge
(default #f)))
(define (tribes-node-postgresql-roles config)
(let ((tribes (tribes-node-configuration-tribes config)))
@@ -26,12 +76,169 @@
(name (tribes-configuration-database-user tribes))
(permissions '(createdb login))))))
(define (edge-certificate-subjects edge tribes)
(let ((subjects (tribes-edge-configuration-certificate-subjects edge)))
(if (null? subjects)
(list (tribes-configuration-host tribes))
subjects)))
(define (edge-certificate-config edge tribes)
(let ((subjects (edge-certificate-subjects edge tribes))
(email (tribes-edge-configuration-certificate-email edge)))
(unless email
(error "edge certificate email is required"
(tribes-edge-configuration-certificate-name edge)))
(lego-certificate-configuration
(name (tribes-edge-configuration-certificate-name edge))
(subjects subjects)
(email email)
(profile (tribes-edge-configuration-certificate-profile edge))
(listen-http
(format #f "~a:~a"
(tribes-edge-configuration-challenge-address edge)
(tribes-edge-configuration-challenge-port edge)))
(renew-days (tribes-edge-configuration-renew-days edge))
(reload-services '(hitch)))))
(define (edge-http-vcl edge)
(plain-file
"tribes-edge-http.vcl"
(string-append
"vcl 4.1;\n\n"
"backend acme {\n"
" .host = \"" (tribes-edge-configuration-challenge-address edge) "\";\n"
" .port = \"" (number->string
(tribes-edge-configuration-challenge-port edge)) "\";\n"
"}\n\n"
"sub vcl_recv {\n"
" if (req.url ~ \"^/\\\\.well-known/acme-challenge/\") {\n"
" return (pass);\n"
" }\n\n"
" return (synth(750));\n"
"}\n\n"
"sub vcl_synth {\n"
" if (resp.status == 750) {\n"
" set resp.status = 308;\n"
" set resp.http.Location = \"https://\" + req.http.host + req.url;\n"
" return (deliver);\n"
" }\n"
"}\n\n"
"sub vcl_backend_response {\n"
" set beresp.uncacheable = true;\n"
" return (deliver);\n"
"}\n")))
(define (edge-cache-vcl edge tribes)
(plain-file
"tribes-edge-cache.vcl"
(string-append
"vcl 4.1;\n\n"
"backend tribes {\n"
" .host = \"" (or (tribes-configuration-listen-address tribes)
"127.0.0.1") "\";\n"
" .port = \"" (number->string
(tribes-configuration-listen-port tribes)) "\";\n"
" .probe = {\n"
" .url = \"/healthz\";\n"
" .interval = 5s;\n"
" .timeout = 1s;\n"
" .window = 5;\n"
" .threshold = 3;\n"
" }\n"
"}\n\n"
"sub vcl_recv {\n"
" set req.http.X-Forwarded-Proto = \"https\";\n"
" set req.http.X-Forwarded-Port = \"" (number->string
(tribes-edge-configuration-https-port edge))
"\";\n"
" set req.http.X-Forwarded-Host = req.http.host;\n"
" if (req.restarts == 0) {\n"
" if (req.http.X-Forwarded-For) {\n"
" set req.http.X-Forwarded-For = req.http.X-Forwarded-For + \", \" + client.ip;\n"
" } else {\n"
" set req.http.X-Forwarded-For = client.ip;\n"
" }\n"
" }\n\n"
" if (req.http.Upgrade ~ \"(?i)websocket\" ||\n"
" req.url ~ \"^/(live|ws/gql|nostr/relay)\") {\n"
" return (pipe);\n"
" }\n\n"
" if (req.method != \"GET\" && req.method != \"HEAD\") {\n"
" return (pass);\n"
" }\n\n"
" if (req.url ~ \"^/(assets/|fonts/|images/)\" ||\n"
" req.url ~ \"^/(favicon\\\\.ico|robots\\\\.txt)$\") {\n"
" return (hash);\n"
" }\n\n"
" return (pass);\n"
"}\n\n"
"sub vcl_backend_response {\n"
" if (bereq.url ~ \"^/(assets/|fonts/|images/)\" ||\n"
" bereq.url ~ \"^/(favicon\\\\.ico|robots\\\\.txt)$\") {\n"
" unset beresp.http.Set-Cookie;\n"
" set beresp.ttl = 1h;\n"
" set beresp.grace = 5m;\n"
" return (deliver);\n"
" }\n\n"
" set beresp.uncacheable = true;\n"
" set beresp.ttl = 0s;\n"
" return (deliver);\n"
"}\n\n"
"sub vcl_deliver {\n"
" if (obj.hits > 0) {\n"
" set resp.http.X-Cache = \"HIT\";\n"
" } else {\n"
" set resp.http.X-Cache = \"MISS\";\n"
" }\n"
"}\n")))
(define (edge-services config)
(let* ((tribes (tribes-node-configuration-tribes config))
(edge (tribes-node-configuration-edge config))
(certificate (edge-certificate-config edge tribes))
(cache-port (tribes-edge-configuration-cache-port edge))
(http-port (tribes-edge-configuration-http-port edge))
(https-port (tribes-edge-configuration-https-port edge)))
(list
(service lego-service-type
(lego-configuration
(certificates (list certificate))))
(service vinyl-service-type
(list
(vinyl-configuration
(name "tribes-http")
(vcl (edge-http-vcl edge))
(listen (list (string-append "0.0.0.0:" (number->string http-port))
(string-append "[::]:" (number->string http-port))))
(storage '("malloc,64M")))
(vinyl-configuration
(name "tribes-edge")
(vcl (edge-cache-vcl edge tribes))
(listen
(list (string-append
(tribes-edge-configuration-cache-address edge)
":"
(number->string cache-port))))
(storage (tribes-edge-configuration-cache-storage edge)))))
(service hitch-service-type
(hitch-configuration
(backend (format #f "[~a]:~a"
(tribes-edge-configuration-cache-address edge)
cache-port))
(frontends (list (format #f "[0.0.0.0]:~a" https-port)
(format #f "[::]:~a" https-port)))
(pem-files (list (lego-certificate-full-pem certificate))))))))
(define (tribes-node-services config)
(list
(service postgresql-service-type
(tribes-node-configuration-postgresql config))
(simple-service 'tribes-postgresql-roles
postgresql-role-service-type
(tribes-node-postgresql-roles config))
(service tribes-service-type
(tribes-node-configuration-tribes config))))
(append
(list
(service postgresql-service-type
(tribes-node-configuration-postgresql config))
(simple-service 'tribes-postgresql-roles
postgresql-role-service-type
(tribes-node-postgresql-roles config))
(service tribes-service-type
(tribes-node-configuration-tribes config)))
(if (tribes-node-configuration-edge config)
(edge-services config)
'())))