(define-module (tw services restic) #:use-module (gnu) #:use-module ((gnu packages admin) #:select (shadow)) #:use-module (gnu services) #:use-module (gnu services configuration) #:use-module (gnu services shepherd) #:use-module (guix gexp) #:use-module (guix packages) #:use-module (tw packages restic) #:export (restic-server-service-type restic-server-configuration)) (define-maybe/no-serialization integer) (define-maybe/no-serialization string) ;; TODO: implement --tls, --tls-cert and --tls-key, maybe using certbot-service-type? (define-configuration/no-serialization restic-server-configuration (repository-path (string "/var/lib/restic") "The directory containing restic's repositories and @code{.htpasswd} file, unless otherwise configured using @code{htpasswd-file}.") (restic-server (package restic-rest-server) "The restic REST server package to use.") (bind-address (string ":8000") "The listen address (including port) to bind to.") (htpasswd-file (maybe-string #f) "Location of @code{.htpasswd} file (default: @code{REPOSITORY-PATH/.htpasswd}).") (auth? (boolean #t) "Whether to authenticate users at all (using .htpasswd).") (verify-upload? (boolean #t) "Whether to verify the integrity of uploaded data. @emph{Do not disable} unless the restic server is to be run on a very low-power device.") (append-only? (boolean #f) "Whether to run the restic server in append-only mode.") (max-repository-size (maybe-integer %unset-value) "Maximum repository size in bytes, if any.") (private-repos-only? (boolean #f) "Whether to let users only access their private restic repos.") (prometheus? (boolean #f) "Whether to serve Prometheus metrics.") (prometheus-auth? (boolean #t) "Whether to require authentication as the @code{metrics} user to access the Prometheus /metrics endpoint.")) (define (restic-server-arguments config) "Turn CONFIG into a list of arguments to the restic-rest-server executable." `("--path" ,(restic-server-configuration-repository-path config) "--log" "/var/log/restic-server.log" "--listen" ,(restic-server-configuration-bind-address config) ,@(if (restic-server-configuration-append-only? config) '("--append-only") '()) ,@(let ((max-size (restic-server-configuration-max-repository-size config))) (if (integer? max-size) `("--max-size" ,max-size) '())) ,@(if (restic-server-configuration-private-repos-only? config) '("--private-repos") '()) ,@(if (restic-server-configuration-prometheus? config) '("--prometheus") '()) ,@(if (restic-server-configuration-prometheus-auth? config) '() '("--prometheus-no-auth")))) (define (restic-server-service config) "Create a `shepherd-service' for the restic REST server from CONFIG." (list (shepherd-service (provision '(restic-server)) (requirement '(networking)) (documentation "Run the Restic REST server to serve backup repositories via HTTP.") (start #~(make-forkexec-constructor (list #$(file-append (restic-server-configuration-restic-server config) "/bin/restic-rest-server") #$@(restic-server-arguments config)) #:user "restic" #:group "restic")) (stop #~(make-kill-destructor))))) (define (restic-server-accounts config) "Create user accounts and groups for the restic REST server defined in CONFIG." (list (user-account (name "restic") (group "restic") (comment "Restic server user") (system? #t) (home-directory (restic-server-configuration-repository-path config)) (shell (file-append shadow "/sbin/nologin"))) (user-group (name "restic") (system? #t)))) (define restic-server-service-type (service-type (name 'restic-server) (extensions (list (service-extension shepherd-root-service-type restic-server-service) (service-extension account-service-type restic-server-accounts))) (description "Restic REST server, running as a service user instead of root.")))