From 880f08599ad7534fb99828bd0d695463fccb9810 Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Fri, 7 Feb 2025 11:25:54 -0800 Subject: [PATCH] Transitions the Docker image to use s6 and s6-overlay for process supervision instead of supervisord (#8886) --- .dockerignore | 2 + .github/workflows/ci.yml | 2 +- .ruff.toml | 4 + Dockerfile | 118 +++++++++-------- docker/docker-prepare.sh | 120 ------------------ docker/env-from-file.sh | 42 ------ docker/flower-conditional.sh | 12 -- docker/init-flow.drawio.png | Bin 0 -> 30635 bytes docker/install_management_commands.sh | 6 +- docker/management_script.sh | 16 +-- docker/paperless_cmd.sh | 16 --- .../etc/ImageMagick-6/paperless-policy.xml} | 0 .../dependencies.d/init-custom-init | 0 .../dependencies.d/init-env-file | 0 .../init-complete/dependencies.d/init-folders | 0 .../dependencies.d/init-migrations | 0 .../dependencies.d/init-modify-user | 0 .../dependencies.d/init-search-index | 0 .../init-complete/dependencies.d/init-start | 0 .../dependencies.d/init-superuser | 0 .../dependencies.d/init-system-checks | 0 .../dependencies.d/init-tesseract-langs | 0 .../dependencies.d/init-wait-for-db | 0 .../dependencies.d/init-wait-for-redis | 0 .../etc/s6-overlay/s6-rc.d/init-complete/run | 8 ++ .../etc/s6-overlay/s6-rc.d/init-complete/type | 1 + .../etc/s6-overlay/s6-rc.d/init-complete/up | 1 + .../dependencies.d/init-search-index | 0 .../dependencies.d/init-system-checks | 0 .../dependencies.d/init-tesseract-langs | 0 .../dependencies.d/init-wait-for-db | 0 .../dependencies.d/init-wait-for-redis | 0 .../s6-overlay/s6-rc.d/init-custom-init/run | 44 +++++++ .../s6-overlay/s6-rc.d/init-custom-init/type | 1 + .../s6-overlay/s6-rc.d/init-custom-init/up | 1 + .../init-env-file/dependencies.d/init-start | 0 .../etc/s6-overlay/s6-rc.d/init-env-file/run | 30 +++++ .../etc/s6-overlay/s6-rc.d/init-env-file/type | 1 + .../etc/s6-overlay/s6-rc.d/init-env-file/up | 1 + .../dependencies.d/init-modify-user | 0 .../etc/s6-overlay/s6-rc.d/init-folders/run | 33 +++++ .../etc/s6-overlay/s6-rc.d/init-folders/type | 1 + .../etc/s6-overlay/s6-rc.d/init-folders/up | 1 + .../dependencies.d/init-folders | 0 .../dependencies.d/init-wait-for-db | 0 .../s6-overlay/s6-rc.d/init-migrations/run | 20 +++ .../s6-overlay/s6-rc.d/init-migrations/type | 1 + .../etc/s6-overlay/s6-rc.d/init-migrations/up | 1 + .../dependencies.d/init-env-file | 0 .../s6-overlay/s6-rc.d/init-modify-user/run | 22 ++++ .../s6-overlay/s6-rc.d/init-modify-user/type | 1 + .../s6-overlay/s6-rc.d/init-modify-user/up | 1 + .../dependencies.d/init-migrations | 0 .../s6-overlay/s6-rc.d/init-search-index/run | 28 ++++ .../s6-overlay/s6-rc.d/init-search-index/type | 1 + .../s6-overlay/s6-rc.d/init-search-index/up | 1 + .../s6-rc.d/init-start/dependencies.d/base | 0 .../etc/s6-overlay/s6-rc.d/init-start/run | 19 +++ .../etc/s6-overlay/s6-rc.d/init-start/type | 1 + .../etc/s6-overlay/s6-rc.d/init-start/up | 1 + .../dependencies.d/init-migrations | 0 .../etc/s6-overlay/s6-rc.d/init-superuser/run | 20 +++ .../s6-overlay/s6-rc.d/init-superuser/type | 1 + .../etc/s6-overlay/s6-rc.d/init-superuser/up | 1 + .../dependencies.d/init-superuser | 0 .../dependencies.d/init-tesseract-langs | 0 .../s6-overlay/s6-rc.d/init-system-checks/run | 15 +++ .../s6-rc.d/init-system-checks/type | 1 + .../s6-overlay/s6-rc.d/init-system-checks/up | 1 + .../dependencies.d/init-env-file | 0 .../s6-rc.d/init-tesseract-langs/run | 65 ++++++++++ .../s6-rc.d/init-tesseract-langs/type | 1 + .../s6-rc.d/init-tesseract-langs/up | 1 + .../dependencies.d/init-env-file | 0 .../s6-overlay/s6-rc.d/init-wait-for-db/run | 70 ++++++++++ .../s6-overlay/s6-rc.d/init-wait-for-db/type | 1 + .../s6-overlay/s6-rc.d/init-wait-for-db/up | 1 + .../dependencies.d/init-env-file | 0 .../s6-rc.d/init-wait-for-redis/run | 14 ++ .../s6-rc.d/init-wait-for-redis/type | 1 + .../s6-overlay/s6-rc.d/init-wait-for-redis/up | 1 + .../svc-consumer/dependencies.d/init-complete | 0 .../etc/s6-overlay/s6-rc.d/svc-consumer/run | 10 ++ .../etc/s6-overlay/s6-rc.d/svc-consumer/type | 1 + .../svc-flower/dependencies.d/init-complete | 0 .../svc-flower/dependencies.d/svc-scheduler | 0 .../svc-flower/dependencies.d/svc-worker | 0 .../etc/s6-overlay/s6-rc.d/svc-flower/run | 24 ++++ .../etc/s6-overlay/s6-rc.d/svc-flower/type | 1 + .../dependencies.d/init-complete | 0 .../svc-scheduler/dependencies.d/svc-worker | 0 .../etc/s6-overlay/s6-rc.d/svc-scheduler/run | 10 ++ .../etc/s6-overlay/s6-rc.d/svc-scheduler/type | 1 + .../dependencies.d/init-complete | 0 .../svc-webserver/dependencies.d/svc-consumer | 0 .../dependencies.d/svc-scheduler | 0 .../svc-webserver/dependencies.d/svc-worker | 0 .../etc/s6-overlay/s6-rc.d/svc-webserver/run | 10 ++ .../etc/s6-overlay/s6-rc.d/svc-webserver/type | 1 + .../svc-worker/dependencies.d/init-complete | 0 .../etc/s6-overlay/s6-rc.d/svc-worker/run | 10 ++ .../etc/s6-overlay/s6-rc.d/svc-worker/type | 1 + .../s6-rc.d/user/contents.d/init-complete | 0 .../s6-rc.d/user/contents.d/svc-consumer | 0 .../s6-rc.d/user/contents.d/svc-flower | 0 .../s6-rc.d/user/contents.d/svc-scheduler | 0 .../s6-rc.d/user/contents.d/svc-webserver | 0 .../s6-rc.d/user/contents.d/svc-worker | 0 .../rootfs/usr/local/bin/convert_mariadb_uuid | 14 ++ docker/rootfs/usr/local/bin/decrypt_documents | 14 ++ docker/rootfs/usr/local/bin/document_archiver | 14 ++ .../usr/local/bin/document_create_classifier | 14 ++ docker/rootfs/usr/local/bin/document_exporter | 14 ++ .../rootfs/usr/local/bin/document_fuzzy_match | 14 ++ docker/rootfs/usr/local/bin/document_importer | 14 ++ docker/rootfs/usr/local/bin/document_index | 14 ++ docker/rootfs/usr/local/bin/document_renamer | 14 ++ docker/rootfs/usr/local/bin/document_retagger | 14 ++ .../usr/local/bin/document_sanity_checker | 14 ++ .../rootfs/usr/local/bin/document_thumbnails | 14 ++ docker/rootfs/usr/local/bin/mail_fetcher | 14 ++ docker/rootfs/usr/local/bin/manage_superuser | 14 ++ docker/rootfs/usr/local/bin/prune_audit_logs | 14 ++ docker/rootfs/usr/local/bin/wait-for-redis.py | 64 ++++++++++ docker/supervisord.conf | 65 ---------- docker/wait-for-redis.py | 44 ------- docs/configuration.md | 6 +- 127 files changed, 843 insertions(+), 367 deletions(-) delete mode 100755 docker/docker-prepare.sh delete mode 100644 docker/env-from-file.sh delete mode 100644 docker/flower-conditional.sh create mode 100644 docker/init-flow.drawio.png delete mode 100755 docker/paperless_cmd.sh rename docker/{imagemagick-policy.xml => rootfs/etc/ImageMagick-6/paperless-policy.xml} (100%) create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-custom-init create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-env-file create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-folders create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-migrations create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-modify-user create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-search-index create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-start create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-superuser create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-system-checks create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-tesseract-langs create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-wait-for-db create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-wait-for-redis create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/up create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/dependencies.d/init-search-index create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/dependencies.d/init-system-checks create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/dependencies.d/init-tesseract-langs create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/dependencies.d/init-wait-for-db create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/dependencies.d/init-wait-for-redis create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/up create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-env-file/dependencies.d/init-start create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/init-env-file/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-env-file/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-env-file/up create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-folders/dependencies.d/init-modify-user create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/init-folders/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-folders/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-folders/up create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/dependencies.d/init-folders create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/dependencies.d/init-wait-for-db create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/up create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-modify-user/dependencies.d/init-env-file create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/init-modify-user/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-modify-user/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-modify-user/up create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-search-index/dependencies.d/init-migrations create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/init-search-index/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-search-index/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-search-index/up create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-start/dependencies.d/base create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/init-start/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-start/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-start/up create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-superuser/dependencies.d/init-migrations create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/init-superuser/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-superuser/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-superuser/up create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/dependencies.d/init-superuser create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/dependencies.d/init-tesseract-langs create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/up create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-tesseract-langs/dependencies.d/init-env-file create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/init-tesseract-langs/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-tesseract-langs/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-tesseract-langs/up create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-db/dependencies.d/init-env-file create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-db/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-db/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-db/up create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-redis/dependencies.d/init-env-file create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-redis/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-redis/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-redis/up create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-consumer/dependencies.d/init-complete create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-consumer/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-consumer/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-flower/dependencies.d/init-complete create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-flower/dependencies.d/svc-scheduler create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-flower/dependencies.d/svc-worker create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-flower/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-flower/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-scheduler/dependencies.d/init-complete create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-scheduler/dependencies.d/svc-worker create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-scheduler/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-scheduler/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/dependencies.d/init-complete create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/dependencies.d/svc-consumer create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/dependencies.d/svc-scheduler create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/dependencies.d/svc-worker create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-worker/dependencies.d/init-complete create mode 100755 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-worker/run create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/svc-worker/type create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/init-complete create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/svc-consumer create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/svc-flower create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/svc-scheduler create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/svc-webserver create mode 100644 docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/svc-worker create mode 100755 docker/rootfs/usr/local/bin/convert_mariadb_uuid create mode 100755 docker/rootfs/usr/local/bin/decrypt_documents create mode 100755 docker/rootfs/usr/local/bin/document_archiver create mode 100755 docker/rootfs/usr/local/bin/document_create_classifier create mode 100755 docker/rootfs/usr/local/bin/document_exporter create mode 100755 docker/rootfs/usr/local/bin/document_fuzzy_match create mode 100755 docker/rootfs/usr/local/bin/document_importer create mode 100755 docker/rootfs/usr/local/bin/document_index create mode 100755 docker/rootfs/usr/local/bin/document_renamer create mode 100755 docker/rootfs/usr/local/bin/document_retagger create mode 100755 docker/rootfs/usr/local/bin/document_sanity_checker create mode 100755 docker/rootfs/usr/local/bin/document_thumbnails create mode 100755 docker/rootfs/usr/local/bin/mail_fetcher create mode 100755 docker/rootfs/usr/local/bin/manage_superuser create mode 100755 docker/rootfs/usr/local/bin/prune_audit_logs create mode 100755 docker/rootfs/usr/local/bin/wait-for-redis.py delete mode 100644 docker/supervisord.conf delete mode 100755 docker/wait-for-redis.py diff --git a/.dockerignore b/.dockerignore index c33ee6452..8c39dd615 100644 --- a/.dockerignore +++ b/.dockerignore @@ -26,3 +26,5 @@ ./dist ./scripts ./resources +# Other stuff +**/*.drawio.png diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d00a8cb43..5e090a227 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,7 +131,7 @@ jobs: - name: Configure ImageMagick run: | - sudo cp docker/imagemagick-policy.xml /etc/ImageMagick-6/policy.xml + sudo cp docker/rootfs/etc/ImageMagick-6/paperless-policy.xml /etc/ImageMagick-6/policy.xml - name: Install Python dependencies run: | diff --git a/.ruff.toml b/.ruff.toml index a29b471c5..ae1bed609 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -76,8 +76,12 @@ ignore = ["DJ001", "SIM105", "RUF012"] "src/paperless_tesseract/tests/test_parser.py" = ["RUF001", "PTH"] # TODO PTH Enable & remove "src/paperless_tika/tests/test_live_tika.py" = ["PTH"] # TODO Enable & remove "src/paperless_tika/tests/test_tika_parser.py" = ["PTH"] # TODO Enable & remove +# Testing "*/tests/*.py" = ["E501", "SIM117"] +# Migrations "*/migrations/*.py" = ["E501", "SIM", "T201"] +# Docker specific +"docker/rootfs/usr/local/bin/wait-for-redis.py" = ["INP001", "T201"] [lint.isort] force-single-line = true diff --git a/Dockerfile b/Dockerfile index 3eab1dc2f..6a575be74 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,11 +43,66 @@ RUN set -eux \ && echo "Generating requirement.txt" \ && pipenv requirements > requirements.txt +# Stage: s6-overlay-base +# Purpose: Installs s6-overlay and rootfs +# Comments: +# - Don't leave anything extra in here either +FROM docker.io/python:3.12-slim-bookworm AS s6-overlay-base + +WORKDIR /usr/src/s6 + +# https://github.com/just-containers/s6-overlay#customizing-s6-overlay-behaviour +ENV \ + S6_BEHAVIOUR_IF_STAGE2_FAILS=2 \ + S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \ + S6_VERBOSITY=1 \ + PATH=/command:$PATH + +# Buildx provided, must be defined to use though +ARG TARGETARCH +ARG TARGETVARIANT +# Lock this version +ARG S6_OVERLAY_VERSION=3.2.0.2 + +ARG S6_BUILD_TIME_PKGS="curl \ + xz-utils" + +RUN set -eux \ + && echo "Installing build time packages" \ + && apt-get update \ + && apt-get install --yes --quiet --no-install-recommends ${S6_BUILD_TIME_PKGS} \ + && echo "Determining arch" \ + && S6_ARCH="" \ + && if [ "${TARGETARCH}${TARGETVARIANT}" = "amd64" ]; then S6_ARCH="x86_64"; \ + elif [ "${TARGETARCH}${TARGETVARIANT}" = "arm64" ]; then S6_ARCH="aarch64"; fi\ + && if [ -z "${S6_ARCH}" ]; then { echo "Error: Not able to determine arch"; exit 1; }; fi \ + && echo "Installing s6-overlay for ${S6_ARCH}" \ + && curl --fail --silent --no-progress-meter --show-error --location --remote-name-all --parallel --parallel-max 4 \ + "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz" \ + "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz.sha256" \ + "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz" \ + "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz.sha256" \ + && echo "Validating s6-archive checksums" \ + && sha256sum --check ./*.sha256 \ + && echo "Unpacking archives" \ + && tar --directory / -Jxpf s6-overlay-noarch.tar.xz \ + && tar --directory / -Jxpf s6-overlay-${S6_ARCH}.tar.xz \ + && echo "Removing downloaded archives" \ + && rm ./*.tar.xz \ + && rm ./*.sha256 \ + && echo "Cleaning up image" \ + && apt-get --yes purge ${S6_BUILD_TIME_PKGS} \ + && apt-get --yes autoremove --purge \ + && rm -rf /var/lib/apt/lists/* + +# Copy our service defs and filesystem +COPY ./docker/rootfs / + # Stage: main-app # Purpose: The final image # Comments: # - Don't leave anything extra in here -FROM docker.io/python:3.12-slim-bookworm AS main-app +FROM s6-overlay-base AS main-app LABEL org.opencontainers.image.authors="paperless-ngx team " LABEL org.opencontainers.image.documentation="https://docs.paperless-ngx.com/" @@ -143,64 +198,23 @@ RUN set -eux \ && dpkg --install ./ghostscript_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \ && echo "Installing jbig2enc" \ && dpkg --install ./jbig2enc_${JBIG2ENC_VERSION}-1_${TARGETARCH}.deb \ + && echo "Configuring imagemagick" \ + && cp /etc/ImageMagick-6/paperless-policy.xml /etc/ImageMagick-6/policy.xml \ && echo "Cleaning up image layer" \ && rm --force --verbose *.deb \ - && rm --recursive --force --verbose /var/lib/apt/lists/* \ - && echo "Installing supervisor" \ - && python3 -m pip install --default-timeout=1000 --upgrade --no-cache-dir supervisor==4.2.5 + && rm --recursive --force --verbose /var/lib/apt/lists/* # Copy gunicorn config # Changes very infrequently WORKDIR /usr/src/paperless/ -COPY gunicorn.conf.py . - -# setup docker-specific things -# These change sometimes, but rarely -WORKDIR /usr/src/paperless/src/docker/ - -COPY [ \ - "docker/imagemagick-policy.xml", \ - "docker/supervisord.conf", \ - "docker/docker-entrypoint.sh", \ - "docker/docker-prepare.sh", \ - "docker/paperless_cmd.sh", \ - "docker/wait-for-redis.py", \ - "docker/env-from-file.sh", \ - "docker/management_script.sh", \ - "docker/flower-conditional.sh", \ - "docker/install_management_commands.sh", \ - "/usr/src/paperless/src/docker/" \ -] - -RUN set -eux \ - && echo "Configuring ImageMagick" \ - && mv imagemagick-policy.xml /etc/ImageMagick-6/policy.xml \ - && echo "Configuring supervisord" \ - && mkdir /var/log/supervisord /var/run/supervisord \ - && mv supervisord.conf /etc/supervisord.conf \ - && echo "Setting up Docker scripts" \ - && mv docker-entrypoint.sh /sbin/docker-entrypoint.sh \ - && chmod 755 /sbin/docker-entrypoint.sh \ - && mv docker-prepare.sh /sbin/docker-prepare.sh \ - && chmod 755 /sbin/docker-prepare.sh \ - && mv wait-for-redis.py /sbin/wait-for-redis.py \ - && chmod 755 /sbin/wait-for-redis.py \ - && mv env-from-file.sh /sbin/env-from-file.sh \ - && chmod 755 /sbin/env-from-file.sh \ - && mv paperless_cmd.sh /usr/local/bin/paperless_cmd.sh \ - && chmod 755 /usr/local/bin/paperless_cmd.sh \ - && mv flower-conditional.sh /usr/local/bin/flower-conditional.sh \ - && chmod 755 /usr/local/bin/flower-conditional.sh \ - && echo "Installing management commands" \ - && chmod +x install_management_commands.sh \ - && ./install_management_commands.sh +COPY --chown=1000:1000 gunicorn.conf.py /usr/src/paperless/gunicorn.conf.py WORKDIR /usr/src/paperless/src/ # Python dependencies # Change pretty frequently -COPY --from=pipenv-base /usr/src/pipenv/requirements.txt ./ +COPY --chown=1000:1000 --from=pipenv-base /usr/src/pipenv/requirements.txt ./ # Packages needed only for building a few quick Python # dependencies @@ -222,7 +236,7 @@ RUN --mount=type=cache,target=/root/.cache/pip/,id=pip-cache \ && echo "Installing build system packages" \ && apt-get update \ && apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \ - && python3 -m pip install --no-cache-dir --upgrade wheel \ + && python3 -m pip install --upgrade wheel \ && echo "Installing Python requirements" \ && curl --fail --silent --no-progress-meter --show-error --location --remote-name-all --parallel --parallel-max 4 \ https://github.com/paperless-ngx/builder/releases/download/psycopg-${PSYCOPG_VERSION}/psycopg_c-${PSYCOPG_VERSION}-cp312-cp312-linux_x86_64.whl \ @@ -267,18 +281,16 @@ RUN set -eux \ && echo "Adjusting all permissions" \ && chown --from root:root --changes --recursive paperless:paperless /usr/src/paperless \ && echo "Collecting static files" \ - && gosu paperless python3 manage.py collectstatic --clear --no-input --link \ - && gosu paperless python3 manage.py compilemessages + && s6-setuidgid paperless python3 manage.py collectstatic --clear --no-input --link \ + && s6-setuidgid paperless python3 manage.py compilemessages VOLUME ["/usr/src/paperless/data", \ "/usr/src/paperless/media", \ "/usr/src/paperless/consume", \ "/usr/src/paperless/export"] -ENTRYPOINT ["/sbin/docker-entrypoint.sh"] +ENTRYPOINT ["/init"] EXPOSE 8000 -CMD ["/usr/local/bin/paperless_cmd.sh"] - HEALTHCHECK --interval=30s --timeout=10s --retries=5 CMD [ "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000" ] diff --git a/docker/docker-prepare.sh b/docker/docker-prepare.sh deleted file mode 100755 index e3d924535..000000000 --- a/docker/docker-prepare.sh +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env bash - -set -e - -wait_for_postgres() { - local attempt_num=1 - local -r max_attempts=5 - - echo "Waiting for PostgreSQL to start..." - - local -r host="${PAPERLESS_DBHOST:-localhost}" - local -r port="${PAPERLESS_DBPORT:-5432}" - - # Disable warning, host and port can't have spaces - # shellcheck disable=SC2086 - while [ ! "$(pg_isready --host ${host} --port ${port})" ]; do - - if [ $attempt_num -eq $max_attempts ]; then - echo "Unable to connect to database." - exit 1 - else - echo "Attempt $attempt_num failed! Trying again in 5 seconds..." - fi - - attempt_num=$(("$attempt_num" + 1)) - sleep 5 - done - echo "Connected to PostgreSQL" -} - -wait_for_mariadb() { - echo "Waiting for MariaDB to start..." - - local -r host="${PAPERLESS_DBHOST:=localhost}" - local -r port="${PAPERLESS_DBPORT:=3306}" - - local attempt_num=1 - local -r max_attempts=5 - - # Disable warning, host and port can't have spaces - # shellcheck disable=SC2086 - while ! true > /dev/tcp/$host/$port; do - - if [ $attempt_num -eq $max_attempts ]; then - echo "Unable to connect to database." - exit 1 - else - echo "Attempt $attempt_num failed! Trying again in 5 seconds..." - - fi - - attempt_num=$(("$attempt_num" + 1)) - sleep 5 - done - echo "Connected to MariaDB" -} - -wait_for_redis() { - # We use a Python script to send the Redis ping - # instead of installing redis-tools just for 1 thing - if ! python3 /sbin/wait-for-redis.py; then - exit 1 - fi -} - -migrations() { - ( - # flock is in place to prevent multiple containers from doing migrations - # simultaneously. This also ensures that the db is ready when the command - # of the current container starts. - flock 200 - echo "Apply database migrations..." - python3 manage.py migrate --skip-checks --no-input - ) 200>"${DATA_DIR}/migration_lock" -} - -django_checks() { - # Explicitly run the Django system checks - echo "Running Django checks" - python3 manage.py check -} - -search_index() { - - local -r index_version=9 - local -r index_version_file=${DATA_DIR}/.index_version - - if [[ (! -f "${index_version_file}") || $(<"${index_version_file}") != "$index_version" ]]; then - echo "Search index out of date. Updating..." - python3 manage.py document_index reindex --no-progress-bar - echo ${index_version} | tee "${index_version_file}" >/dev/null - fi -} - -superuser() { - if [[ -n "${PAPERLESS_ADMIN_USER}" ]]; then - python3 manage.py manage_superuser - fi -} - -do_work() { - if [[ "${PAPERLESS_DBENGINE}" == "mariadb" ]]; then - wait_for_mariadb - elif [[ -n "${PAPERLESS_DBHOST}" ]]; then - wait_for_postgres - fi - - wait_for_redis - - migrations - - django_checks - - search_index - - superuser - -} - -do_work diff --git a/docker/env-from-file.sh b/docker/env-from-file.sh deleted file mode 100644 index 41f51d065..000000000 --- a/docker/env-from-file.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash - -# Scans the environment variables for those with the suffix _FILE -# When located, checks the file exists, and exports the contents -# of the file as the same name, minus the suffix -# This allows the use of Docker secrets or mounted files -# to fill in any of the settings configurable via environment -# variables - -set -eu - -for line in $(printenv) -do - # Extract the name of the environment variable - env_name=${line%%=*} - # Check if it starts with "PAPERLESS_" and ends in "_FILE" - if [[ ${env_name} == PAPERLESS_*_FILE ]]; then - # This should have been named different.. - if [[ ${env_name} == "PAPERLESS_OCR_SKIP_ARCHIVE_FILE" || ${env_name} == "PAPERLESS_MODEL_FILE" ]]; then - continue - fi - # Extract the value of the environment - env_value=${line#*=} - - # Check the file exists - if [[ -f ${env_value} ]]; then - - # Trim off the _FILE suffix - non_file_env_name=${env_name%"_FILE"} - echo "Setting ${non_file_env_name} from file" - - # Reads the value from th file - val="$(< "${!env_name}")" - - # Sets the normal name to the read file contents - export "${non_file_env_name}"="${val}" - - else - echo "File ${env_value} referenced by ${env_name} doesn't exist" - fi - fi -done diff --git a/docker/flower-conditional.sh b/docker/flower-conditional.sh deleted file mode 100644 index f8719e0fd..000000000 --- a/docker/flower-conditional.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -echo "Checking if we should start flower..." - -if [[ -n "${PAPERLESS_ENABLE_FLOWER}" ]]; then - # Small delay to allow celery to be up first - echo "Starting flower in 5s" - sleep 5 - celery --app paperless flower --conf=/usr/src/paperless/src/paperless/flowerconfig.py -else - echo "Not starting flower" -fi diff --git a/docker/init-flow.drawio.png b/docker/init-flow.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..2e19ac16a093b733d56196bc4eb44c28cd5e6581 GIT binary patch literal 30635 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 n9 /usr/local/bin/$command - chmod +x /usr/local/bin/$command + sed "s/management_command/$command/g" management_script.sh >"$PWD/rootfs/usr/local/bin/$command" + chmod +x "$PWD/rootfs/usr/local/bin/$command" done diff --git a/docker/management_script.sh b/docker/management_script.sh index 996435745..1fa31c372 100755 --- a/docker/management_script.sh +++ b/docker/management_script.sh @@ -1,17 +1,13 @@ -#!/usr/bin/env bash +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash set -e -cd /usr/src/paperless/src/ -# This ensures environment is setup -# shellcheck disable=SC1091 -source /sbin/env-from-file.sh +cd "${PAPERLESS_SRC_DIR}" -if [[ $(id -u) == 0 ]] ; -then - gosu paperless python3 manage.py management_command "$@" -elif [[ $(id -un) == "paperless" ]] ; -then +if [[ $(id -u) == 0 ]]; then + s6-setuidgid paperless python3 manage.py management_command "$@" +elif [[ $(id -un) == "paperless" ]]; then python3 manage.py management_command "$@" else echo "Unknown user." diff --git a/docker/paperless_cmd.sh b/docker/paperless_cmd.sh deleted file mode 100755 index afedb1599..000000000 --- a/docker/paperless_cmd.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -SUPERVISORD_WORKING_DIR="${PAPERLESS_SUPERVISORD_WORKING_DIR:-$PWD}" -rootless_args=() -if [ "$(id -u)" == "$(id -u paperless)" ]; then - rootless_args=( - --user - paperless - --logfile - "${SUPERVISORD_WORKING_DIR}/supervisord.log" - --pidfile - "${SUPERVISORD_WORKING_DIR}/supervisord.pid" - ) -fi - -exec /usr/local/bin/supervisord -c /etc/supervisord.conf "${rootless_args[@]}" diff --git a/docker/imagemagick-policy.xml b/docker/rootfs/etc/ImageMagick-6/paperless-policy.xml similarity index 100% rename from docker/imagemagick-policy.xml rename to docker/rootfs/etc/ImageMagick-6/paperless-policy.xml diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-custom-init b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-custom-init new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-env-file b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-env-file new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-folders b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-folders new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-migrations b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-migrations new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-modify-user b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-modify-user new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-search-index b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-search-index new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-start b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-start new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-superuser b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-superuser new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-system-checks b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-system-checks new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-tesseract-langs b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-tesseract-langs new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-wait-for-db b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-wait-for-db new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-wait-for-redis b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/dependencies.d/init-wait-for-redis new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/run new file mode 100755 index 000000000..83c83df9d --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/run @@ -0,0 +1,8 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash +declare -r log_prefix="[init-complete]" +declare -r end_time=$(date +%s) +declare -r start_time=${PAPERLESS_START_TIME_S} + +echo "${log_prefix} paperless-ngx docker container init completed in $(($end_time-$start_time)) seconds" +echo "${log_prefix} Starting services" diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/type new file mode 100644 index 000000000..bdd22a185 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/type @@ -0,0 +1 @@ +oneshot diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/up b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/up new file mode 100644 index 000000000..9fb31e420 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-complete/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-complete/run diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/dependencies.d/init-search-index b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/dependencies.d/init-search-index new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/dependencies.d/init-system-checks b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/dependencies.d/init-system-checks new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/dependencies.d/init-tesseract-langs b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/dependencies.d/init-tesseract-langs new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/dependencies.d/init-wait-for-db b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/dependencies.d/init-wait-for-db new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/dependencies.d/init-wait-for-redis b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/dependencies.d/init-wait-for-redis new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/run new file mode 100755 index 000000000..50cc4c241 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/run @@ -0,0 +1,44 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +declare -r log_prefix="[custom-init]" + +# Mostly borrowed from the LinuxServer.io base image +# https://github.com/linuxserver/docker-baseimage-ubuntu/tree/bionic/root/etc/cont-init.d +declare -r custom_script_dir="/custom-cont-init.d" + +# Tamper checking. +# Don't run files which are owned by anyone except root +# Don't run files which are writeable by others +if [ -d "${custom_script_dir}" ]; then + if [ -n "$(/usr/bin/find "${custom_script_dir}" -maxdepth 1 ! -user root)" ]; then + echo "${log_prefix} **** Potential tampering with custom scripts detected ****" + echo "${log_prefix} **** The folder '${custom_script_dir}' must be owned by root ****" + exit 0 + fi + if [ -n "$(/usr/bin/find "${custom_script_dir}" -maxdepth 1 -perm -o+w)" ]; then + echo "${log_prefix} **** The folder '${custom_script_dir}' or some of contents have write permissions for others, which is a security risk. ****" + echo "${log_prefix} **** Please review the permissions and their contents to make sure they are owned by root, and can only be modified by root. ****" + exit 0 + fi + + # Make sure custom init directory has files in it + if [ -n "$(/bin/ls --almost-all "${custom_script_dir}" 2>/dev/null)" ]; then + echo "${log_prefix} files found in ${custom_script_dir} executing" + # Loop over files in the directory + for SCRIPT in "${custom_script_dir}"/*; do + NAME="$(basename "${SCRIPT}")" + if [ -f "${SCRIPT}" ]; then + echo "${log_prefix} ${NAME}: executing..." + /command/with-contenv /bin/bash "${SCRIPT}" + echo "${log_prefix} ${NAME}: exited $?" + elif [ ! -f "${SCRIPT}" ]; then + echo "${log_prefix} ${NAME}: is not a file" + fi + done + else + echo "${log_prefix} no custom files found exiting..." + fi +else + echo "${log_prefix} ${custom_script_dir} doesn't exist, nothing to do" +fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/type new file mode 100644 index 000000000..bdd22a185 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/type @@ -0,0 +1 @@ +oneshot diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/up b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/up new file mode 100644 index 000000000..69a261a39 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-custom-init/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-custom-init/run diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-env-file/dependencies.d/init-start b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-env-file/dependencies.d/init-start new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-env-file/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-env-file/run new file mode 100755 index 000000000..08b7635f8 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-env-file/run @@ -0,0 +1,30 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +declare -r log_prefix="[env-init]" + +echo "${log_prefix} Checking for environment from files" + +if find /run/s6/container_environment/*"_FILE" -maxdepth 1 > /dev/null 2>&1; then + for FILENAME in /run/s6/container_environment/*; do + if [[ "${FILENAME##*/}" == PAPERLESS_*_FILE ]]; then + # This should have been named different.. + if [[ ${FILENAME} == "PAPERLESS_OCR_SKIP_ARCHIVE_FILE" || ${FILENAME} == "PAPERLESS_MODEL_FILE" ]]; then + continue + fi + SECRETFILE=$(cat "${FILENAME}") + # Check the file exists + if [[ -f ${SECRETFILE} ]]; then + # Trim off trailing _FILE + FILESTRIP=${FILENAME//_FILE/} + # Set environment variable + cat "${SECRETFILE}" > "${FILESTRIP}" + echo "${log_prefix} ${FILESTRIP##*/} set from ${FILENAME##*/}" + else + echo "${log_prefix} cannot find secret in ${FILENAME##*/}" + fi + fi + done +else + echo "${log_prefix} No *_FILE environment found" +fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-env-file/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-env-file/type new file mode 100644 index 000000000..bdd22a185 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-env-file/type @@ -0,0 +1 @@ +oneshot diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-env-file/up b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-env-file/up new file mode 100644 index 000000000..0bb4c22fc --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-env-file/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-env-file/run diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-folders/dependencies.d/init-modify-user b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-folders/dependencies.d/init-modify-user new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-folders/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-folders/run new file mode 100755 index 000000000..5f731ceae --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-folders/run @@ -0,0 +1,33 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +declare -r log_prefix="[init-folders]" + +declare -r export_dir="/usr/src/paperless/export" +declare -r data_dir="${PAPERLESS_DATA_DIR:-/usr/src/paperless/data}" +declare -r media_root_dir="${PAPERLESS_MEDIA_ROOT:-/usr/src/paperless/media}" +declare -r consume_dir="${PAPERLESS_CONSUMPTION_DIR:-/usr/src/paperless/consume}" +declare -r tmp_dir="${PAPERLESS_SCRATCH_DIR:=/tmp/paperless}" + +echo "${log_prefix} Checking for folder existence" + +for dir in \ + "${export_dir}" \ + "${data_dir}" "${data_dir}/index" \ + "${media_root_dir}" "${media_root_dir}/documents" "${media_root_dir}/documents/originals" "${media_root_dir}/documents/thumbnails" \ + "${consume_dir}" \ + "${tmp_dir}"; do + if [[ ! -d "${dir}" ]]; then + mkdir --parents --verbose "${dir}" + fi +done + +echo "${log_prefix} Adjusting file and folder permissions" +for dir in \ + "${export_dir}" \ + "${data_dir}" \ + "${media_root_dir}" \ + "${consume_dir}" \ + "${tmp_dir}"; do + find "${dir}" -not \( -user paperless -and -group paperless \) -exec chown --changes paperless:paperless {} + +done diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-folders/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-folders/type new file mode 100644 index 000000000..bdd22a185 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-folders/type @@ -0,0 +1 @@ +oneshot diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-folders/up b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-folders/up new file mode 100644 index 000000000..0fb7dc7de --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-folders/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-folders/run diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/dependencies.d/init-folders b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/dependencies.d/init-folders new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/dependencies.d/init-wait-for-db b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/dependencies.d/init-wait-for-db new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/run new file mode 100755 index 000000000..db0dc26d3 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/run @@ -0,0 +1,20 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash +declare -r log_prefix="[init-migrations]" +declare -r data_dir="${PAPERLESS_DATA_DIR:-/usr/src/paperless/data}" + +( + # flock is in place to prevent multiple containers from doing migrations + # simultaneously. This also ensures that the db is ready when the command + # of the current container starts. + flock 200 + echo "${log_prefix} Apply database migrations..." + cd "${PAPERLESS_SRC_DIR}" + + if [[ -n "${USER_IS_NON_ROOT}" ]]; then + exec python3 manage.py migrate --skip-checks --no-input + else + exec s6-setuidgid paperless python3 manage.py migrate --skip-checks --no-input + fi + +) 200>"${data_dir}/migration_lock" diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/type new file mode 100644 index 000000000..bdd22a185 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/type @@ -0,0 +1 @@ +oneshot diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/up b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/up new file mode 100644 index 000000000..7c4cbcf6f --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-migrations/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-migrations/run diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-modify-user/dependencies.d/init-env-file b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-modify-user/dependencies.d/init-env-file new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-modify-user/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-modify-user/run new file mode 100755 index 000000000..aa617355d --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-modify-user/run @@ -0,0 +1,22 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash +declare -r log_prefix="[init-user]" + +declare -r usermap_original_uid=$(id -u paperless) +declare -r usermap_original_gid=$(id -g paperless) +declare -r usermap_new_uid=${USERMAP_UID:-$usermap_original_uid} +declare -r usermap_new_gid=${USERMAP_GID:-${usermap_original_gid:-$usermap_new_uid}} + +if [[ ${usermap_new_uid} != "${usermap_original_uid}" ]]; then + echo "${log_prefix} Mapping UID for paperless to $usermap_new_uid" + usermod --non-unique --uid "${usermap_new_uid}" paperless +else + echo "${log_prefix} No UID changes for paperless" +fi + +if [[ ${usermap_new_gid} != "${usermap_original_gid}" ]]; then + echo "${log_prefix} Mapping GID for paperless to $usermap_new_gid" + groupmod --non-unique --gid "${usermap_new_gid}" paperless +else + echo "${log_prefix} No GID changes for paperless" +fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-modify-user/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-modify-user/type new file mode 100644 index 000000000..bdd22a185 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-modify-user/type @@ -0,0 +1 @@ +oneshot diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-modify-user/up b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-modify-user/up new file mode 100644 index 000000000..4c22ce857 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-modify-user/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-modify-user/run diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-search-index/dependencies.d/init-migrations b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-search-index/dependencies.d/init-migrations new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-search-index/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-search-index/run new file mode 100755 index 000000000..2208faf67 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-search-index/run @@ -0,0 +1,28 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +declare -r log_prefix="[init-index]" + +declare -r index_version=9 +declare -r data_dir="${PAPERLESS_DATA_DIR:-/usr/src/paperless/data}" +declare -r index_version_file="${data_dir}/.index_version" + +update_index () { + echo "${log_prefix} Search index out of date. Updating..." + cd "${PAPERLESS_SRC_DIR}" + if [[ -n "${USER_IS_NON_ROOT}" ]]; then + python3 manage.py document_index reindex --no-progress-bar + echo ${index_version} | tee "${index_version_file}" > /dev/null + else + s6-setuidgid paperless python3 manage.py document_index reindex --no-progress-bar + echo ${index_version} | s6-setuidgid paperless tee "${index_version_file}" > /dev/null + fi +} + +if [[ (! -f "${index_version_file}") ]]; then + echo "${log_prefix} No index version file found" + update_index +elif [[ $(<"${index_version_file}") != "$index_version" ]]; then + echo "${log_prefix} index version updated" + update_index +fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-search-index/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-search-index/type new file mode 100644 index 000000000..bdd22a185 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-search-index/type @@ -0,0 +1 @@ +oneshot diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-search-index/up b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-search-index/up new file mode 100644 index 000000000..372763add --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-search-index/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-search-index/run diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-start/dependencies.d/base b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-start/dependencies.d/base new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-start/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-start/run new file mode 100755 index 000000000..b6a26fae7 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-start/run @@ -0,0 +1,19 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +declare -r log_prefix="[init-start]" + +echo "${log_prefix} paperless-ngx docker container starting..." + +# Set some directories into environment for other steps to access via environment +# Sort of like variables for later +printf "/usr/src/paperless/src" > /var/run/s6/container_environment/PAPERLESS_SRC_DIR +echo $(date +%s) > /var/run/s6/container_environment/PAPERLESS_START_TIME_S + +# Check if we're starting as a non-root user +if [ $(id -u) == $(id -u paperless) ]; then + printf "true" > /var/run/s6/container_environment/USER_IS_NON_ROOT + echo "${log_prefix} paperless-ngx docker container running under a user" +else + echo "${log_prefix} paperless-ngx docker container starting init as root" +fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-start/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-start/type new file mode 100644 index 000000000..bdd22a185 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-start/type @@ -0,0 +1 @@ +oneshot diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-start/up b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-start/up new file mode 100644 index 000000000..3a6a26d81 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-start/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-start/run diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-superuser/dependencies.d/init-migrations b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-superuser/dependencies.d/init-migrations new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-superuser/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-superuser/run new file mode 100755 index 000000000..625185181 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-superuser/run @@ -0,0 +1,20 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +declare -r log_prefix="[init-superuser]" + +if [[ -n "${PAPERLESS_ADMIN_USER}" ]]; then + echo "${log_prefix} Creating superuser..." + cd "${PAPERLESS_SRC_DIR}" + + if [[ -n "${USER_IS_NON_ROOT}" ]]; then + python3 manage.py manage_superuser + else + s6-setuidgid paperless python3 manage.py manage_superuser + fi + + echo "${log_prefix} Superuser creation done" + +else + echo "${log_prefix} Not creating superuser" +fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-superuser/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-superuser/type new file mode 100644 index 000000000..bdd22a185 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-superuser/type @@ -0,0 +1 @@ +oneshot diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-superuser/up b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-superuser/up new file mode 100644 index 000000000..055f10a50 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-superuser/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-superuser/run diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/dependencies.d/init-superuser b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/dependencies.d/init-superuser new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/dependencies.d/init-tesseract-langs b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/dependencies.d/init-tesseract-langs new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/run new file mode 100755 index 000000000..d024765cc --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/run @@ -0,0 +1,15 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +declare -r log_prefix="[init-checks]" + +# Explicitly run the Django system checks +echo "${log_prefix} Running Django checks" + +cd "${PAPERLESS_SRC_DIR}" + +if [[ -n "${USER_IS_NON_ROOT}" ]]; then + python3 manage.py check +else + s6-setuidgid paperless python3 manage.py check +fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/type new file mode 100644 index 000000000..bdd22a185 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/type @@ -0,0 +1 @@ +oneshot diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/up b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/up new file mode 100644 index 000000000..7403acc2f --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-system-checks/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-system-checks/run diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-tesseract-langs/dependencies.d/init-env-file b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-tesseract-langs/dependencies.d/init-env-file new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-tesseract-langs/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-tesseract-langs/run new file mode 100755 index 000000000..7ab464583 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-tesseract-langs/run @@ -0,0 +1,65 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +declare -r log_prefix="[init-tesseract-langs]" + +install_languages() { + echo "Installing languages..." + + read -ra langs <<<"$1" + + # Check that it is not empty + if [ ${#langs[@]} -eq 0 ]; then + return + fi + + # Build list of packages to install + to_install=() + for lang in "${langs[@]}"; do + pkg="tesseract-ocr-$lang" + + if dpkg --status "$pkg" &>/dev/null; then + echo "${log_prefix} Package $pkg already installed!" + continue + else + to_install+=("$pkg") + fi + done + + # Use apt only when we install packages + if [ ${#to_install[@]} -gt 0 ]; then + + # Warn the user if they're not root, but try anyway + if [[ -n "${USER_IS_NON_ROOT}" ]]; then + echo "${log_prefix} ERROR: Unable to install language ${pkg} as non-root, startup may fail" + fi + + apt-get --quiet update &>/dev/null + + for pkg in "${to_install[@]}"; do + if ! apt-cache --quiet show "$pkg" &>/dev/null; then + echo "${log_prefix} Skipped $pkg: Package not found! :(" + continue + fi + echo "${log_prefix} Installing package $pkg..." + if ! apt-get --quiet --assume-yes install "$pkg" &>/dev/null; then + echo "${log_prefix} Could not install $pkg" + exit 1 + else + echo "${log_prefix} Installed $pkg" + fi + done + + fi +} + +echo "${log_prefix} Checking if additional teseract languages needed" + +# Install additional languages if specified +if [[ -n "$PAPERLESS_OCR_LANGUAGES" ]]; then + + install_languages "$PAPERLESS_OCR_LANGUAGES" + echo "${log_prefix} Additional packages installed" +else + echo "${log_prefix} No additional installs requested" +fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-tesseract-langs/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-tesseract-langs/type new file mode 100644 index 000000000..bdd22a185 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-tesseract-langs/type @@ -0,0 +1 @@ +oneshot diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-tesseract-langs/up b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-tesseract-langs/up new file mode 100644 index 000000000..16ef2f4d1 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-tesseract-langs/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-tesseract-langs/run diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-db/dependencies.d/init-env-file b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-db/dependencies.d/init-env-file new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-db/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-db/run new file mode 100755 index 000000000..ede8a654a --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-db/run @@ -0,0 +1,70 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +declare -r log_prefix="[init-db-wait]" + +wait_for_postgres() { + local attempt_num=1 + local -r max_attempts=5 + + echo "${log_prefix} Waiting for PostgreSQL to start..." + + local -r host="${PAPERLESS_DBHOST:-localhost}" + local -r port="${PAPERLESS_DBPORT:-5432}" + local -r user="${PAPERLESS_DBUSER:-paperless}" + + # Disable warning, host and port can't have spaces + # shellcheck disable=SC2086 + while [ ! "$(pg_isready -h ${host} -p ${port} --username ${user})" ]; do + + if [ $attempt_num -eq $max_attempts ]; then + echo "${log_prefix} Unable to connect to database." + exit 1 + else + echo "${log_prefix} Attempt $attempt_num failed! Trying again in 5 seconds..." + fi + + attempt_num=$(("$attempt_num" + 1)) + sleep 5 + done + # Extra in case this is a first start + sleep 5 + echo "Connected to PostgreSQL" +} + +wait_for_mariadb() { + echo "${log_prefix} Waiting for MariaDB to start..." + + local -r host="${PAPERLESS_DBHOST:=localhost}" + local -r port="${PAPERLESS_DBPORT:=3306}" + + local attempt_num=1 + local -r max_attempts=5 + + # Disable warning, host and port can't have spaces + # shellcheck disable=SC2086 + while ! true > /dev/tcp/$host/$port; do + + if [ $attempt_num -eq $max_attempts ]; then + echo "${log_prefix} Unable to connect to database." + exit 1 + else + echo "${log_prefix} Attempt $attempt_num failed! Trying again in 5 seconds..." + + fi + + attempt_num=$(("$attempt_num" + 1)) + sleep 5 + done + echo "Connected to MariaDB" +} + +if [[ "${PAPERLESS_DBENGINE}" == "mariadb" ]]; then + echo "${log_prefix} Waiting for MariaDB to report ready" + wait_for_mariadb +elif [[ -n "${PAPERLESS_DBHOST}" ]]; then + echo "${log_prefix} Waiting for postgresql to report ready" + wait_for_postgres +fi + + echo "${log_prefix} Database is ready" diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-db/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-db/type new file mode 100644 index 000000000..bdd22a185 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-db/type @@ -0,0 +1 @@ +oneshot diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-db/up b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-db/up new file mode 100644 index 000000000..6cbecf5f4 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-db/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-wait-for-db/run diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-redis/dependencies.d/init-env-file b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-redis/dependencies.d/init-env-file new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-redis/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-redis/run new file mode 100755 index 000000000..407536aa2 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-redis/run @@ -0,0 +1,14 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +declare -r log_prefix="[init-redis-wait]" + +echo "${log_prefix} Waiting for Redis to report ready" + +# We use a Python script to send the Redis ping +# instead of installing redis-tools just for 1 thing +if ! python3 /usr/local/bin/wait-for-redis.py; then + exit 1 +else + echo "${log_prefix} Redis ready" +fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-redis/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-redis/type new file mode 100644 index 000000000..bdd22a185 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-redis/type @@ -0,0 +1 @@ +oneshot diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-redis/up b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-redis/up new file mode 100644 index 000000000..8a5fa1601 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/init-wait-for-redis/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-wait-for-redis/run diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-consumer/dependencies.d/init-complete b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-consumer/dependencies.d/init-complete new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-consumer/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-consumer/run new file mode 100755 index 000000000..3e1c0472b --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-consumer/run @@ -0,0 +1,10 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +cd ${PAPERLESS_SRC_DIR} + +if [[ -n "${USER_IS_NON_ROOT}" ]]; then + exec python3 manage.py document_consumer +else + exec s6-setuidgid paperless python3 manage.py document_consumer +fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-consumer/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-consumer/type new file mode 100644 index 000000000..5883cff0c --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-consumer/type @@ -0,0 +1 @@ +longrun diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-flower/dependencies.d/init-complete b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-flower/dependencies.d/init-complete new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-flower/dependencies.d/svc-scheduler b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-flower/dependencies.d/svc-scheduler new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-flower/dependencies.d/svc-worker b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-flower/dependencies.d/svc-worker new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-flower/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-flower/run new file mode 100755 index 000000000..a3e4b6a3e --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-flower/run @@ -0,0 +1,24 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +declare -r log_prefix="[svc-flower]" + +echo "${log_prefix} Checking if we should start flower..." + +if [[ -n "${PAPERLESS_ENABLE_FLOWER}" ]]; then + # Small delay to allow celery to be up first + echo "${log_prefix} Starting flower in 5s" + sleep 5 + cd ${PAPERLESS_SRC_DIR} + + if [[ -n "${USER_IS_NON_ROOT}" ]]; then + exec /usr/local/bin/celery --app paperless flower --conf=${PAPERLESS_SRC_DIR}/paperless/flowerconfig.py + else + exec s6-setuidgid paperless /usr/local/bin/celery --app paperless flower --conf=${PAPERLESS_SRC_DIR}/paperless/flowerconfig.py + fi + +else + echo "${log_prefix} Not starting flower" + # https://skarnet.org/software/s6/s6-svc.html + s6-svc -Od . +fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-flower/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-flower/type new file mode 100644 index 000000000..5883cff0c --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-flower/type @@ -0,0 +1 @@ +longrun diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-scheduler/dependencies.d/init-complete b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-scheduler/dependencies.d/init-complete new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-scheduler/dependencies.d/svc-worker b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-scheduler/dependencies.d/svc-worker new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-scheduler/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-scheduler/run new file mode 100755 index 000000000..396a4d2e0 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-scheduler/run @@ -0,0 +1,10 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +cd ${PAPERLESS_SRC_DIR} + +if [[ -n "${USER_IS_NON_ROOT}" ]]; then + exec /usr/local/bin/celery --app paperless beat --loglevel INFO +else + exec s6-setuidgid paperless /usr/local/bin/celery --app paperless beat --loglevel INFO +fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-scheduler/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-scheduler/type new file mode 100644 index 000000000..5883cff0c --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-scheduler/type @@ -0,0 +1 @@ +longrun diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/dependencies.d/init-complete b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/dependencies.d/init-complete new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/dependencies.d/svc-consumer b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/dependencies.d/svc-consumer new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/dependencies.d/svc-scheduler b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/dependencies.d/svc-scheduler new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/dependencies.d/svc-worker b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/dependencies.d/svc-worker new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/run new file mode 100755 index 000000000..423b17531 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/run @@ -0,0 +1,10 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +cd ${PAPERLESS_SRC_DIR} + +if [[ -n "${USER_IS_NON_ROOT}" ]]; then + exec /usr/local/bin/gunicorn -c /usr/src/paperless/gunicorn.conf.py paperless.asgi:application +else + exec s6-setuidgid paperless /usr/local/bin/gunicorn -c /usr/src/paperless/gunicorn.conf.py paperless.asgi:application +fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/type new file mode 100644 index 000000000..5883cff0c --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-webserver/type @@ -0,0 +1 @@ +longrun diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-worker/dependencies.d/init-complete b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-worker/dependencies.d/init-complete new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-worker/run b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-worker/run new file mode 100755 index 000000000..0bf833c56 --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-worker/run @@ -0,0 +1,10 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +cd ${PAPERLESS_SRC_DIR} + +if [[ -n "${USER_IS_NON_ROOT}" ]]; then + exec /usr/local/bin/celery --app paperless worker --loglevel INFO --without-mingle --without-gossip +else + exec s6-setuidgid paperless /usr/local/bin/celery --app paperless worker --loglevel INFO --without-mingle --without-gossip +fi diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-worker/type b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-worker/type new file mode 100644 index 000000000..5883cff0c --- /dev/null +++ b/docker/rootfs/etc/s6-overlay/s6-rc.d/svc-worker/type @@ -0,0 +1 @@ +longrun diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/init-complete b/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/init-complete new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/svc-consumer b/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/svc-consumer new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/svc-flower b/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/svc-flower new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/svc-scheduler b/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/svc-scheduler new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/svc-webserver b/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/svc-webserver new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/svc-worker b/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/svc-worker new file mode 100644 index 000000000..e69de29bb diff --git a/docker/rootfs/usr/local/bin/convert_mariadb_uuid b/docker/rootfs/usr/local/bin/convert_mariadb_uuid new file mode 100755 index 000000000..806a98f3b --- /dev/null +++ b/docker/rootfs/usr/local/bin/convert_mariadb_uuid @@ -0,0 +1,14 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +set -e + +cd "${PAPERLESS_SRC_DIR}" + +if [[ $(id -u) == 0 ]]; then + s6-setuidgid paperless python3 manage.py convert_mariadb_uuid "$@" +elif [[ $(id -un) == "paperless" ]]; then + python3 manage.py convert_mariadb_uuid "$@" +else + echo "Unknown user." +fi diff --git a/docker/rootfs/usr/local/bin/decrypt_documents b/docker/rootfs/usr/local/bin/decrypt_documents new file mode 100755 index 000000000..4da1549ee --- /dev/null +++ b/docker/rootfs/usr/local/bin/decrypt_documents @@ -0,0 +1,14 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +set -e + +cd "${PAPERLESS_SRC_DIR}" + +if [[ $(id -u) == 0 ]]; then + s6-setuidgid paperless python3 manage.py decrypt_documents "$@" +elif [[ $(id -un) == "paperless" ]]; then + python3 manage.py decrypt_documents "$@" +else + echo "Unknown user." +fi diff --git a/docker/rootfs/usr/local/bin/document_archiver b/docker/rootfs/usr/local/bin/document_archiver new file mode 100755 index 000000000..383acfcc6 --- /dev/null +++ b/docker/rootfs/usr/local/bin/document_archiver @@ -0,0 +1,14 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +set -e + +cd "${PAPERLESS_SRC_DIR}" + +if [[ $(id -u) == 0 ]]; then + s6-setuidgid paperless python3 manage.py document_archiver "$@" +elif [[ $(id -un) == "paperless" ]]; then + python3 manage.py document_archiver "$@" +else + echo "Unknown user." +fi diff --git a/docker/rootfs/usr/local/bin/document_create_classifier b/docker/rootfs/usr/local/bin/document_create_classifier new file mode 100755 index 000000000..72dc33d6f --- /dev/null +++ b/docker/rootfs/usr/local/bin/document_create_classifier @@ -0,0 +1,14 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +set -e + +cd "${PAPERLESS_SRC_DIR}" + +if [[ $(id -u) == 0 ]]; then + s6-setuidgid paperless python3 manage.py document_create_classifier "$@" +elif [[ $(id -un) == "paperless" ]]; then + python3 manage.py document_create_classifier "$@" +else + echo "Unknown user." +fi diff --git a/docker/rootfs/usr/local/bin/document_exporter b/docker/rootfs/usr/local/bin/document_exporter new file mode 100755 index 000000000..7f48215d7 --- /dev/null +++ b/docker/rootfs/usr/local/bin/document_exporter @@ -0,0 +1,14 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +set -e + +cd "${PAPERLESS_SRC_DIR}" + +if [[ $(id -u) == 0 ]]; then + s6-setuidgid paperless python3 manage.py document_exporter "$@" +elif [[ $(id -un) == "paperless" ]]; then + python3 manage.py document_exporter "$@" +else + echo "Unknown user." +fi diff --git a/docker/rootfs/usr/local/bin/document_fuzzy_match b/docker/rootfs/usr/local/bin/document_fuzzy_match new file mode 100755 index 000000000..5b9548557 --- /dev/null +++ b/docker/rootfs/usr/local/bin/document_fuzzy_match @@ -0,0 +1,14 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +set -e + +cd "${PAPERLESS_SRC_DIR}" + +if [[ $(id -u) == 0 ]]; then + s6-setuidgid paperless python3 manage.py document_fuzzy_match "$@" +elif [[ $(id -un) == "paperless" ]]; then + python3 manage.py document_fuzzy_match "$@" +else + echo "Unknown user." +fi diff --git a/docker/rootfs/usr/local/bin/document_importer b/docker/rootfs/usr/local/bin/document_importer new file mode 100755 index 000000000..2286e89f7 --- /dev/null +++ b/docker/rootfs/usr/local/bin/document_importer @@ -0,0 +1,14 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +set -e + +cd "${PAPERLESS_SRC_DIR}" + +if [[ $(id -u) == 0 ]]; then + s6-setuidgid paperless python3 manage.py document_importer "$@" +elif [[ $(id -un) == "paperless" ]]; then + python3 manage.py document_importer "$@" +else + echo "Unknown user." +fi diff --git a/docker/rootfs/usr/local/bin/document_index b/docker/rootfs/usr/local/bin/document_index new file mode 100755 index 000000000..2d518b5c5 --- /dev/null +++ b/docker/rootfs/usr/local/bin/document_index @@ -0,0 +1,14 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +set -e + +cd "${PAPERLESS_SRC_DIR}" + +if [[ $(id -u) == 0 ]]; then + s6-setuidgid paperless python3 manage.py document_index "$@" +elif [[ $(id -un) == "paperless" ]]; then + python3 manage.py document_index "$@" +else + echo "Unknown user." +fi diff --git a/docker/rootfs/usr/local/bin/document_renamer b/docker/rootfs/usr/local/bin/document_renamer new file mode 100755 index 000000000..326317a73 --- /dev/null +++ b/docker/rootfs/usr/local/bin/document_renamer @@ -0,0 +1,14 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +set -e + +cd "${PAPERLESS_SRC_DIR}" + +if [[ $(id -u) == 0 ]]; then + s6-setuidgid paperless python3 manage.py document_renamer "$@" +elif [[ $(id -un) == "paperless" ]]; then + python3 manage.py document_renamer "$@" +else + echo "Unknown user." +fi diff --git a/docker/rootfs/usr/local/bin/document_retagger b/docker/rootfs/usr/local/bin/document_retagger new file mode 100755 index 000000000..3bab3e790 --- /dev/null +++ b/docker/rootfs/usr/local/bin/document_retagger @@ -0,0 +1,14 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +set -e + +cd "${PAPERLESS_SRC_DIR}" + +if [[ $(id -u) == 0 ]]; then + s6-setuidgid paperless python3 manage.py document_retagger "$@" +elif [[ $(id -un) == "paperless" ]]; then + python3 manage.py document_retagger "$@" +else + echo "Unknown user." +fi diff --git a/docker/rootfs/usr/local/bin/document_sanity_checker b/docker/rootfs/usr/local/bin/document_sanity_checker new file mode 100755 index 000000000..5c0c29ef2 --- /dev/null +++ b/docker/rootfs/usr/local/bin/document_sanity_checker @@ -0,0 +1,14 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +set -e + +cd "${PAPERLESS_SRC_DIR}" + +if [[ $(id -u) == 0 ]]; then + s6-setuidgid paperless python3 manage.py document_sanity_checker "$@" +elif [[ $(id -un) == "paperless" ]]; then + python3 manage.py document_sanity_checker "$@" +else + echo "Unknown user." +fi diff --git a/docker/rootfs/usr/local/bin/document_thumbnails b/docker/rootfs/usr/local/bin/document_thumbnails new file mode 100755 index 000000000..c1000c31a --- /dev/null +++ b/docker/rootfs/usr/local/bin/document_thumbnails @@ -0,0 +1,14 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +set -e + +cd "${PAPERLESS_SRC_DIR}" + +if [[ $(id -u) == 0 ]]; then + s6-setuidgid paperless python3 manage.py document_thumbnails "$@" +elif [[ $(id -un) == "paperless" ]]; then + python3 manage.py document_thumbnails "$@" +else + echo "Unknown user." +fi diff --git a/docker/rootfs/usr/local/bin/mail_fetcher b/docker/rootfs/usr/local/bin/mail_fetcher new file mode 100755 index 000000000..2ae1d1dfb --- /dev/null +++ b/docker/rootfs/usr/local/bin/mail_fetcher @@ -0,0 +1,14 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +set -e + +cd "${PAPERLESS_SRC_DIR}" + +if [[ $(id -u) == 0 ]]; then + s6-setuidgid paperless python3 manage.py mail_fetcher "$@" +elif [[ $(id -un) == "paperless" ]]; then + python3 manage.py mail_fetcher "$@" +else + echo "Unknown user." +fi diff --git a/docker/rootfs/usr/local/bin/manage_superuser b/docker/rootfs/usr/local/bin/manage_superuser new file mode 100755 index 000000000..9f7f37ecf --- /dev/null +++ b/docker/rootfs/usr/local/bin/manage_superuser @@ -0,0 +1,14 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +set -e + +cd "${PAPERLESS_SRC_DIR}" + +if [[ $(id -u) == 0 ]]; then + s6-setuidgid paperless python3 manage.py manage_superuser "$@" +elif [[ $(id -un) == "paperless" ]]; then + python3 manage.py manage_superuser "$@" +else + echo "Unknown user." +fi diff --git a/docker/rootfs/usr/local/bin/prune_audit_logs b/docker/rootfs/usr/local/bin/prune_audit_logs new file mode 100755 index 000000000..b9142e98e --- /dev/null +++ b/docker/rootfs/usr/local/bin/prune_audit_logs @@ -0,0 +1,14 @@ +#!/command/with-contenv /usr/bin/bash +# shellcheck shell=bash + +set -e + +cd "${PAPERLESS_SRC_DIR}" + +if [[ $(id -u) == 0 ]]; then + s6-setuidgid paperless python3 manage.py prune_audit_logs "$@" +elif [[ $(id -un) == "paperless" ]]; then + python3 manage.py prune_audit_logs "$@" +else + echo "Unknown user." +fi diff --git a/docker/rootfs/usr/local/bin/wait-for-redis.py b/docker/rootfs/usr/local/bin/wait-for-redis.py new file mode 100755 index 000000000..9ae4a35a5 --- /dev/null +++ b/docker/rootfs/usr/local/bin/wait-for-redis.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +""" +Simple script which attempts to ping the Redis broker as set in the environment for +a certain number of times, waiting a little bit in between + +""" + +import os +import sys +import time + +import click +from redis import Redis + + +@click.command(context_settings={"show_default": True}) +@click.option( + "--retry-count", + default=5, + type=int, + help="Count of times to retry the Redis connection", +) +@click.option( + "--retry-sleep", + default=5, + type=int, + help="Seconds to wait between Redis connection retries", +) +@click.argument( + "redis_url", + type=str, + envvar="PAPERLESS_REDIS", + default="redis://localhost:6379", +) +def wait(redis_url: str, retry_count: int, retry_sleep: int) -> None: + click.echo("Waiting for Redis...") + + attempt = 0 + with Redis.from_url(url=redis_url) as client: + while attempt < retry_count: + try: + client.ping() + break + except Exception as e: + click.echo( + f"Redis ping #{attempt} failed.\n" + f"Error: {e!s}.\n" + f"Waiting {retry_sleep}s", + ) + time.sleep(retry_sleep) + attempt += 1 + + if attempt >= retry_count: + click.echo( + "Failed to connect to redis using environment variable PAPERLESS_REDIS.", + ) + sys.exit(os.EX_UNAVAILABLE) + else: + click.echo("Connected to Redis broker.") + sys.exit(os.EX_OK) + + +if __name__ == "__main__": + wait() diff --git a/docker/supervisord.conf b/docker/supervisord.conf deleted file mode 100644 index 009760771..000000000 --- a/docker/supervisord.conf +++ /dev/null @@ -1,65 +0,0 @@ -[supervisord] -nodaemon=true ; start in foreground if true; default false -logfile=/var/log/supervisord/supervisord.log ; main log file; default $CWD/supervisord.log -pidfile=/var/run/supervisord/supervisord.pid ; supervisord pidfile; default supervisord.pid -logfile_maxbytes=50MB ; max main logfile bytes b4 rotation; default 50MB -logfile_backups=10 ; # of main logfile backups; 0 means none, default 10 -loglevel=info ; log level; default info; others: debug,warn,trace -user=root - -[program:gunicorn] -command=gunicorn -c /usr/src/paperless/gunicorn.conf.py paperless.asgi:application -user=paperless -priority = 1 -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 -environment = HOME="/usr/src/paperless",USER="paperless" - -[program:consumer] -command=python3 manage.py document_consumer -user=paperless -stopsignal=INT -priority = 20 -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 -environment = HOME="/usr/src/paperless",USER="paperless" - -[program:celery] - -command = celery --app paperless worker --loglevel INFO --without-mingle --without-gossip -user=paperless -stopasgroup = true -stopwaitsecs = 60 -priority = 5 -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 -environment = HOME="/usr/src/paperless",USER="paperless" - -[program:celery-beat] - -command = celery --app paperless beat --loglevel INFO -user=paperless -stopasgroup = true -priority = 10 -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 -environment = HOME="/usr/src/paperless",USER="paperless" - -[program:celery-flower] -command = /usr/local/bin/flower-conditional.sh -user = paperless -startsecs = 0 -priority = 40 -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 -environment = HOME="/usr/src/paperless",USER="paperless" diff --git a/docker/wait-for-redis.py b/docker/wait-for-redis.py deleted file mode 100755 index c3e4f1d59..000000000 --- a/docker/wait-for-redis.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple script which attempts to ping the Redis broker as set in the environment for -a certain number of times, waiting a little bit in between - -""" - -import os -import sys -import time -from typing import Final - -from redis import Redis - -if __name__ == "__main__": - MAX_RETRY_COUNT: Final[int] = 5 - RETRY_SLEEP_SECONDS: Final[int] = 5 - - REDIS_URL: Final[str] = os.getenv("PAPERLESS_REDIS", "redis://localhost:6379") - - print("Waiting for Redis...", flush=True) - - attempt = 0 - with Redis.from_url(url=REDIS_URL) as client: - while attempt < MAX_RETRY_COUNT: - try: - client.ping() - break - except Exception as e: - print( - f"Redis ping #{attempt} failed.\n" - f"Error: {e!s}.\n" - f"Waiting {RETRY_SLEEP_SECONDS}s", - flush=True, - ) - time.sleep(RETRY_SLEEP_SECONDS) - attempt += 1 - - if attempt >= MAX_RETRY_COUNT: - print("Failed to connect to redis using environment variable PAPERLESS_REDIS.") - sys.exit(os.EX_UNAVAILABLE) - else: - print("Connected to Redis broker.") - sys.exit(os.EX_OK) diff --git a/docs/configuration.md b/docs/configuration.md index b81c10b0d..3724c792d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1596,9 +1596,11 @@ started by the container. #### [`PAPERLESS_SUPERVISORD_WORKING_DIR=`](#PAPERLESS_SUPERVISORD_WORKING_DIR) {#PAPERLESS_SUPERVISORD_WORKING_DIR} -: If this environment variable is defined, the `supervisord.log` and `supervisord.pid` file will be created under the specified path in `PAPERLESS_SUPERVISORD_WORKING_DIR`. Setting `PAPERLESS_SUPERVISORD_WORKING_DIR=/tmp` and `PYTHONPYCACHEPREFIX=/tmp/pycache` would allow paperless to work on a read-only filesystem. +!!! warning - Please take note that the `PAPERLESS_DATA_DIR` and `PAPERLESS_MEDIA_ROOT` paths still have to be writable, just like the `PAPERLESS_SUPERVISORD_WORKING_DIR`. The can be archived by using bind or volume mounts. Only works in the container is run as user *paperless* + This option is deprecated and has no effect. For read only file system support, + see [S6_READ_ONLY_ROOT](https://github.com/just-containers/s6-overlay#customizing-s6-overlay-behaviour) + from s6-overlay. ## Frontend Settings