(define-module (tw services paperless) #:use-module (gnu) #:use-module ((gnu packages admin) #:select (shadow)) #:use-module (gnu services) #:use-module (gnu services configuration) #:use-module (gnu services databases) #:use-module (gnu services web) #:use-module (guix records) #:use-module (tw services docker) #:use-module (tw services restic) #:use-module (tw services web) #:export (paperless-service-type paperless-configuration)) (define %paperless-user "paperless") (define %paperless-uid 481) ; randomly chosen to avoid collisions (define-configuration/no-serialization paperless-configuration (container (string "ghcr.io/paperless-ngx/paperless-ngx:2.5") "Container image to run.") (domain (string "localhost") "The external domain which will resolve to this Grafana instance.") (bind-address (string "127.0.0.1") "The host IP to bind to.") (data-path (string "/var/lib/paperless") "The path to store data in, on the host.") (secret-key-file string "A file name containing a @code{PAPERLESS_SECRET_KEY=...} assignment. This is used to create session tokens, and must be changed from the default. Make sure this file is readable only by the root user.")) (define (paperless-accounts config) (list (user-account (name %paperless-user) (uid %paperless-uid) (group %paperless-user) (comment "Paperless server user") (system? #t) (home-directory (paperless-configuration-data-path config)) (shell (file-append shadow "/sbin/nologin"))) (user-group (name %paperless-user) (id %paperless-uid) (system? #t)))) (define (paperless-environment config) (match-record config (domain bind-address) (plain-file "paperless.env" (string-append "\ PAPERLESS_BIND_ADDR=" bind-address " PAPERLESS_URL=https://" domain " PAPERLESS_OCR_LANGUAGES=eng deu fra por PAPERLESS_TIME_ZONE=Europe/Berlin PAPERLESS_UID=" (number->string %paperless-uid) " PAPERLESS_GID=" (number->string %paperless-uid) " ")))) (define (paperless-docker-service config) (match-record config (container data-path secret-key-file) (list (docker-container-configuration (name "paperless") (image container) (read-only-root? #f) ; wrapper script runs "apt" in container (volumes ;; We need to mount each subdir separately because the container ;; image specifies volumes to be mounted there otherwise. (map (lambda (subdir) (list (string-append data-path "/" subdir) (string-append "/usr/src/paperless/" subdir) #t)) '("consume" "data" "export" "media"))) (environment-files (list (paperless-environment config) secret-key-file)) (network-type "host") ;; Paperless connects to redis://localhost:6379 (default port) by default. (extra-requirements '(redis)))))) (define (paperless-reverse-proxy config) (match-record config (domain bind-address) (if (string=? domain "localhost") (list) (list (https-reverse-proxy-configuration (domains (list domain)) ;; The container runs on port 8000 and has a health check with ;; that port hardcoded, so just use that. (destination-port 8000) (destination-ip (if (string=? bind-address "0.0.0.0") "127.0.0.1" bind-address)) (extra-locations (list (nginx-location-configuration (uri "/ws/") ; e.g. /ws/status/ endpoint ;; https://nginx.org/en/docs/http/websocket.html (body `(("proxy_pass http://" ,destination-ip ":" ,(number->string destination-port) ";") "proxy_http_version 1.1;" "proxy_set_header Upgrade $http_upgrade;" "proxy_set_header Connection \"upgrade\";")))))))))) (define %paperless-backup-repo (restic-local-repository (path "/var/backups/paperless"))) (define %paperless-backup-password (restic-password-source (type 'file) (name "/etc/restic/lud-paperless"))) (define (paperless-backups config) (match-record config (data-path) (list (restic-scheduled-backup (schedule #~"0 5 * * *") (paths (list data-path)) (repo %paperless-backup-repo) (password %paperless-backup-password))))) (define (paperless-backup-cleanup config) (list (restic-scheduled-cleanup (schedule #~"0 12 * * *") (repo %paperless-backup-repo) (password %paperless-backup-password) (keep-daily 30) (keep-monthly -1)))) (define paperless-service-type (service-type (name 'paperless) (extensions (list (service-extension docker-container-service-type paperless-docker-service) (service-extension account-service-type paperless-accounts) (service-extension https-reverse-proxy-service-type paperless-reverse-proxy) (service-extension restic-backup-service-type paperless-backups) (service-extension restic-cleanup-service-type paperless-backup-cleanup))) (description "Paperless server, running under Docker.")))