Last active 3 months ago

As shown on https://youtu.be/P-Pr7Dy6mP8

.env Raw
1# The UID and GID of the user used to run paperless in the container. Set this
2# to your UID and GID on the host so that you have write access to the
3# consumption directory.
4USERMAP_UID=1001
5USERMAP_GID=1001
6
7# Database credentials
8POSTGRES_DB=paperless
9POSTGRES_USER=paperless
10POSTGRES_PASSWORD=paperless
11
12PAPERLESS_DBHOST=paperless-ngx-db
13PAPERLESS_DBNAME=$POSTGRES_DB
14PAPERLESS_DBUSER=$POSTGRES_USER
15PAPERLESS_DBPASS=$POSTGRES_PASSWORD
16
17# Additional languages to install for text recognition, separated by a
18# whitespace. Note that this is
19# different from PAPERLESS_OCR_LANGUAGE (default=eng), which defines the
20# language used for OCR.
21# The container installs English, German, Italian, Spanish and French by
22# default.
23# See https://packages.debian.org/search?keywords=tesseract-ocr-&searchon=names&suite=buster
24# for available languages.
25PAPERLESS_OCR_LANGUAGES=deu fra eng nld
26
27###############################################################################
28# Paperless-specific settings #
29###############################################################################
30
31# All settings defined in the paperless.conf.example can be used here. The
32# Docker setup does not use the configuration file.
33# A few commonly adjusted settings are provided below.
34
35# This is required if you will be exposing Paperless-ngx on a public domain
36# (if doing so please consider security measures such as reverse proxy)
37PAPERLESS_URL=https://paperless.domain.tld
38# Adjust this key if you plan to make paperless available publicly. It should
39# be a very long sequence of random characters. You don't need to remember it.
40PAPERLESS_SECRET_KEY=GENERATEME
41
42# Use this variable to set a timezone for the Paperless Docker containers. If not specified, defaults to UTC.
43PAPERLESS_TIME_ZONE=Europe/Berlin
44
45# The default language to use for OCR. Set this to the language most of your
46# documents are written in.
47PAPERLESS_OCR_LANGUAGE=deu
48
49# Set if accessing paperless via a domain subpath e.g. https://domain.com/PATHPREFIX and using a reverse-proxy like traefik or nginx
50#PAPERLESS_FORCE_SCRIPT_NAME=/PATHPREFIX
51#PAPERLESS_STATIC_URL=/PATHPREFIX/static/ # trailing slash required
52
53PAPERLESS_CONSUMER_ENABLE_BARCODES=true
54
55PAPERLESS_OCR_USER_ARGS= '{"continue_on_soft_render_error": true, "invalidate_digital_signatures": true}'
56
57PAPERLESS_USE_X_FORWARD_HOST=true
58PAPERLESS_CONSUMER_POLLING=10
59PAPERLESS_CONSUMER_POLLING_RETRY_COUNT=5
60PAPERLESS_CONSUMER_POLLING_DELAY=20
61
62PAPERLESS_ADMIN_USER=MyAmazingAdmin
63PAPERLESS_ADMIN_MAIL=alias@domain.tld
64PAPERLESS_ADMIN_PASSWORD="SuperSecure!"
65
66PAPERLESS_EMAIL_HOST=mail.domain.tld
67PAPERLESS_EMAIL_PORT=587
68PAPERLESS_EMAIL_HOST_USER=paperless@domain.tld
69PAPERLESS_EMAIL_HOST_PASSWORD="SuperSecureToo!"
70PAPERLESS_EMAIL_USE_TLS=false
71PAPERLESS_EMAIL_USE_SSL=true
72
73
README Raw
1Last change: 2025-09-25 Marcel Herrguth
2As shown on https://youtu.be/P-Pr7Dy6mP8
3Attribution back to this GIST is required!
4
5Parts of this script are oriented from https://github.com/zammad/zammad/tree/develop/contrib/backup
6This script supports PostGreSQL (docker) stacks only and expects your media to be in a mounted folder.
7
8Restoration only works on a pre-installed Paperless NGX installation of the same version (or higher).
9During Restore, this script will remove the existing database and stop all relevant containers.
10Containers will be re-started after the restoration finished.
11
12COMMAND REFERENCE
13- backup: creates a new snapshot and backs up database and storage.
14- restore <snapshot>: Restore a given snapshot from your backup. If you omit the snapshot ID, latest will be used.
15- list_snapshots: List all available snapshot IDs.
16- verify_health <percentage>: Verify the health of your backup repository. If you omit percentage, 100% will be assumed.
docker-compose.yml Raw
1services:
2 paperless-ngx-broker:
3 image: docker.io/library/redis:8.2-alpine
4 container_name: paperless-ngx-broker
5 hostname: paperless-ngx-broker
6 restart: always
7 networks:
8 - paperless-ngx
9 volumes:
10 - ./data/redis:/data
11
12 paperless-ngx-db:
13 image: docker.io/library/postgres:17-alpine
14 container_name: paperless-ngx-db
15 hostname: paperless-ngx-db
16 restart: always
17 networks:
18 - paperless-ngx
19 volumes:
20 - ./pg:/var/lib/postgresql/data
21
22 paperless-ngx-webserver:
23 image: ghcr.io/paperless-ngx/paperless-ngx:2.18.2
24 restart: always
25 container_name: paperless-ngx-webserver
26 hostname: paperless-ngx-webserver
27 depends_on:
28 - paperless-ngx-db
29 - paperless-ngx-broker
30 - paperless-ngx-gotenberg
31 - paperless-ngx-tika
32 networks:
33 - paperless-ngx
34 - external_network
35 volumes:
36 - ./data/paperless:/usr/src/paperless/data
37 - ./data/media:/usr/src/paperless/media
38 - ./volume/_consume:/usr/src/paperless/consume:z
39 - ./volume/_export:/usr/src/paperless/export:z
40 ports:
41 - "127.0.0.1:8000:8000"
42 env_file: .env
43 environment:
44 PAPERLESS_REDIS: redis://paperless-ngx-broker:6379
45 PAPERLESS_TIKA_ENABLED: 1
46 PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://paperless-ngx-gotenberg:3000
47 PAPERLESS_TIKA_ENDPOINT: http://paperless-ngx-tika:9998
48
49 paperless-ngx-gotenberg:
50 image: docker.io/gotenberg/gotenberg:8.22
51 restart: always
52 networks:
53 - paperless-ngx
54 container_name: paperless-ngx-gotenberg
55 hostname: paperless-ngx-gotenberg
56
57 # The gotenberg chromium route is used to convert .eml files. We do not
58 # want to allow external content like tracking pixels or even javascript.
59 # environment:
60 # CHROMIUM_DISABLE_ROUTES: 1
61 command:
62 - 'gotenberg'
63 - "--chromium-disable-javascript=true"
64 - "--chromium-allow-list=file:///tmp/.*"
65 # - "--chromium-disable-routes=true"
66 # - "--chromium-restart-after=5"
67 # - "--chromium-auto-start=true"
68 # - "--chromium-ignore-certificate-errors=true"
69 # - "--chromium-disable-web-security=true"
70 # - '--chromium-allow-insecure-localhost=true'
71 # - "--chromium-start-timeout=30s"
72 # - "--uno-listener-start-timeout=180s"
73 # - "--libreoffice-disable-routes=false"
74 # - "--libreoffice-auto-start=true"
75 # - "--libreoffice-restart-after=5"
76 # - '--libreoffice-start-timeout=30s'
77 # - '--api-timeout=3000s'
78
79
80 paperless-ngx-tika:
81 image: ghcr.io/paperless-ngx/tika:2.9.1-full
82 container_name: paperless-ngx-tika
83 hostname: paperless-ngx-tika
84 restart: always
85 networks:
86 - paperless-ngx
87
88networks:
89 paperless-ngx:
90 name: paperless-ngx
91 internal: true
92 external_network:
93 enable_ipv6: true
94 ipam:
95 config:
96 - subnet: fd63:e614:1cf8:fb00::2:0/112
97
98# volumes:
99# consume:
100# driver: local
101# driver_opts:
102# type: nfs
103# o: addr=yamato.tha.vpn,nfsvers=4.1,nolock,soft,rw,async
104# device: :/volume1/import/_consume
restic_paperless.sh Raw
1#!/usr/bin/env bash
2#
3# 2025-09-25 Marcel Herrguth
4# As shown on https://youtu.be/P-Pr7Dy6mP8
5# Attribution back to this GIST is required!
6#
7# Parts of this script are oriented from https://github.com/zammad/zammad/tree/develop/contrib/backup
8# This script supports PostGreSQL (docker) stacks only and expects your media to be in a mounted folder.
9#
10# Restoration only works on a pre-installed Paperless NGX installation of the same version (or higher).
11# During Restore, this script will remove the existing database and stop all relevant containers.
12# Containers will be re-started after the restoration finished.
13
14#----- CONFIG -------------------------------------------------------------------------------#
15# Use either a directory or a supported repository type (like s3:https//domain.tld/bucket)
16BACKUP_REPOSITORY='s3:https://s3.domain.tld/resticsample'
17
18# Data directory where your Paperless NGX stores your files
19# If you're using volumes, use /var/lib/docker/volumes/paperless_*
20# Alternatively, use several folder-paths seperated by space if needed
21DATA_DIR='/opt/paperless-ngx/data/'
22# Path and filename of the environment file of your stack
23# This file will be evaluated for access credentials
24ENV_FILE='/opt/paperless-ngx/.env'
25# Paperless Stack name
26# Used as prefix for being able to
27STACK_NAME='paperless-ngx'
28# If you're not using PostGreSQL but SQlite, set the following to 'no'
29BACKUP_DATABASE='yes'
30
31# How many daily and hourly snapshot should stay available?
32HOLD_DAYS=14
33HOLD_HOURLY=1
34
35# Do you want to cause downtime during backup? (This is the safest backup way)
36STOP_DURING_BACKUP='yes'
37#----- CONFIG END ---------------------------------------------------------------------------#
38
39# ---- Magic --------------------------------------------------------------------------------#
40function pre_flight () {
41 # Verify that we have all we need.
42 if [[ ""$BACKUP_REPOSITORY"" == s3:* ]]; then
43 if [[ -z "${AWS_ACCESS_KEY_ID}" || -z "${AWS_SECRET_ACCESS_KEY}" ]]; then
44 echo "You have chosen to backup to S3 (it seems), but either AWS_ACCESS_KEY_ID and/or AWS_SECRET_ACCESS_KEY."
45 exit 1
46 fi
47 else
48 echo "This script has been tested with S3 only. This should be fine."
49 echo "Ensure that the required environment information for restic are available as per their documentation:"
50 echo "https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html"
51 fi
52
53 if [[ -z "${RESTIC_PASSWORD}" ]]; then
54 echo "You forgot to set RESTIC_PASSWORD which is mandatory for your backup repository!"
55 exit 1
56 fi
57
58 # Get all currently running NGX containers
59 DC_DB=$(docker ps| grep "${STACK_NAME}-db" |cut -d " " -f1)
60 DC_BROKER=$(docker ps| grep "${STACK_NAME}-broker" |cut -d " " -f1)
61 DC_GOTENBERG=$(docker ps| grep "${STACK_NAME}-gotenberg" |cut -d " " -f1)
62 DC_TIKA=$(docker ps| grep "${STACK_NAME}-tika" |cut -d " " -f1)
63 DC_WEB=$(docker ps| grep "${STACK_NAME}-webserver" |cut -d " " -f1)
64}
65
66function start_paperless () {
67 echo "# Starting Paperless"
68 docker start $DC_WEB $DC_TIKA $DC_GOTENBERG $DC_BROKER
69}
70
71function stop_paperless () {
72 echo "# Stopping Paperless"
73 docker stop $DC_WEB $DC_TIKA $DC_GOTENBERG $DC_BROKER
74
75 for i in {15..1}; do
76 echo -ne "... Waiting $i seconds for the stack to stop.\r"; sleep 1
77 done
78}
79
80function ensure_variable_set () {
81 if [ -z $1 ]; then
82 echo "ERROR: environment variable ${1} not set!"
83 exit 1
84 fi
85}
86
87function get_db_credentials () {
88 if [ ! -f $ENV_FILE ]; then
89 echo "ERROR: Could not find the configured environment file!"
90 exit 1
91 fi
92
93 eval $(grep -E '^(POSTGRES_DB|POSTGRES_USER|POSTGRES_PASSWORD|USERMAP_UID|USERMAP_GID)=' "$ENV_FILE")
94}
95
96function kind_exit () {
97 # We're nice to our admin and bring Zammad back up before exiting
98 start_paperless
99 exit 1
100}
101
102function delete_old_backups () {
103 echo "# Invoking cleanup as per snapshot rules"
104 restic -r "$BACKUP_REPOSITORY" forget --group-by '' --keep-hourly $HOLD_HOURLY --keep-daily $HOLD_DAYS --prune
105}
106
107function write_backup () {
108 stop_paperless if "${STOP_DURING_BACKUP}x" == 'yesx'
109 echo "# Creating postgresql backup..."
110
111 docker exec $DC_DB pg_dump --dbname "${POSTGRES_DB}" \
112 --username "${POSTGRES_USER}" \
113 --no-privileges --no-owner > /tmp/paperless_db.psql
114
115 state=$?
116
117 if [ "${state}" == "1" ]; then
118 echo -e "\n\n # ERROR(${state}) - Database credentials are wrong or database server configuration is invalid."
119 echo -e " #-> BACKUP WAS NOT SUCCESSFUL"
120
121 kind_exit
122 exit 2
123 fi
124
125 restic -r "$BACKUP_REPOSITORY" migrate
126 restic --no-scan --read-concurrency=25 --pack-size=128 --compression off -r "$BACKUP_REPOSITORY" backup \
127 ${DATA_DIR} /tmp/paperless_db.psql
128
129 state=$?
130
131 # clean up temporary database dump
132 rm -f /tmp/paperless_db.psql
133
134 start_paperless if "${STOP_DURING_BACKUP}x" == 'yesx'
135
136 if [ $state == '1' ]; then
137 echo "# FATAL - Restic could not create the snapshot."
138 elif [ $state == '3' ]; then
139 echo "# WARNING - Files changed during snapshot creation; Snapshot potentially incomplete!"
140 fi
141
142 if [ $state -gt 0 ]; then
143 echo "# BACKUP WAS NOT SUCCESSFUL"
144 exit $state
145 fi
146}
147
148function list_available_snapshots () {
149 pre_flight
150 echo "# Here's your currently available snapshots in your backup repository:"
151 restic -r "$BACKUP_REPOSITORY" snapshots
152}
153
154function verify_backup_repository_health () {
155 pre_flight
156
157 VERIFY_PERCENTAGE="${1:-100}"
158
159 echo "# Checking ${VERIFY_PERCENTAGE} of your repository ..."
160 restic -r "$BACKUP_REPOSITORY" check --read-data-subset="${VERIFY_PERCENTAGE}%"
161}
162
163function get_snapshot_id () {
164 if [ -n "${1}" ]; then
165 RESTORE_SNAPSHOT_ID="${1}"
166 else
167 # User did not provide snapshot so we'll hard guess 'latest'
168 RESTORE_SNAPSHOT_ID='latest'
169
170 echo "... No Snapshot provided, guessing you want to restore 'latest'!"
171 for i in {15..1}; do
172 echo -ne "... You have $i seconds to abort.\r"; sleep 1
173 done
174 fi
175}
176
177function restore_backup () {
178 stop_paperless
179
180 echo "# ... Dropping current database ${POSTGRES_DB}"
181
182 docker exec $DC_DB psql -U ${POSTGRES_USER} -c "\c postgres; DROP DATABASE IF EXISTS ${POSTGRES_DB}; CREATE DATABASE ${POSTGRES_DB} OWNED BY ${POSTGRES_USER};"
183
184 echo "# Restoring PostgreSQL DB"
185
186 # We're removing uncritical dump information that caused "ugly" error
187 # messages on older script versions. These could safely be ignored.
188 restic -r "$BACKUP_REPOSITORY" dump latest '/tmp/paperless_db.psql' | \
189 sed '/^CREATE EXTENSION IF NOT EXISTS plpgsql/d'| \
190 sed '/^COMMENT ON EXTENSION plpgsql/d'| \
191 docker exec $DC_DB psql -U ${POSTGRES_USER} ${POSTGRES_DB}
192
193 state=$?
194
195 if [[ ("${state}" == "1") || ( "${state}" == "2") || ( "${state}" == "3") ]]; then
196 # We're checking for critical restoration errors
197 # It may not cover all possible errors which is out of scope of this script
198 echo -e "\n\n # ERROR(${state}) - Database credentials are wrong or database server configuration is invalid."
199 echo -e " #-> RESTORE WAS NOT SUCCESSFUL"
200
201 kind_exit
202 exit 2
203 fi
204
205 echo "# Restoring Files"
206 restic -r "$BACKUP_REPOSITORY" restore $RESTORE_SNAPSHOT_ID --include "$DATA_DIR" --target /
207
208 state=$?
209
210 if [[ ($state == '1') || ($state == '2') ]]; then
211 echo "# ERROR(${state}) - File restore reported an error."
212 echo "- Check file permissions, and ensure Zammad IS NOT running, and try again."
213 echo -e " \n# RESTORE WAS NOT SUCCESSFUL"
214 exit 1
215 fi
216
217 echo "# Ensuring correct file permissions ..."
218 chown -R ${USERMAP_UID}:${USERMAP_GID} ${DATA_DIR}
219
220 start_paperless
221}
222
223function start_backup_message () {
224 echo -e "\n# Backup script started - $(date)!\n"
225}
226
227function start_restore_message () {
228 echo -e "\n# Restore script started - $(date)!\n"
229}
230
231function finished_backup_message () {
232 echo -e "\n# Backup script finished; Check output! - $(date)!\n"
233}
234
235function finished_restore_message () {
236 echo -e "\n# Restore script finished; Check output! - $(date)!\n"
237}
238
239function execute_backup () {
240 pre_flight
241 get_db_credentials
242 start_backup_message
243 write_backup
244 delete_old_backups
245 finished_backup_message
246}
247
248function execute_restoration () {
249 pre_flight
250 get_db_credentials
251 start_restore_message
252 get_snapshot_id $1
253 restore_backup
254 finished_restore_message
255}
256
257function command_reference () {
258 echo "COMMAND REFERENCE"
259 echo "- backup: creates a new snapshot and backs up database and storage."
260 echo "- restore <snapshot>: Restore a given snapshot from your backup. If you omit the snapshot ID, latest will be used."
261 echo "- list_snapshots: List all available snapshot IDs."
262 echo "- verify_health <percentage>: Verify the health of your backup repository. If you omit percentage, 100% will be assumed."
263}
264
265# ---- Option part and control --------------------------------------------------------------#
266
267case "$1" in
268 backup)
269 execute_backup
270 ;;
271
272 restore)
273 execute_restoration $2
274 ;;
275
276 list_snapshots)
277 list_available_snapshots
278 ;;
279
280 verify_health)
281 verify_backup_repository_health "$2"
282 ;;
283
284 *)
285 command_reference
286 ;;
287esac