aboutsummaryrefslogtreecommitdiff
path: root/tw/services/paperless.scm
blob: e115ea57b9cb90206b71407816a6a8b736f94ed4 (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
(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 <paperless-configuration> (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) "
# OCRmyPDF normally refuses to process signed PDFs, but Paperless-ngx saves
# the original file in addition to the OCR'd one, so it's fine. Also, continue
# even if ghostscript can't parse the processed PDF file, for some reason.
PAPERLESS_OCR_USER_ARGS={\"invalidate_digital_signatures\": true, \"continue_on_soft_render_error\": true}
"))))

(define (paperless-docker-service config)
  (match-record config <paperless-configuration> (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 <paperless-configuration> (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 <paperless-configuration> (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.")))