aboutsummaryrefslogtreecommitdiff
path: root/tw/services/paperless.scm
blob: 75998eee89680645e6b7eb09d3c37dc6719bddc8 (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
(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 (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))
               (websocket-uri "/ws/"))))))   ; e.g. /ws/status/ endpoint

(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.")))