mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-06-22 15:27:28 -05:00
Compare commits
No commits in common. "dev" and "v1.10.0-beta.rc2" have entirely different histories.
dev
...
v1.10.0-be
9
.build-config.json
Normal file
9
.build-config.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"qpdf": {
|
||||
"version": "11.1.1"
|
||||
},
|
||||
"jbig2enc": {
|
||||
"version": "0.29",
|
||||
"git_tag": "0.29"
|
||||
}
|
||||
}
|
36
.codecov.yml
36
.codecov.yml
@ -1,36 +0,0 @@
|
||||
codecov:
|
||||
require_ci_to_pass: true
|
||||
# https://docs.codecov.com/docs/components
|
||||
component_management:
|
||||
individual_components:
|
||||
- component_id: backend
|
||||
paths:
|
||||
- src/**
|
||||
- component_id: frontend
|
||||
paths:
|
||||
- src-ui/**
|
||||
# https://docs.codecov.com/docs/pull-request-comments
|
||||
# codecov will only comment if coverage changes
|
||||
comment:
|
||||
layout: "header, diff, components, flags, files"
|
||||
require_changes: true
|
||||
# https://docs.codecov.com/docs/javascript-bundle-analysis
|
||||
require_bundle_changes: true
|
||||
bundle_change_threshold: "50Kb"
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
# https://docs.codecov.com/docs/commit-status#threshold
|
||||
threshold: 1%
|
||||
patch:
|
||||
default:
|
||||
# For the changed lines only, target 100% covered, but
|
||||
# allow as low as 75%
|
||||
target: 100%
|
||||
threshold: 25%
|
||||
# https://docs.codecov.com/docs/javascript-bundle-analysis
|
||||
bundle_analysis:
|
||||
# Fail if the bundle size increases by more than 1MB
|
||||
warning_threshold: "1MB"
|
||||
status: true
|
@ -1,3 +0,0 @@
|
||||
[codespell]
|
||||
write-changes = True
|
||||
ignore-words-list = criterias,afterall,valeu,ureue,equest,ure,assertIn
|
@ -1,175 +0,0 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM --platform=$BUILDPLATFORM docker.io/node:20-bookworm-slim as main-app
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Buildx provided, must be defined to use though
|
||||
ARG TARGETARCH
|
||||
|
||||
# Can be workflow provided, defaults set for manual building
|
||||
ARG JBIG2ENC_VERSION=0.29
|
||||
ARG QPDF_VERSION=11.9.0
|
||||
ARG GS_VERSION=10.03.1
|
||||
|
||||
# Set Python environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
# Ignore warning from Whitenoise
|
||||
PYTHONWARNINGS="ignore:::django.http.response:517" \
|
||||
PNGX_CONTAINERIZED=1
|
||||
|
||||
#
|
||||
# Begin installation and configuration
|
||||
# Order the steps below from least often changed to most
|
||||
#
|
||||
|
||||
# Packages need for running
|
||||
ARG RUNTIME_PACKAGES="\
|
||||
# General utils
|
||||
curl \
|
||||
# Docker specific
|
||||
gosu \
|
||||
# Timezones support
|
||||
tzdata \
|
||||
# fonts for text file thumbnail generation
|
||||
fonts-liberation \
|
||||
gettext \
|
||||
ghostscript \
|
||||
gnupg \
|
||||
icc-profiles-free \
|
||||
imagemagick \
|
||||
# PostgreSQL
|
||||
postgresql-client \
|
||||
# MySQL / MariaDB
|
||||
mariadb-client \
|
||||
# OCRmyPDF dependencies
|
||||
tesseract-ocr \
|
||||
tesseract-ocr-eng \
|
||||
tesseract-ocr-deu \
|
||||
tesseract-ocr-fra \
|
||||
tesseract-ocr-ita \
|
||||
tesseract-ocr-spa \
|
||||
unpaper \
|
||||
pngquant \
|
||||
jbig2dec \
|
||||
# lxml
|
||||
libxml2 \
|
||||
libxslt1.1 \
|
||||
# itself
|
||||
qpdf \
|
||||
# Mime type detection
|
||||
file \
|
||||
libmagic1 \
|
||||
media-types \
|
||||
zlib1g \
|
||||
# Barcode splitter
|
||||
libzbar0 \
|
||||
poppler-utils \
|
||||
htop \
|
||||
sudo"
|
||||
|
||||
# Install basic runtime packages.
|
||||
# These change very infrequently
|
||||
RUN set -eux \
|
||||
echo "Installing system packages" \
|
||||
&& apt-get update \
|
||||
&& apt-get install --yes --quiet --no-install-recommends ${RUNTIME_PACKAGES}
|
||||
|
||||
ARG PYTHON_PACKAGES="ca-certificates"
|
||||
|
||||
RUN set -eux \
|
||||
echo "Installing python packages" \
|
||||
&& apt-get update \
|
||||
&& apt-get install --yes --quiet ${PYTHON_PACKAGES}
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.7.8 /uv /bin/uv
|
||||
|
||||
|
||||
RUN set -eux \
|
||||
&& echo "Installing pre-built updates" \
|
||||
&& echo "Installing qpdf ${QPDF_VERSION}" \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output libqpdf29_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/qpdf-${QPDF_VERSION}/libqpdf29_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output qpdf_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/qpdf-${QPDF_VERSION}/qpdf_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& dpkg --install ./libqpdf29_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& dpkg --install ./qpdf_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& echo "Installing Ghostscript ${GS_VERSION}" \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output libgs10_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/libgs10_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output ghostscript_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/ghostscript_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output libgs10-common_${GS_VERSION}.dfsg-1_all.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/libgs10-common_${GS_VERSION}.dfsg-1_all.deb \
|
||||
&& dpkg --install ./libgs10-common_${GS_VERSION}.dfsg-1_all.deb \
|
||||
&& dpkg --install ./libgs10_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
|
||||
&& dpkg --install ./ghostscript_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
|
||||
&& echo "Installing jbig2enc" \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output jbig2enc_${JBIG2ENC_VERSION}-1_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/jbig2enc-${JBIG2ENC_VERSION}/jbig2enc_${JBIG2ENC_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& dpkg --install ./jbig2enc_${JBIG2ENC_VERSION}-1_${TARGETARCH}.deb
|
||||
|
||||
# setup docker-specific things
|
||||
# These change sometimes, but rarely
|
||||
WORKDIR /usr/src/paperless/src/docker/
|
||||
|
||||
COPY [ \
|
||||
"docker/rootfs/etc/ImageMagick-6/paperless-policy.xml", \
|
||||
"./" \
|
||||
]
|
||||
|
||||
RUN set -eux \
|
||||
&& echo "Configuring ImageMagick" \
|
||||
&& mv paperless-policy.xml /etc/ImageMagick-6/policy.xml
|
||||
|
||||
|
||||
# Packages needed only for building a few quick Python
|
||||
# dependencies
|
||||
ARG BUILD_PACKAGES="\
|
||||
build-essential \
|
||||
git \
|
||||
# https://www.psycopg.org/docs/install.html#prerequisites
|
||||
libpq-dev \
|
||||
# https://github.com/PyMySQL/mysqlclient#linux
|
||||
default-libmysqlclient-dev \
|
||||
pkg-config"
|
||||
|
||||
# hadolint ignore=DL3042
|
||||
RUN --mount=type=cache,target=/root/.cache/uv,id=pip-cache \
|
||||
set -eux \
|
||||
&& echo "Installing build system packages" \
|
||||
&& apt-get update \
|
||||
&& apt-get install --yes --quiet ${BUILD_PACKAGES}
|
||||
|
||||
RUN set -eux \
|
||||
&& npm update -g pnpm
|
||||
|
||||
# add users, setup scripts
|
||||
# Mount the compiled frontend to expected location
|
||||
RUN set -eux \
|
||||
&& echo "Setting up user/group" \
|
||||
&& groupmod --new-name paperless node \
|
||||
&& usermod --login paperless --home /usr/src/paperless node \
|
||||
&& usermod -s /bin/bash paperless \
|
||||
&& echo "paperless ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers \
|
||||
&& echo "Creating volume directories" \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/paperless-ngx/data \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/paperless-ngx/media \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/paperless-ngx/consume \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/paperless-ngx/export \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/paperless-ngx/.venv \
|
||||
&& echo "Adjusting all permissions" \
|
||||
&& chown --from root:root --changes --recursive paperless:paperless /usr/src/paperless
|
||||
|
||||
VOLUME ["/usr/src/paperless/paperless-ngx/data", \
|
||||
"/usr/src/paperless/paperless-ngx/media", \
|
||||
"/usr/src/paperless/paperless-ngx/consume", \
|
||||
"/usr/src/paperless/paperless-ngx/export", \
|
||||
"/usr/src/paperless/paperless-ngx/.venv"]
|
@ -1,94 +0,0 @@
|
||||
# Paperless-ngx Development Environment
|
||||
|
||||
## Overview
|
||||
|
||||
Welcome to the Paperless-ngx development environment! This setup uses VSCode DevContainers to provide a consistent and seamless development experience.
|
||||
|
||||
### What are DevContainers?
|
||||
|
||||
DevContainers are a feature in VSCode that allows you to develop within a Docker container. This ensures that your development environment is consistent across different machines and setups. By defining a containerized environment, you can eliminate the "works on my machine" problem.
|
||||
|
||||
### Advantages of DevContainers
|
||||
|
||||
- **Consistency**: Same environment for all developers.
|
||||
- **Isolation**: Separate development environment from your local machine.
|
||||
- **Reproducibility**: Easily recreate the environment on any machine.
|
||||
- **Pre-configured Tools**: Include all necessary tools and dependencies in the container.
|
||||
|
||||
## DevContainer Setup
|
||||
|
||||
The DevContainer configuration provides up all the necessary services for Paperless-ngx, including:
|
||||
|
||||
- Redis
|
||||
- Gotenberg
|
||||
- Tika
|
||||
|
||||
Data is stored using Docker volumes to ensure persistence across container restarts.
|
||||
|
||||
## Configuration Files
|
||||
|
||||
The setup includes debugging configurations (`launch.json`) and tasks (`tasks.json`) to help you manage and debug various parts of the project:
|
||||
|
||||
- **Backend Debugging:**
|
||||
- `manage.py runserver`
|
||||
- `manage.py document-consumer`
|
||||
- `celery`
|
||||
- **Maintenance Tasks:**
|
||||
- Create superuser
|
||||
- Run migrations
|
||||
- Recreate virtual environment (`.venv` with `uv`)
|
||||
- Compile frontend assets
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Step 1: Running the DevContainer
|
||||
|
||||
To start the DevContainer:
|
||||
|
||||
1. Open VSCode.
|
||||
2. Open the project folder.
|
||||
3. Open the command palette and choose `Dev Containers: Rebuild and Reopen in Container`.
|
||||
|
||||
VSCode will build and start the DevContainer environment.
|
||||
|
||||
### Step 2: Initial Setup
|
||||
|
||||
Once the DevContainer is up and running, run the `Project Setup: Run all Init Tasks` task to initialize the project.
|
||||
|
||||
Alternatively, the Project Setup can be done with individual tasks:
|
||||
|
||||
1. **Compile Frontend Assets**: `Maintenance: Compile frontend for production`.
|
||||
2. **Run Database Migrations**: `Maintenance: manage.py migrate`.
|
||||
3. **Create Superuser**: `Maintenance: manage.py createsuperuser`.
|
||||
|
||||
### Debugging and Running Services
|
||||
|
||||
You can start and debug backend services either as debugging sessions via `launch.json` or as tasks.
|
||||
|
||||
#### Using `launch.json`
|
||||
|
||||
1. Press `F5` or go to the **Run and Debug** view in VSCode.
|
||||
2. Select the desired configuration:
|
||||
- `Runserver`
|
||||
- `Document Consumer`
|
||||
- `Celery`
|
||||
|
||||
#### Using Tasks
|
||||
|
||||
1. Open the command palette and select `Tasks: Run Task`.
|
||||
2. Choose the desired task:
|
||||
- `Runserver`
|
||||
- `Document Consumer`
|
||||
- `Celery`
|
||||
|
||||
### Additional Maintenance Tasks
|
||||
|
||||
Additional tasks are available for common maintenance operations:
|
||||
|
||||
- **Recreate .venv**: For setting up the virtual environment using `uv`.
|
||||
- **Migrate Database**: To apply database migrations.
|
||||
- **Create Superuser**: To create an admin user for the application.
|
||||
|
||||
## Let's Get Started!
|
||||
|
||||
Follow the steps above to get your development environment up and running. Happy coding!
|
@ -1,28 +0,0 @@
|
||||
{
|
||||
"name": "Paperless Development",
|
||||
"dockerComposeFile": "docker-compose.devcontainer.sqlite-tika.yml",
|
||||
"service": "paperless-development",
|
||||
"workspaceFolder": "/usr/src/paperless/paperless-ngx",
|
||||
"postCreateCommand": "/bin/bash -c 'uv sync --group dev && uv run pre-commit install'",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"mhutchie.git-graph",
|
||||
"ms-python.python",
|
||||
"ms-vscode.js-debug-nightly",
|
||||
"eamodio.gitlens",
|
||||
"yzhang.markdown-all-in-one"
|
||||
],
|
||||
"settings": {
|
||||
"python.defaultInterpreterPath": "/usr/src/paperless/paperless-ngx/.venv/bin/python",
|
||||
"python.pythonPath": "/usr/src/paperless/paperless-ngx/.venv/bin/python",
|
||||
"python.terminal.activateEnvInCurrentTerminal": true,
|
||||
"editor.formatOnPaste": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnType": true,
|
||||
"files.trimTrailingWhitespace": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"remoteUser": "paperless"
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
# Docker Compose file for developing Paperless NGX in VSCode DevContainers.
|
||||
# This file contains everything Paperless NGX needs to run.
|
||||
# Paperless supports amd64, arm, and arm64 hardware.
|
||||
# All compose files of Paperless configure it in the following way:
|
||||
#
|
||||
# - Paperless is (re)started on system boot if it was running before shutdown.
|
||||
# - Docker volumes for storing data are managed by Docker.
|
||||
# - Folders for importing and exporting files are created in the same directory
|
||||
# as this file and mounted to the correct folders inside the container.
|
||||
# - Paperless listens on port 8000.
|
||||
#
|
||||
# SQLite is used as the database. The SQLite file is stored in the data volume.
|
||||
#
|
||||
# In addition, this Docker Compose file adds the following optional
|
||||
# configurations:
|
||||
#
|
||||
# - Apache Tika and Gotenberg servers are started with Paperless NGX and Paperless
|
||||
# is configured to use these services. These provide support for consuming
|
||||
# Office documents (Word, Excel, PowerPoint, and their LibreOffice counterparts).
|
||||
#
|
||||
# This file is intended only to be used through VSCOde devcontainers. See README.md
|
||||
# in the folder .devcontainer.
|
||||
|
||||
services:
|
||||
broker:
|
||||
image: docker.io/library/redis:7
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./redisdata:/data
|
||||
# No ports need to be exposed; the VSCode DevContainer plugin manages them.
|
||||
paperless-development:
|
||||
image: paperless-ngx
|
||||
build:
|
||||
context: ../ # Dockerfile cannot access files from parent directories if context is not set.
|
||||
dockerfile: ./.devcontainer/Dockerfile
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- broker
|
||||
- gotenberg
|
||||
- tika
|
||||
volumes:
|
||||
- ..:/usr/src/paperless/paperless-ngx:delegated
|
||||
- ../.devcontainer/vscode:/usr/src/paperless/paperless-ngx/.vscode:delegated # VSCode config files
|
||||
- virtualenv:/usr/src/paperless/paperless-ngx/.venv # Virtual environment persisted in volume
|
||||
- /usr/src/paperless/paperless-ngx/src/documents/static/frontend # Static frontend files exist only in container
|
||||
- /usr/src/paperless/paperless-ngx/src/.pytest_cache
|
||||
- /usr/src/paperless/paperless-ngx/.ruff_cache
|
||||
- /usr/src/paperless/paperless-ngx/htmlcov
|
||||
- /usr/src/paperless/paperless-ngx/.coverage
|
||||
- ./data:/usr/src/paperless/paperless-ngx/data
|
||||
- ./media:/usr/src/paperless/paperless-ngx/media
|
||||
- ./consume:/usr/src/paperless/paperless-ngx/consume
|
||||
- ~/.gitconfig:/usr/src/paperless/.gitconfig:ro
|
||||
environment:
|
||||
PAPERLESS_REDIS: redis://broker:6379
|
||||
PAPERLESS_TIKA_ENABLED: 1
|
||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
PAPERLESS_STATICDIR: ./src/documents/static
|
||||
PAPERLESS_DEBUG: true
|
||||
# Overrides default command so things don't shut down after the process ends.
|
||||
command: /bin/sh -c "chown -R paperless:paperless /usr/src/paperless/paperless-ngx/src/documents/static/frontend && chown -R paperless:paperless /usr/src/paperless/paperless-ngx/.ruff_cache && while sleep 1000; do :; done"
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:8.17
|
||||
restart: unless-stopped
|
||||
# The Gotenberg Chromium route is used to convert .eml files. We do not
|
||||
# want to allow external content like tracking pixels or even JavaScript.
|
||||
command:
|
||||
- "gotenberg"
|
||||
- "--chromium-disable-javascript=true"
|
||||
- "--chromium-allow-list=file:///tmp/.*"
|
||||
tika:
|
||||
image: docker.io/apache/tika:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
data:
|
||||
media:
|
||||
redisdata:
|
||||
virtualenv:
|
@ -1,58 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Chrome: Debug Angular Frontend",
|
||||
"description": "Debug the Angular Dev Frontend in Chrome",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:4200",
|
||||
"webRoot": "${workspaceFolder}/src-ui",
|
||||
"preLaunchTask": "Start: Frontend Angular"
|
||||
},
|
||||
{
|
||||
"name": "Debug: Backend Server (manage.py runserver)",
|
||||
"description": "Debug the Django Backend Server",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/src/manage.py",
|
||||
"args": [
|
||||
"runserver"
|
||||
],
|
||||
"django": true,
|
||||
"console": "integratedTerminal",
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}/src"
|
||||
},
|
||||
"python": "${workspaceFolder}/.venv/bin/python"
|
||||
},
|
||||
{
|
||||
"name": "Debug: Consumer Service (manage.py document_consumer)",
|
||||
"description": "Debug the Consumer Service which processes files from a directory",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/src/manage.py",
|
||||
"args": [
|
||||
"document_consumer"
|
||||
],
|
||||
"django": true,
|
||||
"console": "integratedTerminal",
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}/src"
|
||||
},
|
||||
"python": "${workspaceFolder}/.venv/bin/python"
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Debug: FullStack",
|
||||
"description": "Debug run the Angular dev frontend, Django backend, and consumer service",
|
||||
"configurations": [
|
||||
"Chrome: Debug Angular Frontend",
|
||||
"Debug: Backend Server (manage.py runserver)",
|
||||
"Debug: Consumer Service (manage.py document_consumer)"
|
||||
],
|
||||
"preLaunchTask": "Start: Celery Worker"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"python.testing.pytestArgs": [
|
||||
"src"
|
||||
],
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestEnabled": true,
|
||||
"files.watcherExclude": {
|
||||
"**/.venv/**": true,
|
||||
"**/pytest_cache/**": true
|
||||
}
|
||||
}
|
@ -1,223 +0,0 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Start: Celery Worker",
|
||||
"description": "Start the Celery Worker which processes background and consume tasks",
|
||||
"type": "shell",
|
||||
"command": "uv run celery --app paperless worker -l DEBUG",
|
||||
"isBackground": true,
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/src"
|
||||
},
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "custom",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": ".",
|
||||
"file": 1,
|
||||
"location": 2,
|
||||
"message": 3
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": "celery.*",
|
||||
"endsPattern": "ready"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Start: Frontend Angular",
|
||||
"description": "Start the Frontend Angular Dev Server",
|
||||
"type": "shell",
|
||||
"command": "pnpm start",
|
||||
"isBackground": true,
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/src-ui"
|
||||
},
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "custom",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": ".",
|
||||
"file": 1,
|
||||
"location": 2,
|
||||
"message": 3
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": ".*",
|
||||
"endsPattern": "Compiled successfully"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Start: Consumer Service (manage.py document_consumer)",
|
||||
"description": "Start the Consumer Service which processes files from a directory",
|
||||
"type": "shell",
|
||||
"command": "uv run python manage.py document_consumer",
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": true,
|
||||
"revealProblems": "onProblem"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/src"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Start: Backend Server (manage.py runserver)",
|
||||
"description": "Start the Backend Server which serves the Django API and the compiled Angular frontend",
|
||||
"type": "shell",
|
||||
"command": "uv run python manage.py runserver",
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": true,
|
||||
"revealProblems": "onProblem"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/src"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Maintenance: manage.py migrate",
|
||||
"description": "Apply database migrations",
|
||||
"type": "shell",
|
||||
"command": "uv run python manage.py migrate",
|
||||
"group": "none",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": true,
|
||||
"revealProblems": "onProblem"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/src"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Maintenance: Build Documentation",
|
||||
"description": "Build the documentation with MkDocs",
|
||||
"type": "shell",
|
||||
"command": "uv run mkdocs build --config-file mkdocs.yml && uv run mkdocs serve",
|
||||
"group": "none",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": true,
|
||||
"revealProblems": "onProblem"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Maintenance: manage.py createsuperuser",
|
||||
"description": "Create a superuser",
|
||||
"type": "shell",
|
||||
"command": "uv run python manage.py createsuperuser",
|
||||
"group": "none",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": true,
|
||||
"revealProblems": "onProblem"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/src"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Maintenance: recreate .venv",
|
||||
"description": "Recreate the python virtual environment and install python dependencies",
|
||||
"type": "shell",
|
||||
"command": "rm -rf .venv && uv venv && uv sync --dev",
|
||||
"group": "none",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": true,
|
||||
"revealProblems": "onProblem"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Maintenance: Install Frontend Dependencies",
|
||||
"description": "Install frontend (pnpm) dependencies",
|
||||
"type": "pnpm",
|
||||
"script": "install",
|
||||
"path": "src-ui",
|
||||
"group": "clean",
|
||||
"problemMatcher": [],
|
||||
"detail": "install dependencies from package"
|
||||
},
|
||||
{
|
||||
"description": "Clean install frontend dependencies and build the frontend for production",
|
||||
"label": "Maintenance: Compile frontend for production",
|
||||
"type": "shell",
|
||||
"command": "pnpm install && ./node_modules/.bin/ng build --configuration production",
|
||||
"group": "none",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": true,
|
||||
"revealProblems": "onProblem"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/src-ui"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Project Setup: Run all Init Tasks",
|
||||
"description": "Runs all init tasks to setup the project including migrate the database, create a superuser and compile the frontend for production",
|
||||
"dependsOrder": "sequence",
|
||||
"dependsOn": [
|
||||
"Maintenance: manage.py migrate",
|
||||
"Maintenance: manage.py createsuperuser",
|
||||
"Maintenance: Compile frontend for production"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Project Start: Run all Services",
|
||||
"description": "Runs all services required to start the project including the Celery Worker, the Consumer Service and the Backend Server",
|
||||
"dependsOn": [
|
||||
"Start: Celery Worker",
|
||||
"Start: Consumer Service (manage.py document_consumer)",
|
||||
"Start: Backend Server (manage.py runserver)"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,30 +1,21 @@
|
||||
# Tool caches
|
||||
**/__pycache__
|
||||
**/.ruff_cache/
|
||||
**/.mypy_cache/
|
||||
# Virtual environment & similar
|
||||
.venv/
|
||||
./src-ui/node_modules
|
||||
./src-ui/dist
|
||||
# IDE folders
|
||||
.idea/
|
||||
.vscode/
|
||||
./src-ui/.vscode
|
||||
# VCS
|
||||
/src-ui/.vscode
|
||||
/src-ui/node_modules
|
||||
/src-ui/dist
|
||||
.git
|
||||
# Test related
|
||||
**/.pytest_cache
|
||||
/export
|
||||
/consume
|
||||
/media
|
||||
/data
|
||||
/docs
|
||||
.pytest_cache
|
||||
/dist
|
||||
/scripts
|
||||
/resources
|
||||
**/tests
|
||||
**/*.spec.ts
|
||||
**/htmlcov
|
||||
# Local folders
|
||||
./export
|
||||
./consume
|
||||
./media
|
||||
./data
|
||||
./docs
|
||||
./dist
|
||||
./scripts
|
||||
./resources
|
||||
# Other stuff
|
||||
**/*.drawio.png
|
||||
/src/.pytest_cache
|
||||
.idea
|
||||
.venv/
|
||||
.vscode/
|
||||
|
@ -27,6 +27,9 @@ indent_style = space
|
||||
[*.md]
|
||||
indent_style = space
|
||||
|
||||
[Pipfile.lock]
|
||||
indent_style = space
|
||||
|
||||
# Tests don't get a line width restriction. It's still a good idea to follow
|
||||
# the 79 character rule, but in the interests of clarity, tests often need to
|
||||
# violate it.
|
||||
|
1
.env
1
.env
@ -1 +1,2 @@
|
||||
COMPOSE_PROJECT_NAME=paperless
|
||||
export PROMPT="(pipenv-projectname)$P$G"
|
||||
|
14
.github/DISCUSSION_TEMPLATE/feature-requests.yml
vendored
14
.github/DISCUSSION_TEMPLATE/feature-requests.yml
vendored
@ -1,14 +0,0 @@
|
||||
title: "[Feature Request] "
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: A clear and concise description of what you would like to see.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: other
|
||||
attributes:
|
||||
label: Other
|
||||
description: Add any other context or information about the feature request here.
|
55
.github/DISCUSSION_TEMPLATE/support.yml
vendored
55
.github/DISCUSSION_TEMPLATE/support.yml
vendored
@ -1,55 +0,0 @@
|
||||
title: "[Support] "
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: What's your question or issue?
|
||||
description: Provide a clear and concise description of what you're trying to do, and what's going wrong.
|
||||
placeholder: |
|
||||
I'm trying to...
|
||||
|
||||
[Include screenshots if helpful]
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: What have you tried?
|
||||
description: Describe any steps you've already taken to troubleshoot or solve the issue.
|
||||
placeholder: |
|
||||
- I checked the logs and saw...
|
||||
- I followed the install guide and tried...
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Paperless-ngx version
|
||||
placeholder: e.g. 1.14.0
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: host-os
|
||||
attributes:
|
||||
label: Host OS
|
||||
description: Include architecture if relevant.
|
||||
placeholder: e.g. Ubuntu 22.04 / Raspberry Pi arm64
|
||||
- type: dropdown
|
||||
id: install-method
|
||||
attributes:
|
||||
label: Installation method
|
||||
options:
|
||||
- Docker - official image
|
||||
- Docker - linuxserver.io image
|
||||
- Bare metal
|
||||
- Other (please describe above)
|
||||
- type: textarea
|
||||
id: system-status
|
||||
attributes:
|
||||
label: System status
|
||||
description: If available, copy & paste the system status output from Settings > System Status > Copy
|
||||
render: json
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant logs or output
|
||||
description: If you have logs, errors that might help, paste it here.
|
||||
render: bash
|
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@ -1 +0,0 @@
|
||||
github: [shamoon, stumpylog]
|
41
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
41
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@ -6,21 +6,14 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### ⚠️ Please remember: issues are for *bugs*
|
||||
That is, something you believe affects every single user of Paperless-ngx, not just you. If you're not sure, start with one of the other options below.
|
||||
Have a question? 👉 [Start a new discussion](https://github.com/paperless-ngx/paperless-ngx/discussions/new) or [ask in chat](https://matrix.to/#/#paperless:adnidor.de).
|
||||
|
||||
Also, note that **Paperless-ngx does not perform OCR or archive file creation itself**, those are handled by other tools. Problems with OCR or archive versions of specific files should likely be raised 'upstream', see https://github.com/ocrmypdf/OCRmyPDF/issues or https://github.com/tesseract-ocr/tesseract/issues
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
#### Have a question? 👉 [Start a new discussion](https://github.com/paperless-ngx/paperless-ngx/discussions/new) or [ask in chat](https://matrix.to/#/#paperlessngx:matrix.org).
|
||||
Before opening an issue, please double check:
|
||||
|
||||
#### Before opening an issue, please double check:
|
||||
|
||||
- [The troubleshooting documentation](https://docs.paperless-ngx.com/troubleshooting/).
|
||||
- [The installation instructions](https://docs.paperless-ngx.com/setup/#installation).
|
||||
- [The troubleshooting documentation](https://paperless-ngx.readthedocs.io/en/latest/troubleshooting.html).
|
||||
- [The installation instructions](https://paperless-ngx.readthedocs.io/en/latest/setup.html#installation).
|
||||
- [Existing issues and discussions](https://github.com/paperless-ngx/paperless-ngx/search?q=&type=issues).
|
||||
- Disable any custom container initialization scripts, if using
|
||||
- Disable any customer container initialization scripts, if using any
|
||||
|
||||
If you encounter issues while installing or configuring Paperless-ngx, please post in the ["Support" section of the discussions](https://github.com/paperless-ngx/paperless-ngx/discussions/new?category=support).
|
||||
- type: textarea
|
||||
@ -86,33 +79,19 @@ body:
|
||||
description: Note there are significant differences from the official image and linuxserver.io, please check if your issue is specific to the third-party image.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: system-status
|
||||
attributes:
|
||||
label: System status
|
||||
description: If available, copy & paste the system status output from Settings > System Status > Copy
|
||||
render: json
|
||||
- type: input
|
||||
id: browser
|
||||
attributes:
|
||||
label: Browser
|
||||
description: Which browser you are using, if relevant.
|
||||
placeholder: e.g. Chrome, Safari
|
||||
- type: textarea
|
||||
- type: input
|
||||
id: config-changes
|
||||
attributes:
|
||||
label: Configuration changes
|
||||
description: Any configuration changes you made in `docker-compose.yml`, `docker-compose.env` or `paperless.conf`.
|
||||
- type: checkboxes
|
||||
id: required-checks
|
||||
- type: input
|
||||
id: other
|
||||
attributes:
|
||||
label: Please confirm the following
|
||||
options:
|
||||
- label: I believe this issue is a bug that affects all users of Paperless-ngx, not something specific to my installation.
|
||||
required: true
|
||||
- label: This issue is not about the OCR or archive creation of a specific file(s). Otherwise, please see above regarding OCR tools.
|
||||
required: true
|
||||
- label: I have already searched for relevant existing issues and discussions before opening this report.
|
||||
required: true
|
||||
- label: I have updated the title field above with a concise description.
|
||||
required: true
|
||||
label: Other
|
||||
description: Any other relevant details.
|
||||
|
6
.github/ISSUE_TEMPLATE/config.yml
vendored
6
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -2,10 +2,10 @@ blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 🤔 Questions and Help
|
||||
url: https://github.com/paperless-ngx/paperless-ngx/discussions
|
||||
about: General questions or support for using Paperless-ngx.
|
||||
about: This issue tracker is not for support questions. Please refer to our Discussions.
|
||||
- name: 💬 Chat
|
||||
url: https://matrix.to/#/#paperlessngx:matrix.org
|
||||
url: https://matrix.to/#/#paperless:adnidor.de
|
||||
about: Want to discuss Paperless-ngx with others? Check out our chat.
|
||||
- name: 🚀 Feature Request
|
||||
url: https://github.com/paperless-ngx/paperless-ngx/discussions/new?category=feature-requests
|
||||
about: Remember to search for existing feature requests and "up-vote" those that you like.
|
||||
about: Remember to search for existing feature requests and "up-vote" any you like
|
||||
|
24
.github/PULL_REQUEST_TEMPLATE.md
vendored
24
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -8,11 +8,7 @@ Note: All PRs with code changes should be targeted to the `dev` branch, pure doc
|
||||
Please include a summary of the change and which issue is fixed (if any) and any relevant motivation / context. List any dependencies that are required for this change. If appropriate, please include an explanation of how your proposed change can be tested. Screenshots and / or videos can also be helpful if appropriate.
|
||||
-->
|
||||
|
||||
<!--
|
||||
⚠️ Important: Pull requests that implement a new feature or enhancement *should almost always target an existing feature request* with evidence of community interest and discussion. This is in order to balance the work of implementing and maintaining new features / enhancements. If that is not currently the case, please open a feature request instead of this PR to gather feedback from both users and the project maintainers.
|
||||
-->
|
||||
|
||||
Closes #(issue or discussion)
|
||||
Fixes # (issue)
|
||||
|
||||
## Type of change
|
||||
|
||||
@ -21,22 +17,16 @@ What type of change does your PR introduce to Paperless-ngx?
|
||||
NOTE: Please check only one box!
|
||||
-->
|
||||
|
||||
- [ ] Bug fix: non-breaking change which fixes an issue.
|
||||
- [ ] New feature / Enhancement: non-breaking change which adds functionality. _Please read the important note above._
|
||||
- [ ] Breaking change: fix or feature that would cause existing functionality to not work as expected.
|
||||
- [ ] Documentation only.
|
||||
- [ ] Other. Please explain:
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] Other (please explain)
|
||||
|
||||
## Checklist:
|
||||
|
||||
<!--
|
||||
NOTE: PRs that do not address the following will not be merged, please do not skip any relevant items.
|
||||
-->
|
||||
|
||||
- [ ] I have read & agree with the [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/main/CONTRIBUTING.md).
|
||||
- [ ] If applicable, I have included testing coverage for new code in this PR, for [backend](https://docs.paperless-ngx.com/development/#testing) and / or [front-end](https://docs.paperless-ngx.com/development/#testing-and-code-style) changes.
|
||||
- [ ] If applicable, I have tested my code for new features & regressions on both mobile & desktop devices, using the latest version of major browsers.
|
||||
- [ ] If applicable, I have checked that all tests pass, see [documentation](https://docs.paperless-ngx.com/development/#back-end-development).
|
||||
- [ ] I have run all `pre-commit` hooks, see [documentation](https://docs.paperless-ngx.com/development/#code-formatting-with-pre-commit-hooks).
|
||||
- [ ] If applicable, I have checked that all tests pass, see [documentation](https://paperless-ngx.readthedocs.io/en/latest/extending.html#back-end-development).
|
||||
- [ ] I have run all `pre-commit` hooks, see [documentation](https://paperless-ngx.readthedocs.io/en/latest/extending.html#code-formatting-with-pre-commit-hooks).
|
||||
- [ ] I have made corresponding changes to the documentation as needed.
|
||||
- [ ] I have checked my modifications for any breaking changes.
|
||||
|
115
.github/dependabot.yml
vendored
115
.github/dependabot.yml
vendored
@ -1,39 +1,27 @@
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
# https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates#package-ecosystem
|
||||
|
||||
version: 2
|
||||
# Required for uv support for now
|
||||
enable-beta-ecosystems: true
|
||||
updates:
|
||||
# Enable version updates for pnpm
|
||||
|
||||
# Enable version updates for npm
|
||||
- package-ecosystem: "npm"
|
||||
target-branch: "dev"
|
||||
# Look for `pnpm-lock.yaml` file in the `/src-ui` directory
|
||||
# Look for `package.json` and `lock` files in the `/src-ui` directory
|
||||
directory: "/src-ui"
|
||||
open-pull-requests-limit: 10
|
||||
# Check the npm registry for updates every month
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
labels:
|
||||
- "frontend"
|
||||
- "dependencies"
|
||||
groups:
|
||||
frontend-angular-dependencies:
|
||||
patterns:
|
||||
- "@angular*"
|
||||
- "@ng-*"
|
||||
- "ngx-*"
|
||||
- "ng2-pdf-viewer"
|
||||
frontend-jest-dependencies:
|
||||
patterns:
|
||||
- "@types/jest"
|
||||
- "jest*"
|
||||
frontend-eslint-dependencies:
|
||||
patterns:
|
||||
- "@typescript-eslint*"
|
||||
- "eslint"
|
||||
# Add reviewers
|
||||
reviewers:
|
||||
- "paperless-ngx/frontend"
|
||||
|
||||
# Enable version updates for Python
|
||||
- package-ecosystem: "uv"
|
||||
- package-ecosystem: "pip"
|
||||
target-branch: "dev"
|
||||
# Look for a `Pipfile` in the `root` directory
|
||||
directory: "/"
|
||||
# Check for updates once a week
|
||||
schedule:
|
||||
@ -41,32 +29,11 @@ updates:
|
||||
labels:
|
||||
- "backend"
|
||||
- "dependencies"
|
||||
groups:
|
||||
development:
|
||||
patterns:
|
||||
- "*pytest*"
|
||||
- "ruff"
|
||||
- "mkdocs-material"
|
||||
- "pre-commit*"
|
||||
django:
|
||||
patterns:
|
||||
- "*django*"
|
||||
- "drf-*"
|
||||
major-versions:
|
||||
update-types:
|
||||
- "major"
|
||||
small-changes:
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
exclude-patterns:
|
||||
- "*django*"
|
||||
- "drf-*"
|
||||
pre-built:
|
||||
patterns:
|
||||
- psycopg*
|
||||
- zxing-cpp
|
||||
# Enable updates for GitHub Actions
|
||||
# Add reviewers
|
||||
reviewers:
|
||||
- "paperless-ngx/backend"
|
||||
|
||||
# Enable updates for Github Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
target-branch: "dev"
|
||||
directory: "/"
|
||||
@ -76,50 +43,6 @@ updates:
|
||||
labels:
|
||||
- "ci-cd"
|
||||
- "dependencies"
|
||||
groups:
|
||||
actions:
|
||||
update-types:
|
||||
- "major"
|
||||
- "minor"
|
||||
- "patch"
|
||||
# Update Dockerfile in root directory
|
||||
- package-ecosystem: "docker"
|
||||
directories:
|
||||
- "/"
|
||||
- "/.devcontainer/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 5
|
||||
labels:
|
||||
- "dependencies"
|
||||
commit-message:
|
||||
prefix: "docker"
|
||||
include: "scope"
|
||||
# Update Docker Compose files in docker/compose directory
|
||||
- package-ecosystem: "docker-compose"
|
||||
directory: "/docker/compose/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 5
|
||||
labels:
|
||||
- "dependencies"
|
||||
commit-message:
|
||||
prefix: "docker-compose"
|
||||
include: "scope"
|
||||
groups:
|
||||
# Individual groups for each image
|
||||
gotenberg:
|
||||
patterns:
|
||||
- "docker.io/gotenberg/gotenberg*"
|
||||
tika:
|
||||
patterns:
|
||||
- "docker.io/apache/tika*"
|
||||
redis:
|
||||
patterns:
|
||||
- "docker.io/library/redis*"
|
||||
mariadb:
|
||||
patterns:
|
||||
- "docker.io/library/mariadb*"
|
||||
postgres:
|
||||
patterns:
|
||||
- "docker.io/library/postgres*"
|
||||
# Add reviewers
|
||||
reviewers:
|
||||
- "paperless-ngx/ci-cd"
|
||||
|
26
.github/labeler.yml
vendored
26
.github/labeler.yml
vendored
@ -1,26 +0,0 @@
|
||||
backend:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'src/**'
|
||||
- 'pyproject.toml'
|
||||
- 'uv.lock'
|
||||
- 'requirements.txt'
|
||||
frontend:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'src-ui/**'
|
||||
documentation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'docs/**'
|
||||
ci-cd:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- '.github/**'
|
||||
# pr types
|
||||
bug:
|
||||
- head-branch:
|
||||
- ['^fix']
|
||||
enhancement:
|
||||
- head-branch:
|
||||
- ['^feature']
|
28
.github/release-drafter.yml
vendored
28
.github/release-drafter.yml
vendored
@ -1,19 +1,26 @@
|
||||
autolabeler:
|
||||
- label: "bug"
|
||||
branch:
|
||||
- '/^fix/'
|
||||
title:
|
||||
- "/^fix/i"
|
||||
- label: "enhancement"
|
||||
branch:
|
||||
- '/^feature/'
|
||||
title:
|
||||
- "/^feature/i"
|
||||
categories:
|
||||
- title: 'Breaking Changes'
|
||||
labels:
|
||||
- 'breaking-change'
|
||||
- title: 'Notable Changes'
|
||||
labels:
|
||||
- 'notable'
|
||||
- title: 'Features / Enhancements'
|
||||
- title: 'Features'
|
||||
labels:
|
||||
- 'enhancement'
|
||||
- title: 'Bug Fixes'
|
||||
labels:
|
||||
- 'bug'
|
||||
- title: 'Documentation'
|
||||
labels:
|
||||
- 'documentation'
|
||||
label: 'documentation'
|
||||
- title: 'Maintenance'
|
||||
labels:
|
||||
- 'chore'
|
||||
@ -22,13 +29,12 @@ categories:
|
||||
- 'ci-cd'
|
||||
- title: 'Dependencies'
|
||||
collapse-after: 3
|
||||
labels:
|
||||
- 'dependencies'
|
||||
label: 'dependencies'
|
||||
- title: 'All App Changes'
|
||||
labels:
|
||||
- 'frontend'
|
||||
- 'backend'
|
||||
collapse-after: 1
|
||||
collapse-after: 0
|
||||
include-labels:
|
||||
- 'enhancement'
|
||||
- 'bug'
|
||||
@ -40,10 +46,6 @@ include-labels:
|
||||
- 'frontend'
|
||||
- 'backend'
|
||||
- 'ci-cd'
|
||||
- 'breaking-change'
|
||||
- 'notable'
|
||||
exclude-labels:
|
||||
- 'skip-changelog'
|
||||
category-template: '### $TITLE'
|
||||
change-template: '- $TITLE @$AUTHOR ([#$NUMBER]($URL))'
|
||||
change-title-escapes: '\<*_&#@'
|
||||
|
402
.github/scripts/cleanup-tags.py
vendored
Normal file
402
.github/scripts/cleanup-tags.py
vendored
Normal file
@ -0,0 +1,402 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from argparse import ArgumentParser
|
||||
from typing import Dict
|
||||
from typing import Final
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from common import get_log_level
|
||||
from github import ContainerPackage
|
||||
from github import GithubBranchApi
|
||||
from github import GithubContainerRegistryApi
|
||||
|
||||
logger = logging.getLogger("cleanup-tags")
|
||||
|
||||
|
||||
class DockerManifest2:
|
||||
"""
|
||||
Data class wrapping the Docker Image Manifest Version 2.
|
||||
|
||||
See https://docs.docker.com/registry/spec/manifest-v2-2/
|
||||
"""
|
||||
|
||||
def __init__(self, data: Dict) -> None:
|
||||
self._data = data
|
||||
# This is the sha256: digest string. Corresponds to GitHub API name
|
||||
# if the package is an untagged package
|
||||
self.digest = self._data["digest"]
|
||||
platform_data_os = self._data["platform"]["os"]
|
||||
platform_arch = self._data["platform"]["architecture"]
|
||||
platform_variant = self._data["platform"].get(
|
||||
"variant",
|
||||
"",
|
||||
)
|
||||
self.platform = f"{platform_data_os}/{platform_arch}{platform_variant}"
|
||||
|
||||
|
||||
class RegistryTagsCleaner:
|
||||
"""
|
||||
This is the base class for the image registry cleaning. Given a package
|
||||
name, it will keep all images which are tagged and all untagged images
|
||||
referred to by a manifest. This results in only images which have been untagged
|
||||
and cannot be referenced except by their SHA in being removed. None of these
|
||||
images should be referenced, so it is fine to delete them.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
package_name: str,
|
||||
repo_owner: str,
|
||||
repo_name: str,
|
||||
package_api: GithubContainerRegistryApi,
|
||||
branch_api: Optional[GithubBranchApi],
|
||||
):
|
||||
self.actually_delete = False
|
||||
self.package_api = package_api
|
||||
self.branch_api = branch_api
|
||||
self.package_name = package_name
|
||||
self.repo_owner = repo_owner
|
||||
self.repo_name = repo_name
|
||||
self.tags_to_delete: List[str] = []
|
||||
self.tags_to_keep: List[str] = []
|
||||
|
||||
# Get the information about all versions of the given package
|
||||
# These are active, not deleted, the default returned from the API
|
||||
self.all_package_versions = self.package_api.get_active_package_versions(
|
||||
self.package_name,
|
||||
)
|
||||
|
||||
# Get a mapping from a tag like "1.7.0" or "feature-xyz" to the ContainerPackage
|
||||
# tagged with it. It makes certain lookups easy
|
||||
self.all_pkgs_tags_to_version: Dict[str, ContainerPackage] = {}
|
||||
for pkg in self.all_package_versions:
|
||||
for tag in pkg.tags:
|
||||
self.all_pkgs_tags_to_version[tag] = pkg
|
||||
logger.info(
|
||||
f"Located {len(self.all_package_versions)} versions of package {self.package_name}",
|
||||
)
|
||||
|
||||
self.decide_what_tags_to_keep()
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
This method will delete image versions, based on the selected tags to delete
|
||||
"""
|
||||
for tag_to_delete in self.tags_to_delete:
|
||||
package_version_info = self.all_pkgs_tags_to_version[tag_to_delete]
|
||||
|
||||
if self.actually_delete:
|
||||
logger.info(
|
||||
f"Deleting {tag_to_delete} (id {package_version_info.id})",
|
||||
)
|
||||
self.package_api.delete_package_version(
|
||||
package_version_info,
|
||||
)
|
||||
|
||||
else:
|
||||
logger.info(
|
||||
f"Would delete {tag_to_delete} (id {package_version_info.id})",
|
||||
)
|
||||
else:
|
||||
logger.info("No tags to delete")
|
||||
|
||||
def clean_untagged(self, is_manifest_image: bool):
|
||||
"""
|
||||
This method will delete untagged images, that is those which are not named. It
|
||||
handles if the image tag is actually a manifest, which points to images that look otherwise
|
||||
untagged.
|
||||
"""
|
||||
|
||||
def _clean_untagged_manifest():
|
||||
"""
|
||||
|
||||
Handles the deletion of untagged images, but where the package is a manifest, ie a multi
|
||||
arch image, which means some "untagged" images need to exist still.
|
||||
|
||||
Ok, bear with me, these are annoying.
|
||||
|
||||
Our images are multi-arch, so the manifest is more like a pointer to a sha256 digest.
|
||||
These images are untagged, but pointed to, and so should not be removed (or every pull fails).
|
||||
|
||||
So for each image getting kept, parse the manifest to find the digest(s) it points to. Then
|
||||
remove those from the list of untagged images. The final result is the untagged, not pointed to
|
||||
version which should be safe to remove.
|
||||
|
||||
Example:
|
||||
Tag: ghcr.io/paperless-ngx/paperless-ngx:1.7.1 refers to
|
||||
amd64: sha256:b9ed4f8753bbf5146547671052d7e91f68cdfc9ef049d06690b2bc866fec2690
|
||||
armv7: sha256:81605222df4ba4605a2ba4893276e5d08c511231ead1d5da061410e1bbec05c3
|
||||
arm64: sha256:374cd68db40734b844705bfc38faae84cc4182371de4bebd533a9a365d5e8f3b
|
||||
each of which appears as untagged image, but isn't really.
|
||||
|
||||
So from the list of untagged packages, remove those digests. Once all tags which
|
||||
are being kept are checked, the remaining untagged packages are actually untagged
|
||||
with no referrals in a manifest to them.
|
||||
"""
|
||||
# Simplify the untagged data, mapping name (which is a digest) to the version
|
||||
# At the moment, these are the images which APPEAR untagged.
|
||||
untagged_versions = {}
|
||||
for x in self.all_package_versions:
|
||||
if x.untagged:
|
||||
untagged_versions[x.name] = x
|
||||
|
||||
skips = 0
|
||||
|
||||
# Parse manifests to locate digests pointed to
|
||||
for tag in sorted(self.tags_to_keep):
|
||||
full_name = f"ghcr.io/{self.repo_owner}/{self.package_name}:{tag}"
|
||||
logger.info(f"Checking manifest for {full_name}")
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[
|
||||
shutil.which("docker"),
|
||||
"manifest",
|
||||
"inspect",
|
||||
full_name,
|
||||
],
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
manifest_list = json.loads(proc.stdout)
|
||||
for manifest_data in manifest_list["manifests"]:
|
||||
manifest = DockerManifest2(manifest_data)
|
||||
|
||||
if manifest.digest in untagged_versions:
|
||||
logger.info(
|
||||
f"Skipping deletion of {manifest.digest},"
|
||||
f" referred to by {full_name}"
|
||||
f" for {manifest.platform}",
|
||||
)
|
||||
del untagged_versions[manifest.digest]
|
||||
skips += 1
|
||||
|
||||
except Exception as err:
|
||||
self.actually_delete = False
|
||||
logger.exception(err)
|
||||
return
|
||||
|
||||
logger.info(
|
||||
f"Skipping deletion of {skips} packages referred to by a manifest",
|
||||
)
|
||||
|
||||
# Delete the untagged and not pointed at packages
|
||||
logger.info(f"Deleting untagged packages of {self.package_name}")
|
||||
for to_delete_name in untagged_versions:
|
||||
to_delete_version = untagged_versions[to_delete_name]
|
||||
|
||||
if self.actually_delete:
|
||||
logger.info(
|
||||
f"Deleting id {to_delete_version.id} named {to_delete_version.name}",
|
||||
)
|
||||
self.package_api.delete_package_version(
|
||||
to_delete_version,
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
f"Would delete {to_delete_name} (id {to_delete_version.id})",
|
||||
)
|
||||
|
||||
def _clean_untagged_non_manifest():
|
||||
"""
|
||||
If the package is not a multi-arch manifest, images without tags are safe to delete.
|
||||
"""
|
||||
|
||||
for package in self.all_package_versions:
|
||||
if package.untagged:
|
||||
if self.actually_delete:
|
||||
logger.info(
|
||||
f"Deleting id {package.id} named {package.name}",
|
||||
)
|
||||
self.package_api.delete_package_version(
|
||||
package,
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
f"Would delete {package.name} (id {package.id})",
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
f"Not deleting tag {package.tags[0]} of package {self.package_name}",
|
||||
)
|
||||
|
||||
logger.info("Beginning untagged image cleaning")
|
||||
|
||||
if is_manifest_image:
|
||||
_clean_untagged_manifest()
|
||||
else:
|
||||
_clean_untagged_non_manifest()
|
||||
|
||||
def decide_what_tags_to_keep(self):
|
||||
"""
|
||||
This method holds the logic to delete what tags to keep and there fore
|
||||
what tags to delete.
|
||||
|
||||
By default, any image with at least 1 tag will be kept
|
||||
"""
|
||||
# By default, keep anything which is tagged
|
||||
self.tags_to_keep = list(set(self.all_pkgs_tags_to_version.keys()))
|
||||
|
||||
|
||||
class MainImageTagsCleaner(RegistryTagsCleaner):
|
||||
def decide_what_tags_to_keep(self):
|
||||
"""
|
||||
Overrides the default logic for deciding what images to keep. Images tagged as "feature-"
|
||||
will be removed, if the corresponding branch no longer exists.
|
||||
"""
|
||||
|
||||
# Default to everything gets kept still
|
||||
super().decide_what_tags_to_keep()
|
||||
|
||||
# Locate the feature branches
|
||||
feature_branches = {}
|
||||
for branch in self.branch_api.get_branches(
|
||||
repo=self.repo_name,
|
||||
):
|
||||
if branch.name.startswith("feature-"):
|
||||
logger.debug(f"Found feature branch {branch.name}")
|
||||
feature_branches[branch.name] = branch
|
||||
|
||||
logger.info(f"Located {len(feature_branches)} feature branches")
|
||||
|
||||
if not len(feature_branches):
|
||||
# Our work here is done, delete nothing
|
||||
return
|
||||
|
||||
# Filter to packages which are tagged with feature-*
|
||||
packages_tagged_feature: List[ContainerPackage] = []
|
||||
for package in self.all_package_versions:
|
||||
if package.tag_matches("feature-"):
|
||||
packages_tagged_feature.append(package)
|
||||
|
||||
# Map tags like "feature-xyz" to a ContainerPackage
|
||||
feature_pkgs_tags_to_versions: Dict[str, ContainerPackage] = {}
|
||||
for pkg in packages_tagged_feature:
|
||||
for tag in pkg.tags:
|
||||
feature_pkgs_tags_to_versions[tag] = pkg
|
||||
|
||||
logger.info(
|
||||
f'Located {len(feature_pkgs_tags_to_versions)} versions of package {self.package_name} tagged "feature-"',
|
||||
)
|
||||
|
||||
# All the feature tags minus all the feature branches leaves us feature tags
|
||||
# with no corresponding branch
|
||||
self.tags_to_delete = list(
|
||||
set(feature_pkgs_tags_to_versions.keys()) - set(feature_branches.keys()),
|
||||
)
|
||||
|
||||
# All the tags minus the set of going to be deleted tags leaves us the
|
||||
# tags which will be kept around
|
||||
self.tags_to_keep = list(
|
||||
set(self.all_pkgs_tags_to_version.keys()) - set(self.tags_to_delete),
|
||||
)
|
||||
logger.info(
|
||||
f"Located {len(self.tags_to_delete)} versions of package {self.package_name} to delete",
|
||||
)
|
||||
|
||||
|
||||
class LibraryTagsCleaner(RegistryTagsCleaner):
|
||||
"""
|
||||
Exists for the off change that someday, the installer library images
|
||||
will need their own logic
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def _main():
|
||||
parser = ArgumentParser(
|
||||
description="Using the GitHub API locate and optionally delete container"
|
||||
" tags which no longer have an associated feature branch",
|
||||
)
|
||||
|
||||
# Requires an affirmative command to actually do a delete
|
||||
parser.add_argument(
|
||||
"--delete",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="If provided, actually delete the container tags",
|
||||
)
|
||||
|
||||
# When a tagged image is updated, the previous version remains, but it no longer tagged
|
||||
# Add this option to remove them as well
|
||||
parser.add_argument(
|
||||
"--untagged",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="If provided, delete untagged containers as well",
|
||||
)
|
||||
|
||||
# If given, the package is assumed to be a multi-arch manifest. Cache packages are
|
||||
# not multi-arch, all other types are
|
||||
parser.add_argument(
|
||||
"--is-manifest",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="If provided, the package is assumed to be a multi-arch manifest following schema v2",
|
||||
)
|
||||
|
||||
# Allows configuration of log level for debugging
|
||||
parser.add_argument(
|
||||
"--loglevel",
|
||||
default="info",
|
||||
help="Configures the logging level",
|
||||
)
|
||||
|
||||
# Get the name of the package being processed this round
|
||||
parser.add_argument(
|
||||
"package",
|
||||
help="The package to process",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.basicConfig(
|
||||
level=get_log_level(args),
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
format="%(asctime)s %(levelname)-8s %(message)s",
|
||||
)
|
||||
|
||||
# Must be provided in the environment
|
||||
repo_owner: Final[str] = os.environ["GITHUB_REPOSITORY_OWNER"]
|
||||
repo: Final[str] = os.environ["GITHUB_REPOSITORY"]
|
||||
gh_token: Final[str] = os.environ["TOKEN"]
|
||||
|
||||
# Find all branches named feature-*
|
||||
# Note: Only relevant to the main application, but simpler to
|
||||
# leave in for all packages
|
||||
with GithubBranchApi(gh_token) as branch_api:
|
||||
with GithubContainerRegistryApi(gh_token, repo_owner) as container_api:
|
||||
if args.package in {"paperless-ngx", "paperless-ngx/builder/cache/app"}:
|
||||
cleaner = MainImageTagsCleaner(
|
||||
args.package,
|
||||
repo_owner,
|
||||
repo,
|
||||
container_api,
|
||||
branch_api,
|
||||
)
|
||||
else:
|
||||
cleaner = LibraryTagsCleaner(
|
||||
args.package,
|
||||
repo_owner,
|
||||
repo,
|
||||
container_api,
|
||||
None,
|
||||
)
|
||||
|
||||
# Set if actually doing a delete vs dry run
|
||||
cleaner.actually_delete = args.delete
|
||||
|
||||
# Clean images with tags
|
||||
cleaner.clean()
|
||||
|
||||
# Clean images which are untagged
|
||||
cleaner.clean_untagged(args.is_manifest)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_main()
|
48
.github/scripts/common.py
vendored
Normal file
48
.github/scripts/common.py
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
import logging
|
||||
|
||||
|
||||
def get_image_tag(
|
||||
repo_name: str,
|
||||
pkg_name: str,
|
||||
pkg_version: str,
|
||||
) -> str:
|
||||
"""
|
||||
Returns a string representing the normal image for a given package
|
||||
"""
|
||||
return f"ghcr.io/{repo_name.lower()}/builder/{pkg_name}:{pkg_version}"
|
||||
|
||||
|
||||
def get_cache_image_tag(
|
||||
repo_name: str,
|
||||
pkg_name: str,
|
||||
pkg_version: str,
|
||||
branch_name: str,
|
||||
) -> str:
|
||||
"""
|
||||
Returns a string representing the expected image cache tag for a given package
|
||||
|
||||
Registry type caching is utilized for the builder images, to allow fast
|
||||
rebuilds, generally almost instant for the same version
|
||||
"""
|
||||
return f"ghcr.io/{repo_name.lower()}/builder/cache/{pkg_name}:{pkg_version}"
|
||||
|
||||
|
||||
def get_log_level(args) -> int:
|
||||
"""
|
||||
Returns a logging level, based
|
||||
:param args:
|
||||
:return:
|
||||
"""
|
||||
levels = {
|
||||
"critical": logging.CRITICAL,
|
||||
"error": logging.ERROR,
|
||||
"warn": logging.WARNING,
|
||||
"warning": logging.WARNING,
|
||||
"info": logging.INFO,
|
||||
"debug": logging.DEBUG,
|
||||
}
|
||||
level = levels.get(args.loglevel.lower())
|
||||
if level is None:
|
||||
level = logging.INFO
|
||||
return level
|
92
.github/scripts/get-build-json.py
vendored
Executable file
92
.github/scripts/get-build-json.py
vendored
Executable file
@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
This is a helper script for the mutli-stage Docker image builder.
|
||||
It provides a single point of configuration for package version control.
|
||||
The output JSON object is used by the CI workflow to determine what versions
|
||||
to build and pull into the final Docker image.
|
||||
|
||||
Python package information is obtained from the Pipfile.lock. As this is
|
||||
kept updated by dependabot, it usually will need no further configuration.
|
||||
The sole exception currently is pikepdf, which has a dependency on qpdf,
|
||||
and is configured here to use the latest version of qpdf built by the workflow.
|
||||
|
||||
Other package version information is configured directly below, generally by
|
||||
setting the version and Git information, if any.
|
||||
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
from common import get_cache_image_tag
|
||||
from common import get_image_tag
|
||||
|
||||
|
||||
def _main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate a JSON object of information required to build the given package, based on the Pipfile.lock",
|
||||
)
|
||||
parser.add_argument(
|
||||
"package",
|
||||
help="The name of the package to generate JSON for",
|
||||
)
|
||||
|
||||
PIPFILE_LOCK_PATH: Final[Path] = Path("Pipfile.lock")
|
||||
BUILD_CONFIG_PATH: Final[Path] = Path(".build-config.json")
|
||||
|
||||
# Read the main config file
|
||||
build_json: Final = json.loads(BUILD_CONFIG_PATH.read_text())
|
||||
|
||||
# Read Pipfile.lock file
|
||||
pipfile_data: Final = json.loads(PIPFILE_LOCK_PATH.read_text())
|
||||
|
||||
args: Final = parser.parse_args()
|
||||
|
||||
# Read from environment variables set by GitHub Actions
|
||||
repo_name: Final[str] = os.environ["GITHUB_REPOSITORY"]
|
||||
branch_name: Final[str] = os.environ["GITHUB_REF_NAME"]
|
||||
|
||||
# Default output values
|
||||
version = None
|
||||
extra_config = {}
|
||||
|
||||
if args.package in pipfile_data["default"]:
|
||||
# Read the version from Pipfile.lock
|
||||
pkg_data = pipfile_data["default"][args.package]
|
||||
pkg_version = pkg_data["version"].split("==")[-1]
|
||||
version = pkg_version
|
||||
|
||||
# Any extra/special values needed
|
||||
if args.package == "pikepdf":
|
||||
extra_config["qpdf_version"] = build_json["qpdf"]["version"]
|
||||
|
||||
elif args.package in build_json:
|
||||
version = build_json[args.package]["version"]
|
||||
|
||||
else:
|
||||
raise NotImplementedError(args.package)
|
||||
|
||||
# The JSON object we'll output
|
||||
output = {
|
||||
"name": args.package,
|
||||
"version": version,
|
||||
"image_tag": get_image_tag(repo_name, args.package, version),
|
||||
"cache_tag": get_cache_image_tag(
|
||||
repo_name,
|
||||
args.package,
|
||||
version,
|
||||
branch_name,
|
||||
),
|
||||
}
|
||||
|
||||
# Add anything special a package may need
|
||||
output.update(extra_config)
|
||||
|
||||
# Output the JSON info to stdout
|
||||
print(json.dumps(output))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_main()
|
274
.github/scripts/github.py
vendored
Normal file
274
.github/scripts/github.py
vendored
Normal file
@ -0,0 +1,274 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
This module contains some useful classes for interacting with the Github API.
|
||||
The full documentation for the API can be found here: https://docs.github.com/en/rest
|
||||
|
||||
Mostly, this focusses on two areas, repo branches and repo packages, as the use case
|
||||
is cleaning up container images which are no longer referred to.
|
||||
|
||||
"""
|
||||
import functools
|
||||
import logging
|
||||
import re
|
||||
import urllib.parse
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
|
||||
logger = logging.getLogger("github-api")
|
||||
|
||||
|
||||
class _GithubApiBase:
|
||||
"""
|
||||
A base class for interacting with the Github API. It
|
||||
will handle the session and setting authorization headers.
|
||||
"""
|
||||
|
||||
def __init__(self, token: str) -> None:
|
||||
self._token = token
|
||||
self._client: Optional[httpx.Client] = None
|
||||
|
||||
def __enter__(self) -> "_GithubApiBase":
|
||||
"""
|
||||
Sets up the required headers for auth and response
|
||||
type from the API
|
||||
"""
|
||||
self._client = httpx.Client()
|
||||
self._client.headers.update(
|
||||
{
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
"Authorization": f"token {self._token}",
|
||||
},
|
||||
)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""
|
||||
Ensures the authorization token is cleaned up no matter
|
||||
the reason for the exit
|
||||
"""
|
||||
if "Accept" in self._client.headers:
|
||||
del self._client.headers["Accept"]
|
||||
if "Authorization" in self._client.headers:
|
||||
del self._client.headers["Authorization"]
|
||||
|
||||
# Close the session as well
|
||||
self._client.close()
|
||||
self._client = None
|
||||
|
||||
def _read_all_pages(self, endpoint):
|
||||
"""
|
||||
Helper function to read all pages of an endpoint, utilizing the
|
||||
next.url until exhausted. Assumes the endpoint returns a list
|
||||
"""
|
||||
internal_data = []
|
||||
|
||||
while True:
|
||||
resp = self._client.get(endpoint)
|
||||
if resp.status_code == 200:
|
||||
internal_data += resp.json()
|
||||
if "next" in resp.links:
|
||||
endpoint = resp.links["next"]["url"]
|
||||
else:
|
||||
logger.debug("Exiting pagination loop")
|
||||
break
|
||||
else:
|
||||
logger.warning(f"Request to {endpoint} return HTTP {resp.status_code}")
|
||||
resp.raise_for_status()
|
||||
|
||||
return internal_data
|
||||
|
||||
|
||||
class _EndpointResponse:
|
||||
"""
|
||||
For all endpoint JSON responses, store the full
|
||||
response data, for ease of extending later, if need be.
|
||||
"""
|
||||
|
||||
def __init__(self, data: Dict) -> None:
|
||||
self._data = data
|
||||
|
||||
|
||||
class GithubBranch(_EndpointResponse):
|
||||
"""
|
||||
Simple wrapper for a repository branch, only extracts name information
|
||||
for now.
|
||||
"""
|
||||
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.name = self._data["name"]
|
||||
|
||||
|
||||
class GithubBranchApi(_GithubApiBase):
|
||||
"""
|
||||
Wrapper around branch API.
|
||||
|
||||
See https://docs.github.com/en/rest/branches/branches
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, token: str) -> None:
|
||||
super().__init__(token)
|
||||
|
||||
self._ENDPOINT = "https://api.github.com/repos/{REPO}/branches"
|
||||
|
||||
def get_branches(self, repo: str) -> List[GithubBranch]:
|
||||
"""
|
||||
Returns all current branches of the given repository owned by the given
|
||||
owner or organization.
|
||||
"""
|
||||
# The environment GITHUB_REPOSITORY already contains the owner in the correct location
|
||||
endpoint = self._ENDPOINT.format(REPO=repo)
|
||||
internal_data = self._read_all_pages(endpoint)
|
||||
return [GithubBranch(branch) for branch in internal_data]
|
||||
|
||||
|
||||
class ContainerPackage(_EndpointResponse):
|
||||
"""
|
||||
Data class wrapping the JSON response from the package related
|
||||
endpoints
|
||||
"""
|
||||
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data)
|
||||
# This is a numerical ID, required for interactions with this
|
||||
# specific package, including deletion of it or restoration
|
||||
self.id: int = self._data["id"]
|
||||
|
||||
# A string name. This might be an actual name or it could be a
|
||||
# digest string like "sha256:"
|
||||
self.name: str = self._data["name"]
|
||||
|
||||
# URL to the package, including its ID, can be used for deletion
|
||||
# or restoration without needing to build up a URL ourselves
|
||||
self.url: str = self._data["url"]
|
||||
|
||||
# The list of tags applied to this image. Maybe an empty list
|
||||
self.tags: List[str] = self._data["metadata"]["container"]["tags"]
|
||||
|
||||
@functools.cached_property
|
||||
def untagged(self) -> bool:
|
||||
"""
|
||||
Returns True if the image has no tags applied to it, False otherwise
|
||||
"""
|
||||
return len(self.tags) == 0
|
||||
|
||||
@functools.cache
|
||||
def tag_matches(self, pattern: str) -> bool:
|
||||
"""
|
||||
Returns True if the image has at least one tag which matches the given regex,
|
||||
False otherwise
|
||||
"""
|
||||
for tag in self.tags:
|
||||
if re.match(pattern, tag) is not None:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return f"Package {self.name}"
|
||||
|
||||
|
||||
class GithubContainerRegistryApi(_GithubApiBase):
|
||||
"""
|
||||
Class wrapper to deal with the Github packages API. This class only deals with
|
||||
container type packages, the only type published by paperless-ngx.
|
||||
"""
|
||||
|
||||
def __init__(self, token: str, owner_or_org: str) -> None:
|
||||
super().__init__(token)
|
||||
self._owner_or_org = owner_or_org
|
||||
if self._owner_or_org == "paperless-ngx":
|
||||
# https://docs.github.com/en/rest/packages#get-all-package-versions-for-a-package-owned-by-an-organization
|
||||
self._PACKAGES_VERSIONS_ENDPOINT = "https://api.github.com/orgs/{ORG}/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions"
|
||||
# https://docs.github.com/en/rest/packages#delete-package-version-for-an-organization
|
||||
self._PACKAGE_VERSION_DELETE_ENDPOINT = "https://api.github.com/orgs/{ORG}/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions/{PACKAGE_VERSION_ID}"
|
||||
else:
|
||||
# https://docs.github.com/en/rest/packages#get-all-package-versions-for-a-package-owned-by-the-authenticated-user
|
||||
self._PACKAGES_VERSIONS_ENDPOINT = "https://api.github.com/user/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions"
|
||||
# https://docs.github.com/en/rest/packages#delete-a-package-version-for-the-authenticated-user
|
||||
self._PACKAGE_VERSION_DELETE_ENDPOINT = "https://api.github.com/user/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions/{PACKAGE_VERSION_ID}"
|
||||
self._PACKAGE_VERSION_RESTORE_ENDPOINT = (
|
||||
f"{self._PACKAGE_VERSION_DELETE_ENDPOINT}/restore"
|
||||
)
|
||||
|
||||
def get_active_package_versions(
|
||||
self,
|
||||
package_name: str,
|
||||
) -> List[ContainerPackage]:
|
||||
"""
|
||||
Returns all the versions of a given package (container images) from
|
||||
the API
|
||||
"""
|
||||
|
||||
package_type: str = "container"
|
||||
# Need to quote this for slashes in the name
|
||||
package_name = urllib.parse.quote(package_name, safe="")
|
||||
|
||||
endpoint = self._PACKAGES_VERSIONS_ENDPOINT.format(
|
||||
ORG=self._owner_or_org,
|
||||
PACKAGE_TYPE=package_type,
|
||||
PACKAGE_NAME=package_name,
|
||||
)
|
||||
|
||||
pkgs = []
|
||||
|
||||
for data in self._read_all_pages(endpoint):
|
||||
pkgs.append(ContainerPackage(data))
|
||||
|
||||
return pkgs
|
||||
|
||||
def get_deleted_package_versions(
|
||||
self,
|
||||
package_name: str,
|
||||
) -> List[ContainerPackage]:
|
||||
package_type: str = "container"
|
||||
# Need to quote this for slashes in the name
|
||||
package_name = urllib.parse.quote(package_name, safe="")
|
||||
|
||||
endpoint = (
|
||||
self._PACKAGES_VERSIONS_ENDPOINT.format(
|
||||
ORG=self._owner_or_org,
|
||||
PACKAGE_TYPE=package_type,
|
||||
PACKAGE_NAME=package_name,
|
||||
)
|
||||
+ "?state=deleted"
|
||||
)
|
||||
|
||||
pkgs = []
|
||||
|
||||
for data in self._read_all_pages(endpoint):
|
||||
pkgs.append(ContainerPackage(data))
|
||||
|
||||
return pkgs
|
||||
|
||||
def delete_package_version(self, package_data: ContainerPackage):
|
||||
"""
|
||||
Deletes the given package version from the GHCR
|
||||
"""
|
||||
resp = self._client.delete(package_data.url)
|
||||
if resp.status_code != 204:
|
||||
logger.warning(
|
||||
f"Request to delete {package_data.url} returned HTTP {resp.status_code}",
|
||||
)
|
||||
|
||||
def restore_package_version(
|
||||
self,
|
||||
package_name: str,
|
||||
package_data: ContainerPackage,
|
||||
):
|
||||
package_type: str = "container"
|
||||
endpoint = self._PACKAGE_VERSION_RESTORE_ENDPOINT.format(
|
||||
ORG=self._owner_or_org,
|
||||
PACKAGE_TYPE=package_type,
|
||||
PACKAGE_NAME=package_name,
|
||||
PACKAGE_VERSION_ID=package_data.id,
|
||||
)
|
||||
|
||||
resp = self._client.post(endpoint)
|
||||
if resp.status_code != 204:
|
||||
logger.warning(
|
||||
f"Request to delete {endpoint} returned HTTP {resp.status_code}",
|
||||
)
|
23
.github/stale.yml
vendored
Normal file
23
.github/stale.yml
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 30
|
||||
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
|
||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
||||
onlyLabels: [cant-reproduce]
|
||||
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
|
||||
# See https://github.com/marketplace/stale for more info on the app
|
||||
# and https://github.com/probot/stale for the configuration docs
|
776
.github/workflows/ci.yml
vendored
776
.github/workflows/ci.yml
vendored
@ -1,4 +1,5 @@
|
||||
name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
@ -11,318 +12,258 @@ on:
|
||||
pull_request:
|
||||
branches-ignore:
|
||||
- 'translations**'
|
||||
env:
|
||||
DEFAULT_UV_VERSION: "0.7.x"
|
||||
# This is the default version of Python to use in most steps which aren't specific
|
||||
DEFAULT_PYTHON_VERSION: "3.11"
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
# We want to run on external PRs, but not on our own internal PRs as they'll be run
|
||||
# by the push to the branch. Without this if check, checks are duplicated since
|
||||
# internal PRs match both the push and pull_request events.
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||
name: Linting Checks
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v5
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
-
|
||||
name: Install tools
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||
- name: Check files
|
||||
uses: pre-commit/action@v3.0.1
|
||||
python-version: "3.9"
|
||||
|
||||
-
|
||||
name: Check files
|
||||
uses: pre-commit/action@v3.0.0
|
||||
|
||||
documentation:
|
||||
name: "Build & Deploy Documentation"
|
||||
runs-on: ubuntu-24.04
|
||||
name: "Build Documentation"
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- pre-commit
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
id: setup-python
|
||||
uses: actions/setup-python@v5
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Install pipenv
|
||||
run: |
|
||||
pipx install pipenv==2022.10.12
|
||||
-
|
||||
name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||
enable-cache: true
|
||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||
- name: Install Python dependencies
|
||||
python-version: 3.9
|
||||
cache: "pipenv"
|
||||
cache-dependency-path: 'Pipfile.lock'
|
||||
-
|
||||
name: Install dependencies
|
||||
run: |
|
||||
uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen
|
||||
- name: Make documentation
|
||||
pipenv sync --dev
|
||||
-
|
||||
name: List installed Python dependencies
|
||||
run: |
|
||||
uv run \
|
||||
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||
--dev \
|
||||
--frozen \
|
||||
mkdocs build --config-file ./mkdocs.yml
|
||||
- name: Deploy documentation
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
pipenv run pip list
|
||||
-
|
||||
name: Make documentation
|
||||
run: |
|
||||
echo "docs.paperless-ngx.com" > "${{ github.workspace }}/docs/CNAME"
|
||||
git config --global user.name "${{ github.actor }}"
|
||||
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
|
||||
uv run \
|
||||
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||
--dev \
|
||||
--frozen \
|
||||
mkdocs gh-deploy --force --no-history
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
cd docs/
|
||||
pipenv run make html
|
||||
-
|
||||
name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: documentation
|
||||
path: site/
|
||||
retention-days: 7
|
||||
path: docs/_build/html/
|
||||
|
||||
tests-backend:
|
||||
name: "Backend Tests (Python ${{ matrix.python-version }})"
|
||||
runs-on: ubuntu-24.04
|
||||
name: "Tests (${{ matrix.python-version }})"
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- pre-commit
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ['3.10', '3.11', '3.12']
|
||||
python-version: ['3.8', '3.9', '3.10']
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Start containers
|
||||
run: |
|
||||
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml pull --quiet
|
||||
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml up --detach
|
||||
- name: Set up Python
|
||||
id: setup-python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "${{ matrix.python-version }}"
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||
enable-cache: true
|
||||
python-version: ${{ steps.setup-python.outputs.python-version }}
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq --no-install-recommends unpaper tesseract-ocr imagemagick ghostscript libzbar0 poppler-utils
|
||||
- name: Configure ImageMagick
|
||||
run: |
|
||||
sudo cp docker/rootfs/etc/ImageMagick-6/paperless-policy.xml /etc/ImageMagick-6/policy.xml
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
uv sync \
|
||||
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||
--group testing \
|
||||
--frozen
|
||||
- name: List installed Python dependencies
|
||||
run: |
|
||||
uv pip list
|
||||
- name: Tests
|
||||
services:
|
||||
tika:
|
||||
image: ghcr.io/paperless-ngx/tika:latest
|
||||
ports:
|
||||
- "9998:9998/tcp"
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:7.6
|
||||
ports:
|
||||
- "3000:3000/tcp"
|
||||
env:
|
||||
PAPERLESS_CI_TEST: 1
|
||||
# Enable Tika end to end testing
|
||||
TIKA_LIVE: 1
|
||||
# Enable paperless_mail testing against real server
|
||||
PAPERLESS_MAIL_TEST_HOST: ${{ secrets.TEST_MAIL_HOST }}
|
||||
PAPERLESS_MAIL_TEST_USER: ${{ secrets.TEST_MAIL_USER }}
|
||||
PAPERLESS_MAIL_TEST_PASSWD: ${{ secrets.TEST_MAIL_PASSWD }}
|
||||
run: |
|
||||
uv run \
|
||||
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||
--dev \
|
||||
--frozen \
|
||||
pytest
|
||||
- name: Upload backend test results to Codecov
|
||||
if: always()
|
||||
uses: codecov/test-results-action@v1
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
flags: backend-python-${{ matrix.python-version }}
|
||||
files: junit.xml
|
||||
- name: Upload backend coverage to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
flags: backend-python-${{ matrix.python-version }}
|
||||
files: coverage.xml
|
||||
- name: Stop containers
|
||||
if: always()
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Install pipenv
|
||||
run: |
|
||||
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml logs
|
||||
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml down
|
||||
install-frontend-dependencies:
|
||||
name: "Install Frontend Dependencies"
|
||||
runs-on: ubuntu-24.04
|
||||
pipx install pipenv==2022.10.12
|
||||
-
|
||||
name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "${{ matrix.python-version }}"
|
||||
cache: "pipenv"
|
||||
cache-dependency-path: 'Pipfile.lock'
|
||||
-
|
||||
name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq --no-install-recommends unpaper tesseract-ocr imagemagick ghostscript libzbar0 poppler-utils
|
||||
-
|
||||
name: Install Python dependencies
|
||||
run: |
|
||||
pipenv sync --dev
|
||||
-
|
||||
name: List installed Python dependencies
|
||||
run: |
|
||||
pipenv run pip list
|
||||
-
|
||||
name: Tests
|
||||
run: |
|
||||
cd src/
|
||||
pipenv run pytest -rfEp
|
||||
-
|
||||
name: Get changed files
|
||||
id: changed-files-specific
|
||||
uses: tj-actions/changed-files@v34
|
||||
with:
|
||||
files: |
|
||||
src/**
|
||||
-
|
||||
name: List all changed files
|
||||
run: |
|
||||
for file in ${{ steps.changed-files-specific.outputs.all_changed_files }}; do
|
||||
echo "${file} was changed"
|
||||
done
|
||||
-
|
||||
name: Publish coverage results
|
||||
if: matrix.python-version == '3.9' && steps.changed-files-specific.outputs.any_changed == 'true'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# https://github.com/coveralls-clients/coveralls-python/issues/251
|
||||
run: |
|
||||
cd src/
|
||||
pipenv run coveralls --service=github
|
||||
|
||||
tests-frontend:
|
||||
name: "Tests Frontend"
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- pre-commit
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||
- name: Cache frontend dependencies
|
||||
id: cache-frontend-deps
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.pnpm-store
|
||||
~/.cache
|
||||
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
|
||||
- name: Install dependencies
|
||||
run: cd src-ui && pnpm install
|
||||
tests-frontend:
|
||||
name: "Frontend Unit Tests (Node ${{ matrix.node-version }} - ${{ matrix.shard-index }}/${{ matrix.shard-count }})"
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- install-frontend-dependencies
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: [20.x]
|
||||
shard-index: [1, 2, 3, 4]
|
||||
shard-count: [4]
|
||||
node-version: [16.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
- uses: actions/checkout@v3
|
||||
-
|
||||
name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
version: 10
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||
- name: Cache frontend dependencies
|
||||
id: cache-frontend-deps
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.pnpm-store
|
||||
~/.cache
|
||||
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
|
||||
- name: Re-link Angular cli
|
||||
run: cd src-ui && pnpm link @angular/cli
|
||||
- name: Linting checks
|
||||
run: cd src-ui && pnpm run lint
|
||||
- name: Run Jest unit tests
|
||||
run: cd src-ui && pnpm run test --max-workers=2 --shard=${{ matrix.shard-index }}/${{ matrix.shard-count }}
|
||||
- name: Upload frontend test results to Codecov
|
||||
uses: codecov/test-results-action@v1
|
||||
if: always()
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
flags: frontend-node-${{ matrix.node-version }}
|
||||
directory: src-ui/
|
||||
- name: Upload frontend coverage to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
flags: frontend-node-${{ matrix.node-version }}
|
||||
directory: src-ui/coverage/
|
||||
tests-frontend-e2e:
|
||||
name: "Frontend E2E Tests (Node ${{ matrix.node-version }} - ${{ matrix.shard-index }}/${{ matrix.shard-count }})"
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- install-frontend-dependencies
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: [20.x]
|
||||
shard-index: [1, 2]
|
||||
shard-count: [2]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||
- name: Cache frontend dependencies
|
||||
id: cache-frontend-deps
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.pnpm-store
|
||||
~/.cache
|
||||
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
|
||||
- name: Re-link Angular cli
|
||||
run: cd src-ui && pnpm link @angular/cli
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-playwright-
|
||||
- name: Install Playwright system dependencies
|
||||
run: npx playwright install-deps
|
||||
- name: Install dependencies
|
||||
run: cd src-ui && pnpm install --no-frozen-lockfile
|
||||
- name: Install Playwright
|
||||
run: cd src-ui && pnpm exec playwright install
|
||||
- name: Run Playwright e2e tests
|
||||
run: cd src-ui && pnpm exec playwright test --shard ${{ matrix.shard-index }}/${{ matrix.shard-count }}
|
||||
frontend-bundle-analysis:
|
||||
name: "Frontend Bundle Analysis"
|
||||
runs-on: ubuntu-24.04
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: cd src-ui && npm ci
|
||||
- run: cd src-ui && npm run test
|
||||
- run: cd src-ui && npm run e2e:ci
|
||||
|
||||
prepare-docker-build:
|
||||
name: Prepare Docker Pipeline Data
|
||||
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/heads/feature-') || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/beta' || contains(github.ref, 'beta.rc') || startsWith(github.ref, 'refs/tags/v'))
|
||||
runs-on: ubuntu-20.04
|
||||
# If the push triggered the installer library workflow, wait for it to
|
||||
# complete here. This ensures the required versions for the final
|
||||
# image have been built, while not waiting at all if the versions haven't changed
|
||||
concurrency:
|
||||
group: build-installer-library
|
||||
cancel-in-progress: false
|
||||
needs:
|
||||
- documentation
|
||||
- tests-backend
|
||||
- tests-frontend
|
||||
- tests-frontend-e2e
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
-
|
||||
name: Set ghcr repository name
|
||||
id: set-ghcr-repository
|
||||
run: |
|
||||
ghcr_name=$(echo "${GITHUB_REPOSITORY}" | awk '{ print tolower($0) }')
|
||||
echo "repository=${ghcr_name}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
version: 10
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||
- name: Cache frontend dependencies
|
||||
id: cache-frontend-deps
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.pnpm-store
|
||||
~/.cache
|
||||
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/package-lock.json') }}
|
||||
- name: Re-link Angular cli
|
||||
run: cd src-ui && pnpm link @angular/cli
|
||||
- name: Build frontend and upload analysis
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
run: cd src-ui && pnpm run build --configuration=production
|
||||
python-version: "3.9"
|
||||
-
|
||||
name: Setup qpdf image
|
||||
id: qpdf-setup
|
||||
run: |
|
||||
build_json=$(python ${GITHUB_WORKSPACE}/.github/scripts/get-build-json.py qpdf)
|
||||
|
||||
echo ${build_json}
|
||||
|
||||
echo "qpdf-json=${build_json}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Setup psycopg2 image
|
||||
id: psycopg2-setup
|
||||
run: |
|
||||
build_json=$(python ${GITHUB_WORKSPACE}/.github/scripts/get-build-json.py psycopg2)
|
||||
|
||||
echo ${build_json}
|
||||
|
||||
echo "psycopg2-json=${build_json}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Setup pikepdf image
|
||||
id: pikepdf-setup
|
||||
run: |
|
||||
build_json=$(python ${GITHUB_WORKSPACE}/.github/scripts/get-build-json.py pikepdf)
|
||||
|
||||
echo ${build_json}
|
||||
|
||||
echo "pikepdf-json=${build_json}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Setup jbig2enc image
|
||||
id: jbig2enc-setup
|
||||
run: |
|
||||
build_json=$(python ${GITHUB_WORKSPACE}/.github/scripts/get-build-json.py jbig2enc)
|
||||
|
||||
echo ${build_json}
|
||||
|
||||
echo "jbig2enc-json=${build_json}" >> $GITHUB_OUTPUT
|
||||
|
||||
outputs:
|
||||
|
||||
ghcr-repository: ${{ steps.set-ghcr-repository.outputs.repository }}
|
||||
|
||||
qpdf-json: ${{ steps.qpdf-setup.outputs.qpdf-json }}
|
||||
|
||||
pikepdf-json: ${{ steps.pikepdf-setup.outputs.pikepdf-json }}
|
||||
|
||||
psycopg2-json: ${{ steps.psycopg2-setup.outputs.psycopg2-json }}
|
||||
|
||||
jbig2enc-json: ${{ steps.jbig2enc-setup.outputs.jbig2enc-json}}
|
||||
|
||||
# build and push image to docker hub.
|
||||
build-docker-image:
|
||||
name: Build Docker image for ${{ github.ref_name }}
|
||||
runs-on: ubuntu-24.04
|
||||
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/heads/feature-') || startsWith(github.ref, 'refs/heads/fix-') || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/beta' || contains(github.ref, 'beta.rc') || startsWith(github.ref, 'refs/tags/v'))
|
||||
runs-on: ubuntu-20.04
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-build-docker-image-${{ github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
needs:
|
||||
- tests-backend
|
||||
- tests-frontend
|
||||
- tests-frontend-e2e
|
||||
- prepare-docker-build
|
||||
steps:
|
||||
- name: Check pushing to Docker Hub
|
||||
id: push-other-places
|
||||
-
|
||||
name: Check pushing to Docker Hub
|
||||
id: docker-hub
|
||||
# Only push to Dockerhub from the main repo AND the ref is either:
|
||||
# main
|
||||
# dev
|
||||
@ -330,27 +271,21 @@ jobs:
|
||||
# a tag
|
||||
# Otherwise forks would require a Docker Hub account and secrets setup
|
||||
run: |
|
||||
if [[ ${{ github.repository_owner }} == "paperless-ngx" && ( ${{ github.ref_name }} == "dev" || ${{ github.ref_name }} == "beta" || ${{ startsWith(github.ref, 'refs/tags/v') }} == "true" ) ]] ; then
|
||||
if [[ ${{ needs.prepare-docker-build.outputs.ghcr-repository }} == "paperless-ngx/paperless-ngx" && ( ${{ github.ref_name }} == "main" || ${{ github.ref_name }} == "dev" || ${{ github.ref_name }} == "beta" || ${{ startsWith(github.ref, 'refs/tags/v') }} == "true" ) ]] ; then
|
||||
echo "Enabling DockerHub image push"
|
||||
echo "enable=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Not pushing to DockerHub"
|
||||
echo "enable=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Set ghcr repository name
|
||||
id: set-ghcr-repository
|
||||
run: |
|
||||
ghcr_name=$(echo "${{ github.repository }}" | awk '{ print tolower($0) }')
|
||||
echo "Name is ${ghcr_name}"
|
||||
echo "ghcr-repository=${ghcr_name}" >> $GITHUB_OUTPUT
|
||||
- name: Gather Docker metadata
|
||||
-
|
||||
name: Gather Docker metadata
|
||||
id: docker-meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/${{ steps.set-ghcr-repository.outputs.ghcr-repository }}
|
||||
name=paperlessngx/paperless-ngx,enable=${{ steps.push-other-places.outputs.enable }}
|
||||
name=quay.io/paperlessngx/paperless-ngx,enable=${{ steps.push-other-places.outputs.enable }}
|
||||
ghcr.io/${{ needs.prepare-docker-build.outputs.ghcr-repository }}
|
||||
name=paperlessngx/paperless-ngx,enable=${{ steps.docker-hub.outputs.enable }}
|
||||
tags: |
|
||||
# Tag branches with branch name
|
||||
type=ref,event=branch
|
||||
@ -358,173 +293,152 @@ jobs:
|
||||
# For a tag x.y.z or vX.Y.Z, output an x.y.z and x.y image tag
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
# If https://github.com/docker/buildx/issues/1044 is resolved,
|
||||
# the append input with a native arm64 arch could be used to
|
||||
# significantly speed up building
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: arm64
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
-
|
||||
name: Login to Github Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
# Don't attempt to login if not pushing to Docker Hub
|
||||
if: steps.push-other-places.outputs.enable == 'true'
|
||||
-
|
||||
name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
# Don't attempt to login is not pushing to Docker Hub
|
||||
if: steps.docker-hub.outputs.enable == 'true'
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Login to Quay.io
|
||||
uses: docker/login-action@v3
|
||||
# Don't attempt to login if not pushing to Quay.io
|
||||
if: steps.push-other-places.outputs.enable == 'true'
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.QUAY_USERNAME }}
|
||||
password: ${{ secrets.QUAY_ROBOT_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.docker-meta.outputs.tags }}
|
||||
labels: ${{ steps.docker-meta.outputs.labels }}
|
||||
build-args: |
|
||||
PNGX_TAG_VERSION=${{ steps.docker-meta.outputs.version }}
|
||||
# Get cache layers from this branch, then dev
|
||||
JBIG2ENC_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.jbig2enc-json).version }}
|
||||
QPDF_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.qpdf-json).version }}
|
||||
PIKEPDF_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.pikepdf-json).version }}
|
||||
PSYCOPG2_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.psycopg2-json).version }}
|
||||
# Get cache layers from this branch, then dev, then main
|
||||
# This allows new branches to get at least some cache benefits, generally from dev
|
||||
cache-from: |
|
||||
type=registry,ref=ghcr.io/${{ steps.set-ghcr-repository.outputs.ghcr-repository }}/builder/cache/app:${{ github.ref_name }}
|
||||
type=registry,ref=ghcr.io/${{ steps.set-ghcr-repository.outputs.ghcr-repository }}/builder/cache/app:dev
|
||||
type=registry,ref=ghcr.io/${{ needs.prepare-docker-build.outputs.ghcr-repository }}/builder/cache/app:${{ github.ref_name }}
|
||||
type=registry,ref=ghcr.io/${{ needs.prepare-docker-build.outputs.ghcr-repository }}/builder/cache/app:dev
|
||||
type=registry,ref=ghcr.io/${{ needs.prepare-docker-build.outputs.ghcr-repository }}/builder/cache/app:main
|
||||
cache-to: |
|
||||
type=registry,mode=max,ref=ghcr.io/${{ steps.set-ghcr-repository.outputs.ghcr-repository }}/builder/cache/app:${{ github.ref_name }}
|
||||
- name: Inspect image
|
||||
type=registry,mode=max,ref=ghcr.io/${{ needs.prepare-docker-build.outputs.ghcr-repository }}/builder/cache/app:${{ github.ref_name }}
|
||||
-
|
||||
name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ fromJSON(steps.docker-meta.outputs.json).tags[0] }}
|
||||
- name: Export frontend artifact from docker
|
||||
-
|
||||
name: Export frontend artifact from docker
|
||||
run: |
|
||||
docker create --name frontend-extract ${{ fromJSON(steps.docker-meta.outputs.json).tags[0] }}
|
||||
docker cp frontend-extract:/usr/src/paperless/src/documents/static/frontend src/documents/static/frontend/
|
||||
- name: Upload frontend artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
-
|
||||
name: Upload frontend artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: frontend-compiled
|
||||
path: src/documents/static/frontend/
|
||||
retention-days: 7
|
||||
|
||||
build-release:
|
||||
name: "Build Release"
|
||||
needs:
|
||||
- build-docker-image
|
||||
- documentation
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
id: setup-python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||
enable-cache: true
|
||||
python-version: ${{ steps.setup-python.outputs.python-version }}
|
||||
- name: Install Python dependencies
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Install pipenv
|
||||
run: |
|
||||
uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen
|
||||
- name: Install system dependencies
|
||||
pip3 install --upgrade pip setuptools wheel pipx
|
||||
pipx install pipenv
|
||||
-
|
||||
name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
cache: "pipenv"
|
||||
cache-dependency-path: 'Pipfile.lock'
|
||||
-
|
||||
name: Install Python dependencies
|
||||
run: |
|
||||
pipenv sync --dev
|
||||
-
|
||||
name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq --no-install-recommends gettext liblept5
|
||||
- name: Download frontend artifact
|
||||
uses: actions/download-artifact@v4
|
||||
-
|
||||
name: Download frontend artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: frontend-compiled
|
||||
path: src/documents/static/frontend/
|
||||
- name: Download documentation artifact
|
||||
uses: actions/download-artifact@v4
|
||||
-
|
||||
name: Download documentation artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: documentation
|
||||
path: docs/_build/html/
|
||||
- name: Generate requirements file
|
||||
-
|
||||
name: Generate requirements file
|
||||
run: |
|
||||
uv export --quiet --no-dev --all-extras --format requirements-txt --output-file requirements.txt
|
||||
- name: Compile messages
|
||||
pipenv requirements > requirements.txt
|
||||
-
|
||||
name: Compile messages
|
||||
run: |
|
||||
cd src/
|
||||
uv run \
|
||||
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||
manage.py compilemessages
|
||||
- name: Collect static files
|
||||
pipenv run python3 manage.py compilemessages
|
||||
-
|
||||
name: Collect static files
|
||||
run: |
|
||||
cd src/
|
||||
uv run \
|
||||
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||
manage.py collectstatic --no-input
|
||||
- name: Move files
|
||||
pipenv run python3 manage.py collectstatic --no-input
|
||||
-
|
||||
name: Move files
|
||||
run: |
|
||||
echo "Making dist folders"
|
||||
for directory in dist \
|
||||
dist/paperless-ngx \
|
||||
dist/paperless-ngx/scripts;
|
||||
do
|
||||
mkdir --verbose --parents ${directory}
|
||||
done
|
||||
|
||||
echo "Copying basic files"
|
||||
for file_name in .dockerignore \
|
||||
.env \
|
||||
Dockerfile \
|
||||
pyproject.toml \
|
||||
uv.lock \
|
||||
requirements.txt \
|
||||
LICENSE \
|
||||
README.md \
|
||||
paperless.conf.example
|
||||
do
|
||||
cp --verbose ${file_name} dist/paperless-ngx/
|
||||
done
|
||||
mv --verbose dist/paperless-ngx/paperless.conf.example dist/paperless-ngx/paperless.conf
|
||||
|
||||
echo "Copying Docker related files"
|
||||
cp --recursive docker/ dist/paperless-ngx/docker
|
||||
|
||||
echo "Copying startup scripts"
|
||||
cp --verbose scripts/*.service scripts/*.sh scripts/*.socket dist/paperless-ngx/scripts/
|
||||
|
||||
echo "Copying source files"
|
||||
cp --recursive src/ dist/paperless-ngx/src
|
||||
echo "Copying documentation"
|
||||
cp --recursive docs/_build/html/ dist/paperless-ngx/docs
|
||||
|
||||
mv --verbose static dist/paperless-ngx
|
||||
- name: Make release package
|
||||
mkdir dist
|
||||
mkdir dist/paperless-ngx
|
||||
mkdir dist/paperless-ngx/scripts
|
||||
cp .dockerignore .env Dockerfile Pipfile Pipfile.lock requirements.txt LICENSE README.md dist/paperless-ngx/
|
||||
cp paperless.conf.example dist/paperless-ngx/paperless.conf
|
||||
cp gunicorn.conf.py dist/paperless-ngx/gunicorn.conf.py
|
||||
cp -r docker/ dist/paperless-ngx/docker
|
||||
cp scripts/*.service scripts/*.sh dist/paperless-ngx/scripts/
|
||||
cp -r src/ dist/paperless-ngx/src
|
||||
cp -r docs/_build/html/ dist/paperless-ngx/docs
|
||||
mv static dist/paperless-ngx
|
||||
-
|
||||
name: Make release package
|
||||
run: |
|
||||
echo "Creating release archive"
|
||||
cd dist
|
||||
sudo chown -R 1000:1000 paperless-ngx/
|
||||
tar -cJf paperless-ngx.tar.xz paperless-ngx/
|
||||
- name: Upload release artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
-
|
||||
name: Upload release artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: release
|
||||
path: dist/paperless-ngx.tar.xz
|
||||
retention-days: 7
|
||||
|
||||
publish-release:
|
||||
name: "Publish Release"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
outputs:
|
||||
prerelease: ${{ steps.get_version.outputs.prerelease }}
|
||||
changelog: ${{ steps.create-release.outputs.body }}
|
||||
@ -533,12 +447,14 @@ jobs:
|
||||
- build-release
|
||||
if: github.ref_type == 'tag' && (startsWith(github.ref_name, 'v') || contains(github.ref_name, '-beta.rc'))
|
||||
steps:
|
||||
- name: Download release artifact
|
||||
uses: actions/download-artifact@v4
|
||||
-
|
||||
name: Download release artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: release
|
||||
path: ./
|
||||
- name: Get version
|
||||
-
|
||||
name: Get version
|
||||
id: get_version
|
||||
run: |
|
||||
echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT
|
||||
@ -547,9 +463,10 @@ jobs:
|
||||
else
|
||||
echo "prerelease=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Create Release and Changelog
|
||||
-
|
||||
name: Create Release and Changelog
|
||||
id: create-release
|
||||
uses: release-drafter/release-drafter@v6
|
||||
uses: paperless-ngx/release-drafter@master
|
||||
with:
|
||||
name: Paperless-ngx ${{ steps.get_version.outputs.version }}
|
||||
tag: ${{ steps.get_version.outputs.version }}
|
||||
@ -558,38 +475,43 @@ jobs:
|
||||
publish: true # ensures release is not marked as draft
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Upload release archive
|
||||
-
|
||||
name: Upload release archive
|
||||
id: upload-release-asset
|
||||
uses: shogo82148/actions-upload-release-asset@v1
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
upload_url: ${{ steps.create-release.outputs.upload_url }}
|
||||
asset_path: ./paperless-ngx.tar.xz
|
||||
asset_name: paperless-ngx-${{ steps.get_version.outputs.version }}.tar.xz
|
||||
asset_content_type: application/x-xz
|
||||
|
||||
append-changelog:
|
||||
name: "Append Changelog"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- publish-release
|
||||
if: needs.publish-release.outputs.prerelease == 'false'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: main
|
||||
- name: Set up Python
|
||||
id: setup-python
|
||||
uses: actions/setup-python@v5
|
||||
-
|
||||
name: Install pipenv
|
||||
run: |
|
||||
pip3 install --upgrade pip setuptools wheel pipx
|
||||
pipx install pipenv
|
||||
-
|
||||
name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||
enable-cache: true
|
||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||
- name: Append Changelog to docs
|
||||
python-version: 3.9
|
||||
cache: "pipenv"
|
||||
cache-dependency-path: 'Pipfile.lock'
|
||||
-
|
||||
name: Append Changelog to docs
|
||||
id: append-Changelog
|
||||
working-directory: docs
|
||||
run: |
|
||||
@ -597,27 +519,23 @@ jobs:
|
||||
git checkout ${{ needs.publish-release.outputs.version }}-changelog
|
||||
echo -e "# Changelog\n\n${{ needs.publish-release.outputs.changelog }}\n" > changelog-new.md
|
||||
echo "Manually linking usernames"
|
||||
sed -i -r 's|@([a-zA-Z0-9_]+) \(\[#|[@\1](https://github.com/\1) ([#|g' changelog-new.md
|
||||
echo "Removing unneeded comment tags"
|
||||
sed -i -r 's|@<!---->|@|g' changelog-new.md
|
||||
sed -i -r 's|@(.+?) \(\[#|[@\1](https://github.com/\1) ([#|ig' changelog-new.md
|
||||
CURRENT_CHANGELOG=`tail --lines +2 changelog.md`
|
||||
echo -e "$CURRENT_CHANGELOG" >> changelog-new.md
|
||||
mv changelog-new.md changelog.md
|
||||
uv run \
|
||||
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||
--dev \
|
||||
pre-commit run --files changelog.md || true
|
||||
pipenv run pre-commit --files changelog.md
|
||||
git config --global user.name "github-actions"
|
||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git commit -am "Changelog ${{ needs.publish-release.outputs.version }} - GHA"
|
||||
git push origin ${{ needs.publish-release.outputs.version }}-changelog
|
||||
- name: Create Pull Request
|
||||
uses: actions/github-script@v7
|
||||
-
|
||||
name: Create Pull Request
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const { repo, owner } = context.repo;
|
||||
const result = await github.rest.pulls.create({
|
||||
title: 'Documentation: Add ${{ needs.publish-release.outputs.version }} changelog',
|
||||
title: '[Documentation] Add ${{ needs.publish-release.outputs.version }} changelog',
|
||||
owner,
|
||||
repo,
|
||||
head: '${{ needs.publish-release.outputs.version }}-changelog',
|
||||
@ -628,5 +546,5 @@ jobs:
|
||||
owner,
|
||||
repo,
|
||||
issue_number: result.data.number,
|
||||
labels: ['documentation', 'skip-changelog']
|
||||
labels: ['documentation']
|
||||
});
|
||||
|
111
.github/workflows/cleanup-tags.yml
vendored
111
.github/workflows/cleanup-tags.yml
vendored
@ -1,64 +1,95 @@
|
||||
# This workflow runs on certain conditions to check for and potentially
|
||||
# delete container images from the GHCR which no longer have an associated
|
||||
# code branch.
|
||||
# Requires a PAT with the correct scope set in the secrets.
|
||||
#
|
||||
# This workflow will not trigger runs on forked repos.
|
||||
# Requires a PAT with the correct scope set in the secrets
|
||||
|
||||
name: Cleanup Image Tags
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * SAT'
|
||||
delete:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/cleanup-tags.yml"
|
||||
- ".github/scripts/cleanup-tags.py"
|
||||
- ".github/scripts/github.py"
|
||||
- ".github/scripts/common.py"
|
||||
|
||||
concurrency:
|
||||
group: registry-tags-cleanup
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
cleanup-images:
|
||||
name: Cleanup Image Tags for ${{ matrix.primary-name }}
|
||||
if: github.repository_owner == 'paperless-ngx'
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
primary-name: ["paperless-ngx", "paperless-ngx/builder/cache/app"]
|
||||
include:
|
||||
- primary-name: "paperless-ngx"
|
||||
cache-name: "paperless-ngx/builder/cache/app"
|
||||
|
||||
- primary-name: "paperless-ngx/builder/qpdf"
|
||||
cache-name: "paperless-ngx/builder/cache/qpdf"
|
||||
|
||||
- primary-name: "paperless-ngx/builder/pikepdf"
|
||||
cache-name: "paperless-ngx/builder/cache/pikepdf"
|
||||
|
||||
- primary-name: "paperless-ngx/builder/jbig2enc"
|
||||
cache-name: "paperless-ngx/builder/cache/jbig2enc"
|
||||
|
||||
- primary-name: "paperless-ngx/builder/psycopg2"
|
||||
cache-name: "paperless-ngx/builder/cache/psycopg2"
|
||||
env:
|
||||
# Requires a personal access token with the OAuth scope delete:packages
|
||||
TOKEN: ${{ secrets.GHA_CONTAINER_DELETE_TOKEN }}
|
||||
steps:
|
||||
- name: Clean temporary images
|
||||
if: "${{ env.TOKEN != '' }}"
|
||||
uses: stumpylog/image-cleaner-action/ephemeral@v0.10.0
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Login to Github Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
token: "${{ env.TOKEN }}"
|
||||
owner: "${{ github.repository_owner }}"
|
||||
is_org: "true"
|
||||
package_name: "${{ matrix.primary-name }}"
|
||||
scheme: "branch"
|
||||
repo_name: "paperless-ngx"
|
||||
match_regex: "(feature|fix)"
|
||||
do_delete: "true"
|
||||
cleanup-untagged-images:
|
||||
name: Cleanup Untagged Images Tags for ${{ matrix.primary-name }}
|
||||
if: github.repository_owner == 'paperless-ngx'
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- cleanup-images
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
primary-name: ["paperless-ngx", "paperless-ngx/builder/cache/app"]
|
||||
env:
|
||||
# Requires a personal access token with the OAuth scope delete:packages
|
||||
TOKEN: ${{ secrets.GHA_CONTAINER_DELETE_TOKEN }}
|
||||
steps:
|
||||
- name: Clean untagged images
|
||||
if: "${{ env.TOKEN != '' }}"
|
||||
uses: stumpylog/image-cleaner-action/untagged@v0.10.0
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
-
|
||||
name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
token: "${{ env.TOKEN }}"
|
||||
owner: "${{ github.repository_owner }}"
|
||||
is_org: "true"
|
||||
package_name: "${{ matrix.primary-name }}"
|
||||
do_delete: "true"
|
||||
python-version: "3.10"
|
||||
-
|
||||
name: Install httpx
|
||||
run: |
|
||||
python -m pip install httpx
|
||||
#
|
||||
# Clean up primary package
|
||||
#
|
||||
-
|
||||
name: Cleanup for package "${{ matrix.primary-name }}"
|
||||
if: "${{ env.TOKEN != '' }}"
|
||||
run: |
|
||||
python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --untagged --is-manifest --delete "${{ matrix.primary-name }}"
|
||||
#
|
||||
# Clean up registry cache package
|
||||
#
|
||||
-
|
||||
name: Cleanup for package "${{ matrix.cache-name }}"
|
||||
if: "${{ env.TOKEN != '' }}"
|
||||
run: |
|
||||
python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --untagged --delete "${{ matrix.cache-name }}"
|
||||
#
|
||||
# Verify tags which are left still pull
|
||||
#
|
||||
-
|
||||
name: Check all tags still pull
|
||||
run: |
|
||||
ghcr_name=$(echo "ghcr.io/${GITHUB_REPOSITORY_OWNER}/${{ matrix.primary-name }}" | awk '{ print tolower($0) }')
|
||||
echo "Pulling all tags of ${ghcr_name}"
|
||||
docker pull --quiet --all-tags ${ghcr_name}
|
||||
docker image list
|
||||
|
20
.github/workflows/codeql-analysis.yml
vendored
20
.github/workflows/codeql-analysis.yml
vendored
@ -10,39 +10,45 @@
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, dev]
|
||||
branches: [ main, dev ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [dev]
|
||||
branches: [ dev ]
|
||||
schedule:
|
||||
- cron: '28 13 * * 5'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: ['javascript', 'python']
|
||||
language: [ 'javascript', 'python' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
30
.github/workflows/crowdin.yml
vendored
30
.github/workflows/crowdin.yml
vendored
@ -1,30 +0,0 @@
|
||||
name: Crowdin Action
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '2 */12 * * *'
|
||||
push:
|
||||
paths: ['src/locale/**', 'src-ui/messages.xlf', 'src-ui/src/locale/**']
|
||||
branches: [dev]
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
name: Crowdin Sync
|
||||
if: github.repository_owner == 'paperless-ngx'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.PNGX_BOT_PAT }}
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
crowdin_branch_name: 'dev'
|
||||
localization_branch_name: l10n_dev
|
||||
pull_request_labels: 'skip-changelog, translation'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
170
.github/workflows/installer-library.yml
vendored
Normal file
170
.github/workflows/installer-library.yml
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
# This workflow will run to update the installer library of
|
||||
# Docker images. These are the images which provide updated wheels
|
||||
# .deb installation packages or maybe just some compiled library
|
||||
|
||||
name: Build Image Library
|
||||
|
||||
on:
|
||||
push:
|
||||
# Must match one of these branches AND one of the paths
|
||||
# to be triggered
|
||||
branches:
|
||||
- "main"
|
||||
- "dev"
|
||||
- "library-*"
|
||||
- "feature-*"
|
||||
paths:
|
||||
# Trigger the workflow if a Dockerfile changed
|
||||
- "docker-builders/**"
|
||||
# Trigger if a package was updated
|
||||
- ".build-config.json"
|
||||
- "Pipfile.lock"
|
||||
# Also trigger on workflow changes related to the library
|
||||
- ".github/workflows/installer-library.yml"
|
||||
- ".github/workflows/reusable-workflow-builder.yml"
|
||||
- ".github/scripts/**"
|
||||
|
||||
# Set a workflow level concurrency group so primary workflow
|
||||
# can wait for this to complete if needed
|
||||
# DO NOT CHANGE without updating main workflow group
|
||||
concurrency:
|
||||
group: build-installer-library
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
prepare-docker-build:
|
||||
name: Prepare Docker Image Version Data
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
-
|
||||
name: Set ghcr repository name
|
||||
id: set-ghcr-repository
|
||||
run: |
|
||||
ghcr_name=$(echo "${GITHUB_REPOSITORY}" | awk '{ print tolower($0) }')
|
||||
echo "repository=${ghcr_name}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
-
|
||||
name: Install jq
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install jq
|
||||
-
|
||||
name: Setup qpdf image
|
||||
id: qpdf-setup
|
||||
run: |
|
||||
build_json=$(python ${GITHUB_WORKSPACE}/.github/scripts/get-build-json.py qpdf)
|
||||
|
||||
echo ${build_json}
|
||||
|
||||
echo "qpdf-json=${build_json}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Setup psycopg2 image
|
||||
id: psycopg2-setup
|
||||
run: |
|
||||
build_json=$(python ${GITHUB_WORKSPACE}/.github/scripts/get-build-json.py psycopg2)
|
||||
|
||||
echo ${build_json}
|
||||
|
||||
echo "psycopg2-json=${build_json}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Setup pikepdf image
|
||||
id: pikepdf-setup
|
||||
run: |
|
||||
build_json=$(python ${GITHUB_WORKSPACE}/.github/scripts/get-build-json.py pikepdf)
|
||||
|
||||
echo ${build_json}
|
||||
|
||||
echo "pikepdf-json=${build_json}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Setup jbig2enc image
|
||||
id: jbig2enc-setup
|
||||
run: |
|
||||
build_json=$(python ${GITHUB_WORKSPACE}/.github/scripts/get-build-json.py jbig2enc)
|
||||
|
||||
echo ${build_json}
|
||||
|
||||
echo "jbig2enc-json=${build_json}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Setup other versions
|
||||
id: cache-bust-setup
|
||||
run: |
|
||||
pillow_version=$(jq ".default.pillow.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
|
||||
lxml_version=$(jq ".default.lxml.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
|
||||
|
||||
echo "Pillow is ${pillow_version}"
|
||||
echo "lxml is ${lxml_version}"
|
||||
|
||||
echo "pillow-version=${pillow_version}" >> $GITHUB_OUTPUT
|
||||
echo "lxml-version=${lxml_version}" >> $GITHUB_OUTPUT
|
||||
|
||||
outputs:
|
||||
|
||||
ghcr-repository: ${{ steps.set-ghcr-repository.outputs.repository }}
|
||||
|
||||
qpdf-json: ${{ steps.qpdf-setup.outputs.qpdf-json }}
|
||||
|
||||
pikepdf-json: ${{ steps.pikepdf-setup.outputs.pikepdf-json }}
|
||||
|
||||
psycopg2-json: ${{ steps.psycopg2-setup.outputs.psycopg2-json }}
|
||||
|
||||
jbig2enc-json: ${{ steps.jbig2enc-setup.outputs.jbig2enc-json }}
|
||||
|
||||
pillow-version: ${{ steps.cache-bust-setup.outputs.pillow-version }}
|
||||
|
||||
lxml-version: ${{ steps.cache-bust-setup.outputs.lxml-version }}
|
||||
|
||||
build-qpdf-debs:
|
||||
name: qpdf
|
||||
needs:
|
||||
- prepare-docker-build
|
||||
uses: ./.github/workflows/reusable-workflow-builder.yml
|
||||
with:
|
||||
dockerfile: ./docker-builders/Dockerfile.qpdf
|
||||
build-json: ${{ needs.prepare-docker-build.outputs.qpdf-json }}
|
||||
build-args: |
|
||||
QPDF_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.qpdf-json).version }}
|
||||
|
||||
build-jbig2enc:
|
||||
name: jbig2enc
|
||||
needs:
|
||||
- prepare-docker-build
|
||||
uses: ./.github/workflows/reusable-workflow-builder.yml
|
||||
with:
|
||||
dockerfile: ./docker-builders/Dockerfile.jbig2enc
|
||||
build-json: ${{ needs.prepare-docker-build.outputs.jbig2enc-json }}
|
||||
build-args: |
|
||||
JBIG2ENC_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.jbig2enc-json).version }}
|
||||
|
||||
build-psycopg2-wheel:
|
||||
name: psycopg2
|
||||
needs:
|
||||
- prepare-docker-build
|
||||
uses: ./.github/workflows/reusable-workflow-builder.yml
|
||||
with:
|
||||
dockerfile: ./docker-builders/Dockerfile.psycopg2
|
||||
build-json: ${{ needs.prepare-docker-build.outputs.psycopg2-json }}
|
||||
build-args: |
|
||||
PSYCOPG2_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.psycopg2-json).version }}
|
||||
|
||||
build-pikepdf-wheel:
|
||||
name: pikepdf
|
||||
needs:
|
||||
- prepare-docker-build
|
||||
- build-qpdf-debs
|
||||
uses: ./.github/workflows/reusable-workflow-builder.yml
|
||||
with:
|
||||
dockerfile: ./docker-builders/Dockerfile.pikepdf
|
||||
build-json: ${{ needs.prepare-docker-build.outputs.pikepdf-json }}
|
||||
build-args: |
|
||||
REPO=${{ needs.prepare-docker-build.outputs.ghcr-repository }}
|
||||
QPDF_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.qpdf-json).version }}
|
||||
PIKEPDF_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.pikepdf-json).version }}
|
||||
PILLOW_VERSION=${{ needs.prepare-docker-build.outputs.pillow-version }}
|
||||
LXML_VERSION=${{ needs.prepare-docker-build.outputs.lxml-version }}
|
112
.github/workflows/pr-bot.yml
vendored
112
.github/workflows/pr-bot.yml
vendored
@ -1,112 +0,0 @@
|
||||
name: PR Bot
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
jobs:
|
||||
pr-bot:
|
||||
name: Automated PR Bot
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Label PR by file path or branch name
|
||||
# see .github/labeler.yml for the labeler config
|
||||
uses: actions/labeler@v5
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Label by size
|
||||
uses: Gascon1/pr-size-labeler@v1.3.0
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
xs_label: 'small-change'
|
||||
xs_diff: '9'
|
||||
s_label: 'non-trivial'
|
||||
s_diff: '99999'
|
||||
fail_if_xl: 'false'
|
||||
excluded_files: /\.lock$/ /\.txt$/ ^src-ui/pnpm-lock\.yaml$ ^src-ui/messages\.xlf$ ^src/locale/en_US/LC_MESSAGES/django\.po$
|
||||
- name: Label by PR title
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const title = pr.title.toLowerCase();
|
||||
const labels = [];
|
||||
|
||||
if (/^(fix|bugfix)/i.test(title)) {
|
||||
labels.push('bug');
|
||||
} else if (/^feature/i.test(title)) {
|
||||
labels.push('enhancement');
|
||||
} else if (!/^(dependabot)/i.test(title)) {
|
||||
labels.push('enhancement'); // Default fallback
|
||||
}
|
||||
|
||||
if (labels.length) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
labels,
|
||||
});
|
||||
core.info(`Added labels based on title: ${labels.join(', ')}`);
|
||||
}
|
||||
- name: Label bot-generated PRs
|
||||
if: ${{ contains(github.actor, 'dependabot') || contains(github.actor, 'crowdin-bot') }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const user = pr.user.login.toLowerCase();
|
||||
const labels = [];
|
||||
|
||||
if (user.includes('dependabot')) {
|
||||
labels.push('dependencies');
|
||||
}
|
||||
|
||||
if (user.includes('crowdin-bot')) {
|
||||
labels.push('translation', 'skip-changelog');
|
||||
}
|
||||
|
||||
if (labels.length) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
labels,
|
||||
});
|
||||
}
|
||||
- name: Welcome comment
|
||||
if: ${{ !contains(github.actor, 'bot') }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const user = pr.user.login;
|
||||
|
||||
const { data: members } = await github.rest.orgs.listMembers({
|
||||
org: 'paperless-ngx',
|
||||
});
|
||||
|
||||
const memberLogins = members.map(m => m.login.toLowerCase());
|
||||
if (memberLogins.includes(user.toLowerCase())) {
|
||||
core.info('Skipping comment: user is org member');
|
||||
return;
|
||||
}
|
||||
|
||||
const body =
|
||||
"Hello @" + user + ",\n\n" +
|
||||
"Thank you very much for submitting this PR to us!\n\n" +
|
||||
"This is what will happen next:\n\n" +
|
||||
"1. CI tests will run against your PR to ensure quality and consistency.\n" +
|
||||
"2. Next, human contributors from paperless-ngx review your changes.\n" +
|
||||
"3. Please address any issues that come up during the review as soon as you are able to.\n" +
|
||||
"4. If accepted, your pull request will be merged into the `dev` branch and changes there will be tested further.\n" +
|
||||
"5. Eventually, changes from you and other contributors will be merged into `main` and a new release will be made.\n\n" +
|
||||
"You'll be hearing from us soon, and thank you again for contributing to our project.";
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: pr.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body,
|
||||
});
|
37
.github/workflows/project-actions.yml
vendored
37
.github/workflows/project-actions.yml
vendored
@ -1,5 +1,10 @@
|
||||
name: Project Automations
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
pull_request_target: #_target allows access to secrets
|
||||
types:
|
||||
- opened
|
||||
@ -7,18 +12,46 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
todo: Todo
|
||||
done: Done
|
||||
in_progress: In Progress
|
||||
|
||||
jobs:
|
||||
issue_opened_or_reopened:
|
||||
name: issue_opened_or_reopened
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'issues' && (github.event.action == 'opened' || github.event.action == 'reopened')
|
||||
steps:
|
||||
- name: Add issue to project and set status to ${{ env.todo }}
|
||||
uses: leonsteinhaeuser/project-beta-automations@v2.0.1
|
||||
with:
|
||||
gh_token: ${{ secrets.GH_TOKEN }}
|
||||
organization: paperless-ngx
|
||||
project_id: 2
|
||||
resource_node_id: ${{ github.event.issue.node_id }}
|
||||
status_value: ${{ env.todo }} # Target status
|
||||
pr_opened_or_reopened:
|
||||
name: pr_opened_or_reopened
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# write permission is required for autolabeler
|
||||
pull-requests: write
|
||||
if: github.event_name == 'pull_request_target' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request.user.login != 'dependabot'
|
||||
steps:
|
||||
- name: Add PR to project and set status to "Needs Review"
|
||||
uses: leonsteinhaeuser/project-beta-automations@v2.0.1
|
||||
with:
|
||||
gh_token: ${{ secrets.GH_TOKEN }}
|
||||
organization: paperless-ngx
|
||||
project_id: 2
|
||||
resource_node_id: ${{ github.event.pull_request.node_id }}
|
||||
status_value: "Needs Review" # Target status
|
||||
- name: Label PR with release-drafter
|
||||
uses: release-drafter/release-drafter@v6
|
||||
uses: release-drafter/release-drafter@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
283
.github/workflows/repo-maintenance.yml
vendored
283
.github/workflows/repo-maintenance.yml
vendored
@ -1,283 +0,0 @@
|
||||
name: 'Repository Maintenance'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 3 * * *'
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
concurrency:
|
||||
group: lock
|
||||
jobs:
|
||||
stale:
|
||||
name: 'Stale'
|
||||
if: github.repository_owner == 'paperless-ngx'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
days-before-stale: 7
|
||||
days-before-close: 14
|
||||
any-of-labels: 'stale,cant-reproduce,not a bug'
|
||||
stale-issue-label: stale
|
||||
stale-pr-label: stale
|
||||
stale-issue-message: >
|
||||
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
|
||||
|
||||
lock-threads:
|
||||
name: 'Lock Old Threads'
|
||||
if: github.repository_owner == 'paperless-ngx'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
issue-inactive-days: '30'
|
||||
pr-inactive-days: '30'
|
||||
discussion-inactive-days: '30'
|
||||
log-output: true
|
||||
issue-comment: >
|
||||
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new discussion or issue for related concerns. See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
|
||||
|
||||
pr-comment: >
|
||||
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new discussion or issue for related concerns. See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
|
||||
|
||||
discussion-comment: >
|
||||
This discussion has been automatically locked since there has not been any recent activity after it was closed. Please open a new discussion for related concerns. See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
|
||||
|
||||
close-answered-discussions:
|
||||
name: 'Close Answered Discussions'
|
||||
if: github.repository_owner == 'paperless-ngx'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const query = `query($owner:String!, $name:String!) {
|
||||
repository(owner:$owner, name:$name){
|
||||
discussions(first:100, answered:true, states:[OPEN]) {
|
||||
nodes {
|
||||
id,
|
||||
number
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
const variables = {
|
||||
owner: context.repo.owner,
|
||||
name: context.repo.repo,
|
||||
}
|
||||
const result = await github.graphql(query, variables)
|
||||
|
||||
console.log(`Found ${result.repository.discussions.nodes.length} open answered discussions`)
|
||||
|
||||
for (const discussion of result.repository.discussions.nodes) {
|
||||
console.log(`Closing discussion #${discussion.number} (${discussion.id})`)
|
||||
|
||||
const addCommentMutation = `mutation($discussion:ID!, $body:String!) {
|
||||
addDiscussionComment(input:{discussionId:$discussion, body:$body}) {
|
||||
clientMutationId
|
||||
}
|
||||
}`;
|
||||
const commentVariables = {
|
||||
discussion: discussion.id,
|
||||
body: 'This discussion has been automatically closed because it was marked as answered. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
|
||||
}
|
||||
await github.graphql(addCommentMutation, commentVariables)
|
||||
|
||||
const closeDiscussionMutation = `mutation($discussion:ID!, $reason:DiscussionCloseReason!) {
|
||||
closeDiscussion(input:{discussionId:$discussion, reason:$reason}) {
|
||||
clientMutationId
|
||||
}
|
||||
}`;
|
||||
const closeVariables = {
|
||||
discussion: discussion.id,
|
||||
reason: "RESOLVED",
|
||||
}
|
||||
await github.graphql(closeDiscussionMutation, closeVariables)
|
||||
|
||||
await sleep(1000)
|
||||
}
|
||||
close-outdated-discussions:
|
||||
name: 'Close Outdated Discussions'
|
||||
if: github.repository_owner == 'paperless-ngx'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const CUTOFF_DAYS = 180;
|
||||
const cutoff = new Date();
|
||||
cutoff.setDate(cutoff.getDate() - CUTOFF_DAYS);
|
||||
|
||||
const query = `query(
|
||||
$owner:String!,
|
||||
$name:String!,
|
||||
$supportCategory:ID!,
|
||||
$generalCategory:ID!,
|
||||
) {
|
||||
supportDiscussions: repository(owner:$owner, name:$name){
|
||||
discussions(
|
||||
categoryId:$supportCategory,
|
||||
last:50,
|
||||
answered:false,
|
||||
states:[OPEN],
|
||||
) {
|
||||
nodes {
|
||||
id,
|
||||
number,
|
||||
updatedAt
|
||||
}
|
||||
},
|
||||
},
|
||||
generalDiscussions: repository(owner:$owner, name:$name){
|
||||
discussions(
|
||||
categoryId:$generalCategory,
|
||||
last:50,
|
||||
states:[OPEN],
|
||||
) {
|
||||
nodes {
|
||||
id,
|
||||
number,
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
const variables = {
|
||||
owner: context.repo.owner,
|
||||
name: context.repo.repo,
|
||||
supportCategory: "DIC_kwDOG1Zs184CBKWK",
|
||||
generalCategory: "DIC_kwDOG1Zs184CBKWJ"
|
||||
}
|
||||
const result = await github.graphql(query, variables);
|
||||
const combinedDiscussions = [
|
||||
...result.supportDiscussions.discussions.nodes,
|
||||
...result.generalDiscussions.discussions.nodes,
|
||||
]
|
||||
|
||||
console.log(`Checking ${combinedDiscussions.length} open discussions`);
|
||||
|
||||
for (const discussion of combinedDiscussions) {
|
||||
if (new Date(discussion.updatedAt) < cutoff) {
|
||||
console.log(`Closing outdated discussion #${discussion.number} (${discussion.id}), last updated at ${discussion.updatedAt}`);
|
||||
const addCommentMutation = `mutation($discussion:ID!, $body:String!) {
|
||||
addDiscussionComment(input:{discussionId:$discussion, body:$body}) {
|
||||
clientMutationId
|
||||
}
|
||||
}`;
|
||||
const commentVariables = {
|
||||
discussion: discussion.id,
|
||||
body: 'This discussion has been automatically closed due to inactivity. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
|
||||
}
|
||||
await github.graphql(addCommentMutation, commentVariables);
|
||||
|
||||
const closeDiscussionMutation = `mutation($discussion:ID!, $reason:DiscussionCloseReason!) {
|
||||
closeDiscussion(input:{discussionId:$discussion, reason:$reason}) {
|
||||
clientMutationId
|
||||
}
|
||||
}`;
|
||||
const closeVariables = {
|
||||
discussion: discussion.id,
|
||||
reason: "OUTDATED",
|
||||
}
|
||||
await github.graphql(closeDiscussionMutation, closeVariables);
|
||||
|
||||
await sleep(1000);
|
||||
}
|
||||
}
|
||||
close-unsupported-feature-requests:
|
||||
name: 'Close Unsupported Feature Requests'
|
||||
if: github.repository_owner == 'paperless-ngx'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const CUTOFF_MAX_COUNT = 80;
|
||||
const CUTOFF_1_DAYS = 180;
|
||||
const CUTOFF_1_COUNT = 5;
|
||||
const CUTOFF_2_DAYS = 365;
|
||||
const CUTOFF_2_COUNT = 20;
|
||||
const CUTOFF_3_DAYS = 730;
|
||||
const CUTOFF_3_COUNT = 40;
|
||||
|
||||
const cutoff1Date = new Date();
|
||||
cutoff1Date.setDate(cutoff1Date.getDate() - CUTOFF_1_DAYS);
|
||||
const cutoff2Date = new Date();
|
||||
cutoff2Date.setDate(cutoff2Date.getDate() - CUTOFF_2_DAYS);
|
||||
const cutoff3Date = new Date();
|
||||
cutoff3Date.setDate(cutoff3Date.getDate() - CUTOFF_3_DAYS);
|
||||
|
||||
const query = `query(
|
||||
$owner:String!,
|
||||
$name:String!,
|
||||
$featureRequestsCategory:ID!,
|
||||
) {
|
||||
repository(owner:$owner, name:$name){
|
||||
discussions(
|
||||
categoryId:$featureRequestsCategory,
|
||||
last:100,
|
||||
states:[OPEN],
|
||||
) {
|
||||
nodes {
|
||||
id,
|
||||
number,
|
||||
updatedAt,
|
||||
upvoteCount,
|
||||
}
|
||||
},
|
||||
}
|
||||
}`;
|
||||
const variables = {
|
||||
owner: context.repo.owner,
|
||||
name: context.repo.repo,
|
||||
featureRequestsCategory: "DIC_kwDOG1Zs184CBNr4"
|
||||
}
|
||||
const result = await github.graphql(query, variables);
|
||||
|
||||
for (const discussion of result.repository.discussions.nodes) {
|
||||
const discussionUpdatedDate = new Date(discussion.updatedAt);
|
||||
const discussionCreatedDate = new Date(discussion.createdAt);
|
||||
if ((discussionUpdatedDate < cutoff1Date && discussion.upvoteCount < CUTOFF_MAX_COUNT) ||
|
||||
(discussionCreatedDate < cutoff1Date && discussion.upvoteCount < CUTOFF_1_COUNT) ||
|
||||
(discussionCreatedDate < cutoff2Date && discussion.upvoteCount < CUTOFF_2_COUNT) ||
|
||||
(discussionCreatedDate < cutoff3Date && discussion.upvoteCount < CUTOFF_3_COUNT)) {
|
||||
console.log(`Closing discussion #${discussion.number} (${discussion.id}), last updated at ${discussion.updatedAt} with votes ${discussion.upvoteCount}`);
|
||||
const addCommentMutation = `mutation($discussion:ID!, $body:String!) {
|
||||
addDiscussionComment(input:{discussionId:$discussion, body:$body}) {
|
||||
clientMutationId
|
||||
}
|
||||
}`;
|
||||
const commentVariables = {
|
||||
discussion: discussion.id,
|
||||
body: 'This discussion has been automatically closed due to lack of community support. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
|
||||
}
|
||||
await github.graphql(addCommentMutation, commentVariables);
|
||||
|
||||
const closeDiscussionMutation = `mutation($discussion:ID!, $reason:DiscussionCloseReason!) {
|
||||
closeDiscussion(input:{discussionId:$discussion, reason:$reason}) {
|
||||
clientMutationId
|
||||
}
|
||||
}`;
|
||||
const closeVariables = {
|
||||
discussion: discussion.id,
|
||||
reason: "OUTDATED",
|
||||
}
|
||||
await github.graphql(closeDiscussionMutation, closeVariables);
|
||||
|
||||
await sleep(1000);
|
||||
}
|
||||
}
|
53
.github/workflows/reusable-workflow-builder.yml
vendored
Normal file
53
.github/workflows/reusable-workflow-builder.yml
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
name: Reusable Image Builder
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
dockerfile:
|
||||
required: true
|
||||
type: string
|
||||
build-json:
|
||||
required: true
|
||||
type: string
|
||||
build-args:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ fromJSON(inputs.build-json).name }}-${{ fromJSON(inputs.build-json).version }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build-image:
|
||||
name: Build ${{ fromJSON(inputs.build-json).name }} @ ${{ fromJSON(inputs.build-json).version }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Login to Github Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
-
|
||||
name: Build ${{ fromJSON(inputs.build-json).name }}
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ${{ inputs.dockerfile }}
|
||||
tags: ${{ fromJSON(inputs.build-json).image_tag }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
build-args: ${{ inputs.build-args }}
|
||||
push: true
|
||||
cache-from: type=registry,ref=${{ fromJSON(inputs.build-json).cache_tag }}
|
||||
cache-to: type=registry,mode=max,ref=${{ fromJSON(inputs.build-json).cache_tag }}
|
69
.github/workflows/translate-strings.yml
vendored
69
.github/workflows/translate-strings.yml
vendored
@ -1,69 +0,0 @@
|
||||
name: Generate Translation Strings
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
jobs:
|
||||
generate-translate-strings:
|
||||
name: Generate Translation Strings
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.PNGX_BOT_PAT }}
|
||||
ref: ${{ github.head_ref }}
|
||||
- name: Set up Python
|
||||
id: setup-python
|
||||
uses: actions/setup-python@v5
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq --no-install-recommends gettext
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
enable-cache: true
|
||||
- name: Install backend python dependencies
|
||||
run: |
|
||||
uv sync \
|
||||
--group dev \
|
||||
--frozen
|
||||
- name: Generate backend translation strings
|
||||
run: cd src/ && uv run manage.py makemessages -l en_US -i "samples*"
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||
- name: Cache frontend dependencies
|
||||
id: cache-frontend-deps
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.pnpm-store
|
||||
~/.cache
|
||||
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
|
||||
- name: Install frontend dependencies
|
||||
if: steps.cache-frontend-deps.outputs.cache-hit != 'true'
|
||||
run: cd src-ui && pnpm install
|
||||
- name: Re-link Angular cli
|
||||
run: cd src-ui && pnpm link @angular/cli
|
||||
- name: Generate frontend translation strings
|
||||
run: |
|
||||
cd src-ui
|
||||
pnpm run ng extract-i18n
|
||||
- name: Commit changes
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
file_pattern: 'src-ui/messages.xlf src/locale/en_US/LC_MESSAGES/django.po'
|
||||
commit_message: "Auto translate strings"
|
||||
commit_user_name: "GitHub Actions"
|
||||
commit_author: "GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com>"
|
15
.gitignore
vendored
15
.gitignore
vendored
@ -22,7 +22,6 @@ var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
/src/paperless_mail/templates/node_modules
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
@ -44,7 +43,6 @@ nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.pytest_cache
|
||||
junit.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
@ -53,8 +51,8 @@ junit.xml
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# MkDocs documentation
|
||||
site/
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
@ -67,8 +65,6 @@ target/
|
||||
.vscode
|
||||
/src-ui/.vscode
|
||||
/docs/.vscode
|
||||
.vscode-server
|
||||
*CommandMarker
|
||||
|
||||
# Other stuff that doesn't belong
|
||||
.virtualenv
|
||||
@ -77,7 +73,6 @@ virtualenv
|
||||
.venv/
|
||||
/docker-compose.env
|
||||
/docker-compose.yml
|
||||
.ruff_cache/
|
||||
|
||||
# Used for development
|
||||
scripts/import-for-development
|
||||
@ -101,9 +96,3 @@ scripts/nuke
|
||||
|
||||
# celery schedule file
|
||||
celerybeat-schedule*
|
||||
|
||||
# ignore .devcontainer sub folders
|
||||
/.devcontainer/consume/
|
||||
/.devcontainer/data/
|
||||
/.devcontainer/media/
|
||||
/.devcontainer/redisdata/
|
||||
|
@ -5,14 +5,12 @@
|
||||
repos:
|
||||
# General hooks
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
rev: v4.3.0
|
||||
hooks:
|
||||
- id: check-docstring-first
|
||||
- id: check-json
|
||||
exclude: "tsconfig.*json"
|
||||
- id: check-yaml
|
||||
args:
|
||||
- "--unsafe"
|
||||
- id: check-toml
|
||||
- id: check-executables-have-shebangs
|
||||
- id: end-of-file-fixer
|
||||
@ -28,39 +26,52 @@ repos:
|
||||
- svg
|
||||
- id: check-case-conflict
|
||||
- id: detect-private-key
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.4.0
|
||||
hooks:
|
||||
- id: codespell
|
||||
exclude: "(^src-ui/src/locale/)|(^src-ui/pnpm-lock.yaml)|(^src-ui/e2e/)|(^src/paperless_mail/tests/samples/)"
|
||||
exclude_types:
|
||||
- pofile
|
||||
- json
|
||||
# See https://github.com/prettier/prettier/issues/15742 for the fork reason
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: 'v3.3.3'
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: "v2.7.1"
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or:
|
||||
- javascript
|
||||
- ts
|
||||
- markdown
|
||||
additional_dependencies:
|
||||
- prettier@3.3.3
|
||||
- 'prettier-plugin-organize-imports@4.1.0'
|
||||
exclude: "(^Pipfile\\.lock$)"
|
||||
# Python hooks
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.9.9
|
||||
- repo: https://github.com/asottile/reorder_python_imports
|
||||
rev: v3.9.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: "v2.5.1"
|
||||
- id: reorder-python-imports
|
||||
exclude: "(migrations)"
|
||||
- repo: https://github.com/asottile/yesqa
|
||||
rev: "v1.4.0"
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
- id: yesqa
|
||||
exclude: "(migrations)"
|
||||
- repo: https://github.com/asottile/add-trailing-comma
|
||||
rev: "v2.3.0"
|
||||
hooks:
|
||||
- id: add-trailing-comma
|
||||
exclude: "(migrations)"
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 5.0.4
|
||||
hooks:
|
||||
- id: flake8
|
||||
files: ^src/
|
||||
args:
|
||||
- "--config=./src/setup.cfg"
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.10.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.2.2
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
exclude: "(migrations)"
|
||||
args:
|
||||
- "--py38-plus"
|
||||
# Dockerfile hooks
|
||||
- repo: https://github.com/AleksaC/hadolint-py
|
||||
rev: v2.12.0.3
|
||||
rev: v2.10.0
|
||||
hooks:
|
||||
- id: hadolint
|
||||
# Shell script hooks
|
||||
@ -68,16 +79,9 @@ repos:
|
||||
rev: v6.2.1
|
||||
hooks:
|
||||
- id: beautysh
|
||||
additional_dependencies:
|
||||
- setuptools
|
||||
args:
|
||||
- "--tab"
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: "v0.10.0.1"
|
||||
rev: "v0.8.0.4"
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
- repo: https://github.com/google/yamlfmt
|
||||
rev: v0.14.0
|
||||
hooks:
|
||||
- id: yamlfmt
|
||||
exclude: "^src-ui/pnpm-lock.yaml"
|
||||
|
4
.prettierrc
Normal file
4
.prettierrc
Normal file
@ -0,0 +1,4 @@
|
||||
# https://prettier.io/docs/en/options.html#semicolons
|
||||
semi: false
|
||||
# https://prettier.io/docs/en/options.html#quotes
|
||||
singleQuote: true
|
@ -1,19 +0,0 @@
|
||||
const config = {
|
||||
// https://prettier.io/docs/en/options.html#semicolons
|
||||
semi: false,
|
||||
// https://prettier.io/docs/en/options.html#quotes
|
||||
singleQuote: true,
|
||||
// https://prettier.io/docs/en/options.html#trailing-commas
|
||||
trailingComma: 'es5',
|
||||
overrides: [
|
||||
{
|
||||
files: ['docs/*.md'],
|
||||
options: {
|
||||
tabWidth: 4,
|
||||
},
|
||||
},
|
||||
],
|
||||
plugins: [require('prettier-plugin-organize-imports')],
|
||||
}
|
||||
|
||||
module.exports = config
|
16
.readthedocs.yml
Normal file
16
.readthedocs.yml
Normal file
@ -0,0 +1,16 @@
|
||||
# .readthedocs.yml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
# Optionally set the version of Python and requirements required to build your docs
|
||||
python:
|
||||
version: "3.8"
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
@ -5,6 +5,5 @@
|
||||
/src-ui/ @paperless-ngx/frontend
|
||||
|
||||
/src/ @paperless-ngx/backend
|
||||
pyproject.toml @paperless-ngx/backend
|
||||
uv.lock @paperless-ngx/backend
|
||||
Pipfile* @paperless-ngx/backend
|
||||
*.py @paperless-ngx/backend
|
||||
|
@ -5,7 +5,7 @@
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socioeconomic status,
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
|
@ -11,7 +11,7 @@ If you want to implement something big:
|
||||
|
||||
## Python
|
||||
|
||||
Paperless supports python 3.10 - 3.12 at this time. We format Python code with [ruff](https://docs.astral.sh/ruff/formatter/).
|
||||
Paperless supports python 3.8 and 3.9. We format Python code with [Black](https://github.com/psf/black).
|
||||
|
||||
## Branches
|
||||
|
||||
@ -27,11 +27,11 @@ Please format and test your code! I know it's a hassle, but it makes sure that y
|
||||
|
||||
To test your code, execute `pytest` in the src/ directory. This also generates a html coverage report, which you can use to see if you missed anything important during testing.
|
||||
|
||||
Before you can run `pytest`, ensure to [properly set up your local environment](https://docs.paperless-ngx.com/development/#initial-setup-and-first-start).
|
||||
Before you can run `pytest`, ensure to [properly set up your local environment](https://paperless-ngx.readthedocs.io/en/latest/extending.html#initial-setup-and-first-start).
|
||||
|
||||
## More info:
|
||||
|
||||
... is available [in the documentation](https://docs.paperless-ngx.com/development).
|
||||
... is available in the documentation. https://paperless-ngx.readthedocs.io/en/latest/extending.html
|
||||
|
||||
# Merging PRs
|
||||
|
||||
@ -45,7 +45,7 @@ Examples of `non-trivial` PRs might include:
|
||||
|
||||
- Additional features
|
||||
- Large changes to many distinct files
|
||||
- Breaking or deprecation of existing features
|
||||
- Breaking or depreciation of existing features
|
||||
|
||||
Our community review process for `non-trivial` PRs is the following:
|
||||
|
||||
@ -58,13 +58,6 @@ Our community review process for `non-trivial` PRs is the following:
|
||||
|
||||
This process might be slow as community members have different schedules and time to dedicate to the Paperless project. However it ensures community code reviews are as brilliantly thorough as they once were with @jonaswinkler.
|
||||
|
||||
# AI-Generated Code
|
||||
|
||||
This project does not specifically prohibit the use of AI-generated code _during the process_ of creating a PR, however:
|
||||
|
||||
1. Any code present in the final PR that was generated using AI sources should be clearly attributed as such and must not violate copyright protections.
|
||||
2. We will not accept PRs that are entirely or mostly AI-derived.
|
||||
|
||||
# Translating Paperless-ngx
|
||||
|
||||
Some notes about translation:
|
||||
@ -81,7 +74,7 @@ Some notes about translation:
|
||||
|
||||
If a language has already been added, and you would like to contribute new translations or change existing translations, please read the "Translation" section in the README.md file for further details on that.
|
||||
|
||||
If you would like the project to be translated to another language, first head over to https://crowdin.com/project/paperless-ngx to check if that language has already been enabled for translation.
|
||||
If you would like the project to be translated to another language, first head over to https://crwd.in/paperless-ngx to check if that language has already been enabled for translation.
|
||||
If not, please request the language to be added by creating an issue on GitHub. The issue should contain:
|
||||
|
||||
- English name of the language (the localized name can be added on Crowdin).
|
||||
@ -94,7 +87,7 @@ The following files need to be changed:
|
||||
|
||||
- src-ui/angular.json (under the _projects/paperless-ui/i18n/locales_ JSON key)
|
||||
- src/paperless/settings.py (in the _LANGUAGES_ array)
|
||||
- src-ui/src/app/services/settings.service.ts (inside the _LANGUAGE_OPTIONS_ array)
|
||||
- src-ui/src/app/services/settings.service.ts (inside the _getLanguageOptions_ method)
|
||||
- src-ui/src/app/app.module.ts (import locale from _angular/common/locales_ and call _registerLocaleData_)
|
||||
|
||||
Please add the language in the correct order, alphabetically by locale.
|
||||
@ -137,19 +130,3 @@ All team members are notified when mentioned or assigned to a relevant issue or
|
||||
We are not overly strict with inviting people to the organization. If you have read the [team permissions](#permissions) and think having additional access would enhance your contributions, please reach out to an [admin](#structure) of the team.
|
||||
|
||||
The admins occasionally invite contributors directly if we believe having them on a team will accelerate their work.
|
||||
|
||||
# Automatic Repository Maintenance
|
||||
|
||||
The Paperless-ngx team appreciates all effort and interest from the community in filing bug reports, creating feature requests, sharing ideas and helping other
|
||||
community members. That said, in an effort to keep the repository organized and managebale the project uses automatic handling of certain areas:
|
||||
|
||||
- Issues that cannot be reproduced will be marked 'stale' after 7 days of inactivity and closed after 14 further days of inactivity.
|
||||
- Issues, pull requests and discussions that are closed will be locked after 30 days of inactivity.
|
||||
- Discussions with a marked answer will be automatically closed.
|
||||
- Discussions in the 'General' or 'Support' categories will be closed after 180 days of inactivity.
|
||||
- Feature requests that do not meet the following thresholds will be closed: 180 days of inactivity, < 5 "up-votes" after 180 days, < 20 "up-votes" after 1 year or < 80 "up-votes" at 2 years.
|
||||
|
||||
In all cases, threads can be re-opened by project maintainers and, of course, users can always create a new discussion for related concerns.
|
||||
Finally, remember that all information remains searchable and 'closed' feature requests can still serve as inspiration for new features.
|
||||
|
||||
Thank you all for your contributions.
|
||||
|
329
Dockerfile
329
Dockerfile
@ -1,144 +1,108 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md
|
||||
# syntax=docker/dockerfile:1.4
|
||||
|
||||
# Stage: compile-frontend
|
||||
# Purpose: Compiles the frontend
|
||||
# Notes:
|
||||
# - Does PNPM stuff with Typescript and such
|
||||
FROM --platform=$BUILDPLATFORM docker.io/node:20-bookworm-slim AS compile-frontend
|
||||
# Pull the installer images from the library
|
||||
# These are all built previously
|
||||
# They provide either a .deb or .whl
|
||||
|
||||
ARG JBIG2ENC_VERSION
|
||||
ARG QPDF_VERSION
|
||||
ARG PIKEPDF_VERSION
|
||||
ARG PSYCOPG2_VERSION
|
||||
|
||||
FROM ghcr.io/paperless-ngx/paperless-ngx/builder/jbig2enc:${JBIG2ENC_VERSION} as jbig2enc-builder
|
||||
FROM ghcr.io/paperless-ngx/paperless-ngx/builder/qpdf:${QPDF_VERSION} as qpdf-builder
|
||||
FROM ghcr.io/paperless-ngx/paperless-ngx/builder/pikepdf:${PIKEPDF_VERSION} as pikepdf-builder
|
||||
FROM ghcr.io/paperless-ngx/paperless-ngx/builder/psycopg2:${PSYCOPG2_VERSION} as psycopg2-builder
|
||||
|
||||
FROM --platform=$BUILDPLATFORM node:16-bullseye-slim AS compile-frontend
|
||||
|
||||
# This stage compiles the frontend
|
||||
# This stage runs once for the native platform, as the outputs are not
|
||||
# dependent on target arch
|
||||
# Inputs: None
|
||||
|
||||
COPY ./src-ui /src/src-ui
|
||||
|
||||
WORKDIR /src/src-ui
|
||||
RUN set -eux \
|
||||
&& npm update -g pnpm \
|
||||
&& npm install -g corepack@latest \
|
||||
&& corepack enable \
|
||||
&& pnpm install
|
||||
|
||||
ARG PNGX_TAG_VERSION=
|
||||
# Add the tag to the environment file if its a tagged dev build
|
||||
RUN set -eux && \
|
||||
case "${PNGX_TAG_VERSION}" in \
|
||||
dev|beta|fix*|feature*) \
|
||||
sed -i -E "s/tag: '([a-z\.]+)'/tag: '${PNGX_TAG_VERSION}'/g" /src/src-ui/src/environments/environment.prod.ts \
|
||||
;; \
|
||||
esac
|
||||
|
||||
&& npm update npm -g \
|
||||
&& npm ci --omit=optional
|
||||
RUN set -eux \
|
||||
&& ./node_modules/.bin/ng build --configuration production
|
||||
|
||||
# Stage: s6-overlay-base
|
||||
# Purpose: Installs s6-overlay and rootfs
|
||||
# Comments:
|
||||
# - Don't leave anything extra in here either
|
||||
FROM ghcr.io/astral-sh/uv:0.7.9-python3.12-bookworm-slim AS s6-overlay-base
|
||||
FROM --platform=$BUILDPLATFORM python:3.9-slim-bullseye as pipenv-base
|
||||
|
||||
WORKDIR /usr/src/s6
|
||||
# This stage generates the requirements.txt file using pipenv
|
||||
# This stage runs once for the native platform, as the outputs are not
|
||||
# dependent on target arch
|
||||
# This way, pipenv dependencies are not left in the final image
|
||||
# nor can pipenv mess up the final image somehow
|
||||
# Inputs: None
|
||||
|
||||
# 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
|
||||
WORKDIR /usr/src/pipenv
|
||||
|
||||
# Buildx provided, must be defined to use though
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
# Lock this version
|
||||
ARG S6_OVERLAY_VERSION=3.2.1.0
|
||||
|
||||
ARG S6_BUILD_TIME_PKGS="curl \
|
||||
xz-utils"
|
||||
COPY Pipfile* ./
|
||||
|
||||
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/*
|
||||
&& echo "Installing pipenv" \
|
||||
&& python3 -m pip install --no-cache-dir --upgrade pipenv \
|
||||
&& echo "Generating requirement.txt" \
|
||||
&& pipenv requirements > requirements.txt
|
||||
|
||||
# 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 s6-overlay-base AS main-app
|
||||
FROM python:3.9-slim-bullseye as main-app
|
||||
|
||||
LABEL org.opencontainers.image.authors="paperless-ngx team <hello@paperless-ngx.com>"
|
||||
LABEL org.opencontainers.image.documentation="https://docs.paperless-ngx.com/"
|
||||
LABEL org.opencontainers.image.documentation="https://paperless-ngx.readthedocs.io/en/latest/"
|
||||
LABEL org.opencontainers.image.source="https://github.com/paperless-ngx/paperless-ngx"
|
||||
LABEL org.opencontainers.image.url="https://github.com/paperless-ngx/paperless-ngx"
|
||||
LABEL org.opencontainers.image.licenses="GPL-3.0-only"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Buildx provided, must be defined to use though
|
||||
ARG TARGETARCH
|
||||
|
||||
# Can be workflow provided, defaults set for manual building
|
||||
ARG JBIG2ENC_VERSION=0.30
|
||||
ARG QPDF_VERSION=11.9.0
|
||||
ARG GS_VERSION=10.03.1
|
||||
|
||||
# Set Python environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
# Ignore warning from Whitenoise about async iterators
|
||||
PYTHONWARNINGS="ignore:::django.http.response:517" \
|
||||
PNGX_CONTAINERIZED=1 \
|
||||
# https://docs.astral.sh/uv/reference/settings/#link-mode
|
||||
UV_LINK_MODE=copy \
|
||||
UV_CACHE_DIR=/cache/uv/
|
||||
|
||||
#
|
||||
# Begin installation and configuration
|
||||
# Order the steps below from least often changed to most
|
||||
#
|
||||
|
||||
# copy jbig2enc
|
||||
# Basically will never change again
|
||||
COPY --from=jbig2enc-builder /usr/src/jbig2enc/src/.libs/libjbig2enc* /usr/local/lib/
|
||||
COPY --from=jbig2enc-builder /usr/src/jbig2enc/src/jbig2 /usr/local/bin/
|
||||
COPY --from=jbig2enc-builder /usr/src/jbig2enc/src/*.h /usr/local/include/
|
||||
|
||||
# Packages need for running
|
||||
ARG RUNTIME_PACKAGES="\
|
||||
# General utils
|
||||
curl \
|
||||
# Docker specific
|
||||
gosu \
|
||||
# Timezones support
|
||||
tzdata \
|
||||
file \
|
||||
# fonts for text file thumbnail generation
|
||||
fonts-liberation \
|
||||
gettext \
|
||||
ghostscript \
|
||||
gnupg \
|
||||
gosu \
|
||||
icc-profiles-free \
|
||||
imagemagick \
|
||||
# PostgreSQL
|
||||
media-types \
|
||||
liblept5 \
|
||||
libpq5 \
|
||||
libxml2 \
|
||||
liblcms2-2 \
|
||||
libtiff5 \
|
||||
libxslt1.1 \
|
||||
libfreetype6 \
|
||||
libwebp6 \
|
||||
libopenjp2-7 \
|
||||
libimagequant0 \
|
||||
libraqm0 \
|
||||
libgnutls30 \
|
||||
libjpeg62-turbo \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-setuptools \
|
||||
postgresql-client \
|
||||
# MySQL / MariaDB
|
||||
mariadb-client \
|
||||
# For Numpy
|
||||
libatlas3-base \
|
||||
# OCRmyPDF dependencies
|
||||
tesseract-ocr \
|
||||
tesseract-ocr-eng \
|
||||
@ -146,18 +110,13 @@ ARG RUNTIME_PACKAGES="\
|
||||
tesseract-ocr-fra \
|
||||
tesseract-ocr-ita \
|
||||
tesseract-ocr-spa \
|
||||
unpaper \
|
||||
# Suggested for OCRmyPDF
|
||||
pngquant \
|
||||
# Suggested for pikepdf
|
||||
jbig2dec \
|
||||
# lxml
|
||||
libxml2 \
|
||||
libxslt1.1 \
|
||||
# itself
|
||||
qpdf \
|
||||
tzdata \
|
||||
unpaper \
|
||||
# Mime type detection
|
||||
file \
|
||||
libmagic1 \
|
||||
media-types \
|
||||
zlib1g \
|
||||
# Barcode splitter
|
||||
libzbar0 \
|
||||
@ -169,100 +128,128 @@ RUN set -eux \
|
||||
echo "Installing system packages" \
|
||||
&& apt-get update \
|
||||
&& apt-get install --yes --quiet --no-install-recommends ${RUNTIME_PACKAGES} \
|
||||
&& echo "Installing pre-built updates" \
|
||||
&& curl --fail --silent --no-progress-meter --show-error --location --remote-name-all --parallel --parallel-max 4 \
|
||||
https://github.com/paperless-ngx/builder/releases/download/qpdf-${QPDF_VERSION}/libqpdf29_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/qpdf-${QPDF_VERSION}/qpdf_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/libgs10_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/ghostscript_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/libgs10-common_${GS_VERSION}.dfsg-1_all.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/jbig2enc-${JBIG2ENC_VERSION}/jbig2enc_${JBIG2ENC_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& echo "Installing qpdf ${QPDF_VERSION}" \
|
||||
&& dpkg --install ./libqpdf29_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& dpkg --install ./qpdf_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& echo "Installing Ghostscript ${GS_VERSION}" \
|
||||
&& dpkg --install ./libgs10-common_${GS_VERSION}.dfsg-1_all.deb \
|
||||
&& dpkg --install ./libgs10_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
|
||||
&& 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/*
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& echo "Installing supervisor" \
|
||||
&& python3 -m pip install --default-timeout=1000 --upgrade --no-cache-dir supervisor==4.2.4
|
||||
|
||||
# Copy gunicorn config
|
||||
# Changes very infrequently
|
||||
WORKDIR /usr/src/paperless/
|
||||
|
||||
COPY gunicorn.conf.py .
|
||||
|
||||
# setup docker-specific things
|
||||
# Use mounts to avoid copying installer files into the image
|
||||
# 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/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 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 managment commands" \
|
||||
&& chmod +x install_management_commands.sh \
|
||||
&& ./install_management_commands.sh
|
||||
|
||||
# Install the built packages from the installer library images
|
||||
# Use mounts to avoid copying installer files into the image
|
||||
# These change sometimes
|
||||
RUN --mount=type=bind,from=qpdf-builder,target=/qpdf \
|
||||
--mount=type=bind,from=psycopg2-builder,target=/psycopg2 \
|
||||
--mount=type=bind,from=pikepdf-builder,target=/pikepdf \
|
||||
set -eux \
|
||||
&& echo "Installing qpdf" \
|
||||
&& apt-get install --yes --no-install-recommends /qpdf/usr/src/qpdf/libqpdf29_*.deb \
|
||||
&& apt-get install --yes --no-install-recommends /qpdf/usr/src/qpdf/qpdf_*.deb \
|
||||
&& echo "Installing pikepdf and dependencies" \
|
||||
&& python3 -m pip install --no-cache-dir /pikepdf/usr/src/wheels/pyparsing*.whl \
|
||||
&& python3 -m pip install --no-cache-dir /pikepdf/usr/src/wheels/packaging*.whl \
|
||||
&& python3 -m pip install --no-cache-dir /pikepdf/usr/src/wheels/lxml*.whl \
|
||||
&& python3 -m pip install --no-cache-dir /pikepdf/usr/src/wheels/Pillow*.whl \
|
||||
&& python3 -m pip install --no-cache-dir /pikepdf/usr/src/wheels/pikepdf*.whl \
|
||||
&& python3 -m pip list \
|
||||
&& echo "Installing psycopg2" \
|
||||
&& python3 -m pip install --no-cache-dir /psycopg2/usr/src/wheels/psycopg2*.whl \
|
||||
&& python3 -m pip list
|
||||
|
||||
WORKDIR /usr/src/paperless/src/
|
||||
|
||||
# Python dependencies
|
||||
# Change pretty frequently
|
||||
COPY --chown=1000:1000 ["pyproject.toml", "uv.lock", "/usr/src/paperless/src/"]
|
||||
COPY --from=pipenv-base /usr/src/pipenv/requirements.txt ./
|
||||
|
||||
# Packages needed only for building a few quick Python
|
||||
# dependencies
|
||||
ARG BUILD_PACKAGES="\
|
||||
build-essential \
|
||||
# https://github.com/PyMySQL/mysqlclient#linux
|
||||
git \
|
||||
default-libmysqlclient-dev \
|
||||
pkg-config"
|
||||
python3-dev"
|
||||
|
||||
# hadolint ignore=DL3042
|
||||
RUN --mount=type=cache,target=${UV_CACHE_DIR},id=python-cache \
|
||||
set -eux \
|
||||
RUN set -eux \
|
||||
&& 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 \
|
||||
&& echo "Installing Python requirements" \
|
||||
&& uv export --quiet --no-dev --all-extras --format requirements-txt --output-file requirements.txt \
|
||||
&& uv pip install --system --no-python-downloads --python-preference system --requirements requirements.txt \
|
||||
&& echo "Installing NLTK data" \
|
||||
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" snowball_data \
|
||||
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" stopwords \
|
||||
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" punkt_tab \
|
||||
&& python3 -m pip install --default-timeout=1000 --no-cache-dir --requirement requirements.txt \
|
||||
&& echo "Cleaning up image" \
|
||||
&& apt-get --yes purge ${BUILD_PACKAGES} \
|
||||
&& apt-get --yes autoremove --purge \
|
||||
&& apt-get -y purge ${BUILD_PACKAGES} \
|
||||
&& apt-get -y autoremove --purge \
|
||||
&& apt-get clean --yes \
|
||||
&& rm --recursive --force --verbose *.whl \
|
||||
&& rm --recursive --force --verbose /var/lib/apt/lists/* \
|
||||
&& rm --recursive --force --verbose /tmp/* \
|
||||
&& rm --recursive --force --verbose /var/tmp/* \
|
||||
&& rm --recursive --force --verbose /var/cache/apt/archives/* \
|
||||
&& truncate --size 0 /var/log/*log
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& rm -rf /tmp/* \
|
||||
&& rm -rf /var/tmp/* \
|
||||
&& rm -rf /var/cache/apt/archives/* \
|
||||
&& truncate -s 0 /var/log/*log
|
||||
|
||||
# copy backend
|
||||
COPY --chown=1000:1000 ./src ./
|
||||
COPY ./src ./
|
||||
|
||||
# copy frontend
|
||||
COPY --from=compile-frontend --chown=1000:1000 /src/src/documents/static/frontend/ ./documents/static/frontend/
|
||||
COPY --from=compile-frontend /src/src/documents/static/frontend/ ./documents/static/frontend/
|
||||
|
||||
# add users, setup scripts
|
||||
# Mount the compiled frontend to expected location
|
||||
RUN set -eux \
|
||||
&& sed -i '1s|^#!/usr/bin/env python3|#!/command/with-contenv python3|' manage.py \
|
||||
&& echo "Setting up user/group" \
|
||||
&& addgroup --gid 1000 paperless \
|
||||
&& useradd --uid 1000 --gid paperless --home-dir /usr/src/paperless paperless \
|
||||
&& echo "Creating volume directories" \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/data \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/media \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/consume \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/export \
|
||||
&& echo "Creating gnupg directory" \
|
||||
&& mkdir -m700 --verbose /usr/src/paperless/.gnupg \
|
||||
&& echo "Adjusting all permissions" \
|
||||
&& chown --from root:root --changes --recursive paperless:paperless /usr/src/paperless \
|
||||
&& echo "Collecting static files" \
|
||||
&& s6-setuidgid paperless python3 manage.py collectstatic --clear --no-input --link \
|
||||
&& s6-setuidgid paperless python3 manage.py compilemessages
|
||||
&& chown -R paperless:paperless ../ \
|
||||
&& gosu paperless python3 manage.py collectstatic --clear --no-input \
|
||||
&& gosu paperless python3 manage.py compilemessages
|
||||
|
||||
VOLUME ["/usr/src/paperless/data", \
|
||||
"/usr/src/paperless/media", \
|
||||
"/usr/src/paperless/consume", \
|
||||
"/usr/src/paperless/export"]
|
||||
|
||||
ENTRYPOINT ["/init"]
|
||||
ENTRYPOINT ["/sbin/docker-entrypoint.sh"]
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=10s --retries=5 CMD [ "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000" ]
|
||||
CMD ["/usr/local/bin/paperless_cmd.sh"]
|
||||
|
80
Pipfile
Normal file
80
Pipfile
Normal file
@ -0,0 +1,80 @@
|
||||
[[source]]
|
||||
url = "https://pypi.python.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[[source]]
|
||||
url = "https://www.piwheels.org/simple"
|
||||
verify_ssl = true
|
||||
name = "piwheels"
|
||||
|
||||
[packages]
|
||||
dateparser = "~=1.1"
|
||||
django = "~=4.1"
|
||||
django-cors-headers = "*"
|
||||
django-extensions = "*"
|
||||
django-filter = "~=22.1"
|
||||
djangorestframework = "~=3.14"
|
||||
filelock = "*"
|
||||
gunicorn = "*"
|
||||
imap-tools = "*"
|
||||
langdetect = "*"
|
||||
pathvalidate = "*"
|
||||
pillow = "~=9.3"
|
||||
pikepdf = "*"
|
||||
python-gnupg = "*"
|
||||
python-dotenv = "*"
|
||||
python-dateutil = "*"
|
||||
python-magic = "*"
|
||||
psycopg2 = "*"
|
||||
rapidfuzz = "*"
|
||||
redis = {extras = ["hiredis"], version = "*"}
|
||||
scikit-learn = "~=1.1"
|
||||
# Pin this until piwheels is building 1.9 (see https://www.piwheels.org/project/scipy/)
|
||||
scipy = "==1.8.1"
|
||||
numpy = "*"
|
||||
whitenoise = "~=6.2"
|
||||
watchdog = "~=2.1"
|
||||
whoosh="~=2.7"
|
||||
inotifyrecursive = "~=0.3"
|
||||
ocrmypdf = "~=14.0"
|
||||
tqdm = "*"
|
||||
tika = "*"
|
||||
# TODO: This will sadly also install daphne+dependencies,
|
||||
# which an ASGI server we don't need. Adds about 15MB image size.
|
||||
channels = "~=3.0"
|
||||
# Locked version until https://github.com/django/channels_redis/issues/332
|
||||
# is resolved
|
||||
channels-redis = "==3.4.1"
|
||||
uvicorn = {extras = ["standard"], version = "*"}
|
||||
concurrent-log-handler = "*"
|
||||
"pdfminer.six" = "*"
|
||||
"backports.zoneinfo" = {version = "*", markers = "python_version < '3.9'"}
|
||||
"importlib-resources" = {version = "*", markers = "python_version < '3.9'"}
|
||||
zipp = {version = "*", markers = "python_version < '3.9'"}
|
||||
pyzbar = "*"
|
||||
mysqlclient = "*"
|
||||
celery = {extras = ["redis"], version = "*"}
|
||||
django-celery-results = "*"
|
||||
setproctitle = "*"
|
||||
nltk = "*"
|
||||
pdf2image = "*"
|
||||
flower = "*"
|
||||
|
||||
[dev-packages]
|
||||
coveralls = "*"
|
||||
factory-boy = "*"
|
||||
pycodestyle = "*"
|
||||
pytest = "*"
|
||||
pytest-cov = "*"
|
||||
pytest-django = "*"
|
||||
pytest-env = "*"
|
||||
pytest-sugar = "*"
|
||||
pytest-xdist = "*"
|
||||
sphinx = "~=5.3"
|
||||
sphinx_rtd_theme = "*"
|
||||
tox = "*"
|
||||
black = "*"
|
||||
pre-commit = "*"
|
||||
sphinx-autobuild = "*"
|
||||
myst-parser = "*"
|
2696
Pipfile.lock
generated
Normal file
2696
Pipfile.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
96
README.md
96
README.md
@ -1,16 +1,12 @@
|
||||
[](https://github.com/paperless-ngx/paperless-ngx/actions)
|
||||
[](https://crowdin.com/project/paperless-ngx)
|
||||
[](https://docs.paperless-ngx.com)
|
||||
[](https://codecov.io/gh/paperless-ngx/paperless-ngx)
|
||||
[](https://paperless-ngx.readthedocs.io/en/latest/?badge=latest)
|
||||
[](https://coveralls.io/github/paperless-ngx/paperless-ngx?branch=master)
|
||||
[](https://matrix.to/#/%23paperlessngx%3Amatrix.org)
|
||||
[](https://demo.paperless-ngx.com)
|
||||
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/paperless-ngx/paperless-ngx/blob/main/resources/logo/web/png/White%20logo%20-%20no%20background.png" width="50%">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://github.com/paperless-ngx/paperless-ngx/raw/main/resources/logo/web/png/Black%20logo%20-%20no%20background.png" width="50%">
|
||||
<img src="https://github.com/paperless-ngx/paperless-ngx/raw/main/resources/logo/web/png/Black%20logo%20-%20no%20background.png" width="50%">
|
||||
</picture>
|
||||
<img src="https://github.com/paperless-ngx/paperless-ngx/raw/main/resources/logo/web/png/Black%20logo%20-%20no%20background.png#gh-light-mode-only" width="50%" />
|
||||
<img src="https://github.com/paperless-ngx/paperless-ngx/raw/main/resources/logo/web/png/White%20logo%20-%20no%20background.png#gh-dark-mode-only" width="50%" />
|
||||
</p>
|
||||
|
||||
<!-- omit in toc -->
|
||||
@ -19,9 +15,10 @@
|
||||
|
||||
Paperless-ngx is a document management system that transforms your physical documents into a searchable online archive so you can keep, well, _less paper_.
|
||||
|
||||
Paperless-ngx is the official successor to the original [Paperless](https://github.com/the-paperless-project/paperless) & [Paperless-ng](https://github.com/jonaswinkler/paperless-ng) projects and is designed to distribute the responsibility of advancing and supporting the project among a team of people. [Consider joining us!](#community-support)
|
||||
Paperless-ngx forked from [paperless-ng](https://github.com/jonaswinkler/paperless-ng) to continue the great work and distribute responsibility of supporting and advancing the project among a team of people. [Consider joining us!](#community-support) Discussion of this transition can be found in issues
|
||||
[#1599](https://github.com/jonaswinkler/paperless-ng/issues/1599) and [#1632](https://github.com/jonaswinkler/paperless-ng/issues/1632).
|
||||
|
||||
Thanks to the generous folks at [DigitalOcean](https://m.do.co/c/8d70b916d462), a demo is available at [demo.paperless-ngx.com](https://demo.paperless-ngx.com) using login `demo` / `demo`. _Note: demo content is reset frequently and confidential information should not be uploaded._
|
||||
A demo is available at [demo.paperless-ngx.com](https://demo.paperless-ngx.com) using login `demo` / `demo`. _Note: demo content is reset frequently and confidential information should not be uploaded._
|
||||
|
||||
- [Features](#features)
|
||||
- [Getting started](#getting-started)
|
||||
@ -30,60 +27,68 @@ Thanks to the generous folks at [DigitalOcean](https://m.do.co/c/8d70b916d462),
|
||||
- [Translation](#translation)
|
||||
- [Feature Requests](#feature-requests)
|
||||
- [Bugs](#bugs)
|
||||
- [Related Projects](#related-projects)
|
||||
- [Affiliated Projects](#affiliated-projects)
|
||||
- [Important Note](#important-note)
|
||||
|
||||
<p align="right">This project is supported by:<br/>
|
||||
<a href="https://m.do.co/c/8d70b916d462" style="padding-top: 4px; display: block;">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_white.svg" width="140px">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="140px">
|
||||
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_black_.svg" width="140px">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
# Features
|
||||
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docs/assets/screenshots/documents-smallcards-dark.png">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docs/assets/screenshots/documents-smallcards.png">
|
||||
<img src="https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docs/assets/screenshots/documents-smallcards.png">
|
||||
</picture>
|
||||

|
||||

|
||||
|
||||
A full list of [features](https://docs.paperless-ngx.com/#features) and [screenshots](https://docs.paperless-ngx.com/#screenshots) are available in the [documentation](https://docs.paperless-ngx.com/).
|
||||
- Organize and index your scanned documents with tags, correspondents, types, and more.
|
||||
- Performs OCR on your documents, adds selectable text to image only documents and adds tags, correspondents and document types to your documents.
|
||||
- Supports PDF documents, images, plain text files, and Office documents (Word, Excel, Powerpoint, and LibreOffice equivalents).
|
||||
- Office document support is optional and provided by Apache Tika (see [configuration](https://paperless-ngx.readthedocs.io/en/latest/configuration.html#tika-settings))
|
||||
- Paperless stores your documents plain on disk. Filenames and folders are managed by paperless and their format can be configured freely.
|
||||
- Single page application front end.
|
||||
- Includes a dashboard that shows basic statistics and has document upload.
|
||||
- Filtering by tags, correspondents, types, and more.
|
||||
- Customizable views can be saved and displayed on the dashboard.
|
||||
- Full text search helps you find what you need.
|
||||
- Auto completion suggests relevant words from your documents.
|
||||
- Results are sorted by relevance to your search query.
|
||||
- Highlighting shows you which parts of the document matched the query.
|
||||
- Searching for similar documents ("More like this")
|
||||
- Email processing: Paperless adds documents from your email accounts.
|
||||
- Configure multiple accounts and filters for each account.
|
||||
- When adding documents from mail, paperless can move these mail to a new folder, mark them as read, flag them as important or delete them.
|
||||
- Machine learning powered document matching.
|
||||
- Paperless-ngx learns from your documents and will be able to automatically assign tags, correspondents and types to documents once you've stored a few documents in paperless.
|
||||
- Optimized for multi core systems: Paperless-ngx consumes multiple documents in parallel.
|
||||
- The integrated sanity checker makes sure that your document archive is in good health.
|
||||
- [More screenshots are available in the documentation](https://paperless-ngx.readthedocs.io/en/latest/screenshots.html).
|
||||
|
||||
# Getting started
|
||||
|
||||
The easiest way to deploy paperless is `docker compose`. The files in the [`/docker/compose` directory](https://github.com/paperless-ngx/paperless-ngx/tree/main/docker/compose) are configured to pull the image from the GitHub container registry.
|
||||
The easiest way to deploy paperless is docker-compose. The files in the [`/docker/compose` directory](https://github.com/paperless-ngx/paperless-ngx/tree/main/docker/compose) are configured to pull the image from Github Packages.
|
||||
|
||||
If you'd like to jump right in, you can configure a `docker compose` environment with our install script:
|
||||
If you'd like to jump right in, you can configure a docker-compose environment with our install script:
|
||||
|
||||
```bash
|
||||
bash -c "$(curl -L https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"
|
||||
```
|
||||
|
||||
More details and step-by-step guides for alternative installation methods can be found in [the documentation](https://docs.paperless-ngx.com/setup/#installation).
|
||||
Alternatively, you can install the dependencies and setup apache and a database server yourself. The [documentation](https://paperless-ngx.readthedocs.io/en/latest/setup.html#installation) has a step by step guide on how to do it.
|
||||
|
||||
Migrating from Paperless-ng is easy, just drop in the new docker image! See the [documentation on migrating](https://docs.paperless-ngx.com/setup/#migrating-to-paperless-ngx) for more details.
|
||||
Migrating from Paperless-ng is easy, just drop in the new docker image! See the [documentation on migrating](https://paperless-ngx.readthedocs.io/en/latest/setup.html#migrating-from-paperless-ng) for more details.
|
||||
|
||||
<!-- omit in toc -->
|
||||
|
||||
### Documentation
|
||||
|
||||
The documentation for Paperless-ngx is available at [https://docs.paperless-ngx.com](https://docs.paperless-ngx.com/).
|
||||
The documentation for Paperless-ngx is available on [ReadTheDocs](https://paperless-ngx.readthedocs.io/).
|
||||
|
||||
# Contributing
|
||||
|
||||
If you feel like contributing to the project, please do! Bug fixes, enhancements, visual fixes etc. are always welcome. If you want to implement something big: Please start a discussion about that! The [documentation](https://docs.paperless-ngx.com/development/) has some basic information on how to get started.
|
||||
If you feel like contributing to the project, please do! Bug fixes, enhancements, visual fixes etc. are always welcome. If you want to implement something big: Please start a discussion about that! The [documentation](https://paperless-ngx.readthedocs.io/en/latest/extending.html) has some basic information on how to get started.
|
||||
|
||||
## Community Support
|
||||
|
||||
People interested in continuing the work on paperless-ngx are encouraged to reach out here on github and in the [Matrix Room](https://matrix.to/#/#paperless:matrix.org). If you would like to contribute to the project on an ongoing basis there are multiple [teams](https://github.com/orgs/paperless-ngx/people) (frontend, ci/cd, etc) that could use your help so please reach out!
|
||||
People interested in continuing the work on paperless-ngx are encouraged to reach out here on github and in the [Matrix Room](https://matrix.to/#/#paperless:adnidor.de). If you would like to contribute to the project on an ongoing basis there are multiple [teams](https://github.com/orgs/paperless-ngx/people) (frontend, ci/cd, etc) that could use your help so please reach out!
|
||||
|
||||
## Translation
|
||||
|
||||
Paperless-ngx is available in many languages that are coordinated on Crowdin. If you want to help out by translating paperless-ngx into your language, please head over to https://crowdin.com/project/paperless-ngx, and thank you! More details can be found in [CONTRIBUTING.md](https://github.com/paperless-ngx/paperless-ngx/blob/main/CONTRIBUTING.md#translating-paperless-ngx).
|
||||
Paperless-ngx is available in many languages that are coordinated on Crowdin. If you want to help out by translating paperless-ngx into your language, please head over to https://crwd.in/paperless-ngx, and thank you! More details can be found in [CONTRIBUTING.md](https://github.com/paperless-ngx/paperless-ngx/blob/main/CONTRIBUTING.md#translating-paperless-ngx).
|
||||
|
||||
## Feature Requests
|
||||
|
||||
@ -93,11 +98,24 @@ Feature requests can be submitted via [GitHub Discussions](https://github.com/pa
|
||||
|
||||
For bugs please [open an issue](https://github.com/paperless-ngx/paperless-ngx/issues) or [start a discussion](https://github.com/paperless-ngx/paperless-ngx/discussions) if you have questions.
|
||||
|
||||
# Related Projects
|
||||
# Affiliated Projects
|
||||
|
||||
Please see [the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Related-Projects) for a user-maintained list of related projects and software that is compatible with Paperless-ngx.
|
||||
Paperless has been around a while now, and people are starting to build stuff on top of it. If you're one of those people, we can add your project to this list:
|
||||
|
||||
- [Paperless App](https://github.com/bauerj/paperless_app): An Android/iOS app for Paperless-ngx. Also works with the original Paperless and Paperless-ng.
|
||||
- [Paperless Share](https://github.com/qcasey/paperless_share). Share any files from your Android application with paperless. Very simple, but works with all of the mobile scanning apps out there that allow you to share scanned documents.
|
||||
- [Scan to Paperless](https://github.com/sbrunner/scan-to-paperless): Scan and prepare (crop, deskew, OCR, ...) your documents for Paperless.
|
||||
- [Paperless Mobile](https://github.com/astubenbord/paperless-mobile): A modern, feature rich mobile application for Paperless.
|
||||
|
||||
These projects also exist, but their status and compatibility with paperless-ngx is unknown.
|
||||
|
||||
- [paperless-cli](https://github.com/stgarf/paperless-cli): A golang command line binary to interact with a Paperless instance.
|
||||
|
||||
This project also exists, but needs updates to be compatible with paperless-ngx.
|
||||
|
||||
- [Paperless Desktop](https://github.com/thomasbrueggemann/paperless-desktop): A desktop UI for your Paperless installation. Runs on Mac, Linux, and Windows.
|
||||
Known issues on Mac: (Could not load reminders and documents)
|
||||
|
||||
# Important Note
|
||||
|
||||
> Document scanners are typically used to scan sensitive documents like your social insurance number, tax records, invoices, etc. **Paperless-ngx should never be run on an untrusted host** because information is stored in clear text without encryption. No guarantees are made regarding security (but we do try!) and you use the app at your own risk.
|
||||
> **The safest way to run Paperless-ngx is on a local server in your own home with backups in place**.
|
||||
Document scanners are typically used to scan sensitive documents. Things like your social insurance number, tax records, invoices, etc. Everything is stored in the clear without encryption. This means that Paperless should never be run on an untrusted host. Instead, I recommend that if you do want to use it, run it locally on a server in your own home.
|
||||
|
@ -1,9 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
The Paperless-ngx team and community take security bugs seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
|
||||
|
||||
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/paperless-ngx/paperless-ngx/security/advisories/new) tab.
|
||||
|
||||
The team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
|
47
build-docker-image.sh
Executable file
47
build-docker-image.sh
Executable file
@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Helper script for building the Docker image locally.
|
||||
# Parses and provides the nessecary versions of other images to Docker
|
||||
# before passing in the rest of script args.
|
||||
|
||||
# First Argument: The Dockerfile to build
|
||||
# Other Arguments: Additional arguments to docker build
|
||||
|
||||
# Example Usage:
|
||||
# ./build-docker-image.sh Dockerfile -t paperless-ngx:my-awesome-feature
|
||||
|
||||
set -eux
|
||||
|
||||
if ! command -v jq; then
|
||||
echo "jq required"
|
||||
exit 1
|
||||
elif [ ! -f "$1" ]; then
|
||||
echo "$1 is not a file, please provide the Dockerfile"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Parse what we can from Pipfile.lock
|
||||
pikepdf_version=$(jq ".default.pikepdf.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
|
||||
psycopg2_version=$(jq ".default.psycopg2.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
|
||||
pillow_version=$(jq ".default.pillow.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
|
||||
lxml_version=$(jq ".default.lxml.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
|
||||
# Read this from the other config file
|
||||
qpdf_version=$(jq ".qpdf.version" .build-config.json | sed 's/"//g')
|
||||
jbig2enc_version=$(jq ".jbig2enc.version" .build-config.json | sed 's/"//g')
|
||||
# Get the branch name (used for caching)
|
||||
branch_name=$(git rev-parse --abbrev-ref HEAD)
|
||||
|
||||
# https://docs.docker.com/develop/develop-images/build_enhancements/
|
||||
# Required to use cache-from
|
||||
export DOCKER_BUILDKIT=1
|
||||
|
||||
docker build --file "$1" \
|
||||
--progress=plain \
|
||||
--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/app:"${branch_name}" \
|
||||
--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/app:dev \
|
||||
--build-arg JBIG2ENC_VERSION="${jbig2enc_version}" \
|
||||
--build-arg QPDF_VERSION="${qpdf_version}" \
|
||||
--build-arg PIKEPDF_VERSION="${pikepdf_version}" \
|
||||
--build-arg PILLOW_VERSION="${pillow_version}" \
|
||||
--build-arg LXML_VERSION="${lxml_version}" \
|
||||
--build-arg PSYCOPG2_VERSION="${psycopg2_version}" "${@:2}" .
|
@ -1,6 +1,4 @@
|
||||
project_id_env: CROWDIN_PROJECT_ID
|
||||
api_token_env: CROWDIN_PERSONAL_TOKEN
|
||||
preserve_hierarchy: true
|
||||
commit_message: '[ci skip]'
|
||||
files:
|
||||
- source: /src/locale/en_US/LC_MESSAGES/django.po
|
||||
translation: /src/locale/%locale_with_underscore%/LC_MESSAGES/django.po
|
||||
|
35
docker-builders/Dockerfile.jbig2enc
Normal file
35
docker-builders/Dockerfile.jbig2enc
Normal file
@ -0,0 +1,35 @@
|
||||
# This Dockerfile compiles the jbig2enc library
|
||||
# Inputs:
|
||||
# - JBIG2ENC_VERSION - the Git tag to checkout and build
|
||||
|
||||
FROM debian:bullseye-slim as main
|
||||
|
||||
LABEL org.opencontainers.image.description="A intermediate image with jbig2enc built"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG JBIG2ENC_VERSION
|
||||
|
||||
ARG BUILD_PACKAGES="\
|
||||
build-essential \
|
||||
automake \
|
||||
libtool \
|
||||
libleptonica-dev \
|
||||
zlib1g-dev \
|
||||
git \
|
||||
ca-certificates"
|
||||
|
||||
WORKDIR /usr/src/jbig2enc
|
||||
|
||||
RUN set -eux \
|
||||
&& echo "Installing build tools" \
|
||||
&& apt-get update --quiet \
|
||||
&& apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \
|
||||
&& echo "Building jbig2enc" \
|
||||
&& git clone --quiet --branch $JBIG2ENC_VERSION https://github.com/agl/jbig2enc . \
|
||||
&& ./autogen.sh \
|
||||
&& ./configure \
|
||||
&& make \
|
||||
&& echo "Cleaning up image" \
|
||||
&& apt-get -y purge ${BUILD_PACKAGES} \
|
||||
&& apt-get -y autoremove --purge \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
92
docker-builders/Dockerfile.pikepdf
Normal file
92
docker-builders/Dockerfile.pikepdf
Normal file
@ -0,0 +1,92 @@
|
||||
# This Dockerfile builds the pikepdf wheel
|
||||
# Inputs:
|
||||
# - REPO - Docker repository to pull qpdf from
|
||||
# - QPDF_VERSION - The image qpdf version to copy .deb files from
|
||||
# - PIKEPDF_VERSION - Version of pikepdf to build wheel for
|
||||
|
||||
# Default to pulling from the main repo registry when manually building
|
||||
ARG REPO="paperless-ngx/paperless-ngx"
|
||||
|
||||
ARG QPDF_VERSION
|
||||
FROM ghcr.io/${REPO}/builder/qpdf:${QPDF_VERSION} as qpdf-builder
|
||||
|
||||
# This does nothing, except provide a name for a copy below
|
||||
|
||||
FROM python:3.9-slim-bullseye as main
|
||||
|
||||
LABEL org.opencontainers.image.description="A intermediate image with pikepdf wheel built"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG PIKEPDF_VERSION
|
||||
# These are not used, but will still bust the cache if one changes
|
||||
# Otherwise, the main image will try to build thing (and fail)
|
||||
ARG PILLOW_VERSION
|
||||
ARG LXML_VERSION
|
||||
|
||||
ARG BUILD_PACKAGES="\
|
||||
build-essential \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
# qpdf requirement - https://github.com/qpdf/qpdf#crypto-providers
|
||||
libgnutls28-dev \
|
||||
# lxml requrements - https://lxml.de/installation.html
|
||||
libxml2-dev \
|
||||
libxslt1-dev \
|
||||
# Pillow requirements - https://pillow.readthedocs.io/en/stable/installation.html#external-libraries
|
||||
# JPEG functionality
|
||||
libjpeg62-turbo-dev \
|
||||
# conpressed PNG
|
||||
zlib1g-dev \
|
||||
# compressed TIFF
|
||||
libtiff-dev \
|
||||
# type related services
|
||||
libfreetype-dev \
|
||||
# color management
|
||||
liblcms2-dev \
|
||||
# WebP format
|
||||
libwebp-dev \
|
||||
# JPEG 2000
|
||||
libopenjp2-7-dev \
|
||||
# improved color quantization
|
||||
libimagequant-dev \
|
||||
# complex text layout support
|
||||
libraqm-dev"
|
||||
|
||||
WORKDIR /usr/src
|
||||
|
||||
COPY --from=qpdf-builder /usr/src/qpdf/*.deb ./
|
||||
|
||||
# As this is an base image for a multi-stage final image
|
||||
# the added size of the install is basically irrelevant
|
||||
|
||||
RUN set -eux \
|
||||
&& echo "Installing build tools" \
|
||||
&& apt-get update --quiet \
|
||||
&& apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \
|
||||
&& echo "Installing qpdf" \
|
||||
&& dpkg --install libqpdf29_*.deb \
|
||||
&& dpkg --install libqpdf-dev_*.deb \
|
||||
&& echo "Installing Python tools" \
|
||||
&& python3 -m pip install --no-cache-dir --upgrade \
|
||||
pip \
|
||||
wheel \
|
||||
# https://pikepdf.readthedocs.io/en/latest/installation.html#requirements
|
||||
pybind11 \
|
||||
&& echo "Building pikepdf wheel ${PIKEPDF_VERSION}" \
|
||||
&& mkdir wheels \
|
||||
&& python3 -m pip wheel \
|
||||
# Build the package at the required version
|
||||
pikepdf==${PIKEPDF_VERSION} \
|
||||
# Output the *.whl into this directory
|
||||
--wheel-dir wheels \
|
||||
# Do not use a binary packge for the package being built
|
||||
--no-binary=pikepdf \
|
||||
# Do use binary packages for dependencies
|
||||
--prefer-binary \
|
||||
# Don't cache build files
|
||||
--no-cache-dir \
|
||||
&& ls -ahl wheels \
|
||||
&& echo "Cleaning up image" \
|
||||
&& apt-get -y purge ${BUILD_PACKAGES} \
|
||||
&& apt-get -y autoremove --purge \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
48
docker-builders/Dockerfile.psycopg2
Normal file
48
docker-builders/Dockerfile.psycopg2
Normal file
@ -0,0 +1,48 @@
|
||||
# This Dockerfile builds the psycopg2 wheel
|
||||
# Inputs:
|
||||
# - PSYCOPG2_VERSION - Version to build
|
||||
|
||||
FROM python:3.9-slim-bullseye as main
|
||||
|
||||
LABEL org.opencontainers.image.description="A intermediate image with psycopg2 wheel built"
|
||||
|
||||
ARG PSYCOPG2_VERSION
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ARG BUILD_PACKAGES="\
|
||||
build-essential \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
# https://www.psycopg.org/docs/install.html#prerequisites
|
||||
libpq-dev"
|
||||
|
||||
WORKDIR /usr/src
|
||||
|
||||
# As this is an base image for a multi-stage final image
|
||||
# the added size of the install is basically irrelevant
|
||||
|
||||
RUN set -eux \
|
||||
&& echo "Installing build tools" \
|
||||
&& apt-get update --quiet \
|
||||
&& apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \
|
||||
&& echo "Installing Python tools" \
|
||||
&& python3 -m pip install --no-cache-dir --upgrade pip wheel \
|
||||
&& echo "Building psycopg2 wheel ${PSYCOPG2_VERSION}" \
|
||||
&& cd /usr/src \
|
||||
&& mkdir wheels \
|
||||
&& python3 -m pip wheel \
|
||||
# Build the package at the required version
|
||||
psycopg2==${PSYCOPG2_VERSION} \
|
||||
# Output the *.whl into this directory
|
||||
--wheel-dir wheels \
|
||||
# Do not use a binary packge for the package being built
|
||||
--no-binary=psycopg2 \
|
||||
# Do use binary packages for dependencies
|
||||
--prefer-binary \
|
||||
# Don't cache build files
|
||||
--no-cache-dir \
|
||||
&& ls -ahl wheels/ \
|
||||
&& echo "Cleaning up image" \
|
||||
&& apt-get -y purge ${BUILD_PACKAGES} \
|
||||
&& apt-get -y autoremove --purge \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
48
docker-builders/Dockerfile.qpdf
Normal file
48
docker-builders/Dockerfile.qpdf
Normal file
@ -0,0 +1,48 @@
|
||||
# This Dockerfile compiles the jbig2enc library
|
||||
# Inputs:
|
||||
# - QPDF_VERSION - the version of qpdf to build a .deb.
|
||||
# Must be present as a deb-src in bookworm
|
||||
|
||||
FROM debian:bullseye-slim as main
|
||||
|
||||
LABEL org.opencontainers.image.description="A intermediate image with qpdf built"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
# This must match to pikepdf's minimum at least
|
||||
ARG QPDF_VERSION
|
||||
|
||||
ARG BUILD_PACKAGES="\
|
||||
build-essential \
|
||||
debhelper \
|
||||
debian-keyring \
|
||||
devscripts \
|
||||
equivs \
|
||||
libtool \
|
||||
# https://qpdf.readthedocs.io/en/stable/installation.html#system-requirements
|
||||
libjpeg62-turbo-dev \
|
||||
libgnutls28-dev \
|
||||
packaging-dev \
|
||||
cmake \
|
||||
zlib1g-dev"
|
||||
|
||||
WORKDIR /usr/src
|
||||
|
||||
RUN set -eux \
|
||||
&& echo "Installing build tools" \
|
||||
&& apt-get update --quiet \
|
||||
&& apt-get install --yes --quiet --no-install-recommends $BUILD_PACKAGES \
|
||||
&& echo "Getting qpdf src" \
|
||||
&& echo "deb-src http://deb.debian.org/debian/ bookworm main" > /etc/apt/sources.list.d/bookworm-src.list \
|
||||
&& apt-get update \
|
||||
&& mkdir qpdf \
|
||||
&& cd qpdf \
|
||||
&& apt-get source --yes --quiet qpdf=${QPDF_VERSION}-1/bookworm \
|
||||
&& echo "Building qpdf" \
|
||||
&& cd qpdf-$QPDF_VERSION \
|
||||
&& export DEB_BUILD_OPTIONS="terse nocheck nodoc parallel=2" \
|
||||
&& dpkg-buildpackage --build=binary --unsigned-source --unsigned-changes --post-clean \
|
||||
&& ls -ahl ../*.deb \
|
||||
&& echo "Cleaning up image" \
|
||||
&& apt-get -y purge ${BUILD_PACKAGES} \
|
||||
&& apt-get -y autoremove --purge \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
@ -1,26 +0,0 @@
|
||||
# Docker Compose file for running paperless testing with actual gotenberg
|
||||
# and Tika containers for a more end to end test of the Tika related functionality
|
||||
# Can be used locally or by the CI to start the necessary containers with the
|
||||
# correct networking for the tests
|
||||
|
||||
services:
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:8.20
|
||||
hostname: gotenberg
|
||||
container_name: gotenberg
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||
# want to allow external content like tracking pixels or even javascript.
|
||||
command:
|
||||
- "gotenberg"
|
||||
- "--chromium-disable-javascript=true"
|
||||
- "--chromium-allow-list=file:///tmp/.*"
|
||||
- "--log-level=warn"
|
||||
- "--log-format=text"
|
||||
tika:
|
||||
image: docker.io/apache/tika:latest
|
||||
hostname: tika
|
||||
container_name: tika
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
@ -1,17 +1,26 @@
|
||||
###############################################################################
|
||||
# Paperless-ngx settings #
|
||||
###############################################################################
|
||||
|
||||
# See http://docs.paperless-ngx.com/configuration/ for all available options.
|
||||
|
||||
# The UID and GID of the user used to run paperless in the container. Set this
|
||||
# to your UID and GID on the host so that you have write access to the
|
||||
# consumption directory.
|
||||
#USERMAP_UID=1000
|
||||
#USERMAP_GID=1000
|
||||
|
||||
# See the documentation linked above for all options. A few commonly adjusted settings
|
||||
# are provided below.
|
||||
# Additional languages to install for text recognition, separated by a
|
||||
# whitespace. Note that this is
|
||||
# different from PAPERLESS_OCR_LANGUAGE (default=eng), which defines the
|
||||
# language used for OCR.
|
||||
# The container installs English, German, Italian, Spanish and French by
|
||||
# default.
|
||||
# See https://packages.debian.org/search?keywords=tesseract-ocr-&searchon=names&suite=buster
|
||||
# for available languages.
|
||||
#PAPERLESS_OCR_LANGUAGES=tur ces
|
||||
|
||||
###############################################################################
|
||||
# Paperless-specific settings #
|
||||
###############################################################################
|
||||
|
||||
# All settings defined in the paperless.conf.example can be used here. The
|
||||
# Docker setup does not use the configuration file.
|
||||
# A few commonly adjusted settings are provided below.
|
||||
|
||||
# This is required if you will be exposing Paperless-ngx on a public domain
|
||||
# (if doing so please consider security measures such as reverse proxy)
|
||||
@ -21,17 +30,13 @@
|
||||
# be a very long sequence of random characters. You don't need to remember it.
|
||||
#PAPERLESS_SECRET_KEY=change-me
|
||||
|
||||
# Use this variable to set a timezone for the Paperless Docker containers. Defaults to UTC.
|
||||
# Use this variable to set a timezone for the Paperless Docker containers. If not specified, defaults to UTC.
|
||||
#PAPERLESS_TIME_ZONE=America/Los_Angeles
|
||||
|
||||
# The default language to use for OCR. Set this to the language most of your
|
||||
# documents are written in.
|
||||
#PAPERLESS_OCR_LANGUAGE=eng
|
||||
|
||||
# Additional languages to install for text recognition, separated by a whitespace.
|
||||
# Note that this is different from PAPERLESS_OCR_LANGUAGE (default=eng), which defines
|
||||
# the language used for OCR.
|
||||
# The container installs English, German, Italian, Spanish and French by default.
|
||||
# See https://packages.debian.org/search?keywords=tesseract-ocr-&searchon=names&suite=buster
|
||||
# for available languages.
|
||||
#PAPERLESS_OCR_LANGUAGES=tur ces
|
||||
# Set if accessing paperless via a domain subpath e.g. https://domain.com/PATHPREFIX and using a reverse-proxy like traefik or nginx
|
||||
#PAPERLESS_FORCE_SCRIPT_NAME=/PATHPREFIX
|
||||
#PAPERLESS_STATIC_URL=/PATHPREFIX/static/ # trailing slash required
|
||||
|
@ -1,4 +1,4 @@
|
||||
# docker compose file for running paperless from the Docker Hub.
|
||||
# docker-compose file for running paperless from the Docker Hub.
|
||||
# This file contains everything paperless needs to run.
|
||||
# Paperless supports amd64, arm and arm64 hardware.
|
||||
#
|
||||
@ -10,7 +10,7 @@
|
||||
# as this file and mounted to the correct folders inside the container.
|
||||
# - Paperless listens on port 8000.
|
||||
#
|
||||
# In addition to that, this Docker Compose file adds the following optional
|
||||
# In addition to that, this docker-compose file adds the following optional
|
||||
# configurations:
|
||||
#
|
||||
# - Instead of SQLite (default), MariaDB is used as the database server.
|
||||
@ -23,21 +23,23 @@
|
||||
#
|
||||
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
|
||||
# and '.env' into a folder.
|
||||
# - Run 'docker compose pull'.
|
||||
# - Run 'docker compose up -d'.
|
||||
|
||||
# - Run 'docker-compose pull'.
|
||||
# - Run 'docker-compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker-compose up -d'.
|
||||
#
|
||||
# For more extensive installation and update instructions, refer to the
|
||||
# documentation.
|
||||
|
||||
version: "3.4"
|
||||
services:
|
||||
broker:
|
||||
image: docker.io/library/redis:8
|
||||
image: docker.io/library/redis:7
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redisdata:/data
|
||||
|
||||
db:
|
||||
image: docker.io/library/mariadb:11
|
||||
image: docker.io/library/mariadb:10
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- dbdata:/var/lib/mysql
|
||||
@ -47,6 +49,9 @@ services:
|
||||
MARIADB_USER: paperless
|
||||
MARIADB_PASSWORD: paperless
|
||||
MARIADB_ROOT_PASSWORD: paperless
|
||||
ports:
|
||||
- "3306:3306"
|
||||
|
||||
webserver:
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:latest
|
||||
restart: unless-stopped
|
||||
@ -56,7 +61,12 @@ services:
|
||||
- gotenberg
|
||||
- tika
|
||||
ports:
|
||||
- "8000:8000"
|
||||
- 8000:8000
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- data:/usr/src/paperless/data
|
||||
- media:/usr/src/paperless/media
|
||||
@ -73,18 +83,18 @@ services:
|
||||
PAPERLESS_TIKA_ENABLED: 1
|
||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:8.20
|
||||
image: docker.io/gotenberg/gotenberg:7.6
|
||||
restart: unless-stopped
|
||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||
# want to allow external content like tracking pixels or even javascript.
|
||||
command:
|
||||
- "gotenberg"
|
||||
- "--chromium-disable-javascript=true"
|
||||
- "--chromium-allow-list=file:///tmp/.*"
|
||||
- "--chromium-disable-routes=true"
|
||||
|
||||
tika:
|
||||
image: docker.io/apache/tika:latest
|
||||
image: ghcr.io/paperless-ngx/tika:latest
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Docker Compose file for running paperless from the Docker Hub.
|
||||
# docker-compose file for running paperless from the Docker Hub.
|
||||
# This file contains everything paperless needs to run.
|
||||
# Paperless supports amd64, arm and arm64 hardware.
|
||||
#
|
||||
@ -10,7 +10,7 @@
|
||||
# as this file and mounted to the correct folders inside the container.
|
||||
# - Paperless listens on port 8000.
|
||||
#
|
||||
# In addition to that, this Docker Compose file adds the following optional
|
||||
# In addition to that, this docker-compose file adds the following optional
|
||||
# configurations:
|
||||
#
|
||||
# - Instead of SQLite (default), MariaDB is used as the database server.
|
||||
@ -19,20 +19,23 @@
|
||||
#
|
||||
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
|
||||
# and '.env' into a folder.
|
||||
# - Run 'docker compose pull'.
|
||||
# - Run 'docker compose up -d'.
|
||||
# - Run 'docker-compose pull'.
|
||||
# - Run 'docker-compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker-compose up -d'.
|
||||
#
|
||||
# For more extensive installation and update instructions, refer to the
|
||||
# documentation.
|
||||
|
||||
version: "3.4"
|
||||
services:
|
||||
broker:
|
||||
image: docker.io/library/redis:8
|
||||
image: docker.io/library/redis:7
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redisdata:/data
|
||||
|
||||
db:
|
||||
image: docker.io/library/mariadb:11
|
||||
image: docker.io/library/mariadb:10
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- dbdata:/var/lib/mysql
|
||||
@ -42,6 +45,9 @@ services:
|
||||
MARIADB_USER: paperless
|
||||
MARIADB_PASSWORD: paperless
|
||||
MARIADB_ROOT_PASSWORD: paperless
|
||||
ports:
|
||||
- "3306:3306"
|
||||
|
||||
webserver:
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:latest
|
||||
restart: unless-stopped
|
||||
@ -49,7 +55,12 @@ services:
|
||||
- db
|
||||
- broker
|
||||
ports:
|
||||
- "8000:8000"
|
||||
- 8000:8000
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- data:/usr/src/paperless/data
|
||||
- media:/usr/src/paperless/media
|
||||
@ -63,6 +74,8 @@ services:
|
||||
PAPERLESS_DBUSER: paperless # only needed if non-default username
|
||||
PAPERLESS_DBPASS: paperless # only needed if non-default password
|
||||
PAPERLESS_DBPORT: 3306
|
||||
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Docker Compose file for running paperless from the Docker Hub.
|
||||
# docker-compose file for running paperless from the Docker Hub.
|
||||
# This file contains everything paperless needs to run.
|
||||
# Paperless supports amd64, arm and arm64 hardware.
|
||||
#
|
||||
@ -10,7 +10,7 @@
|
||||
# as this file and mounted to the correct folders inside the container.
|
||||
# - Paperless listens on port 8010.
|
||||
#
|
||||
# In addition to that, this Docker Compose file adds the following optional
|
||||
# In addition to that, this docker-compose file adds the following optional
|
||||
# configurations:
|
||||
#
|
||||
# - Instead of SQLite (default), PostgreSQL is used as the database server.
|
||||
@ -18,22 +18,26 @@
|
||||
# To install and update paperless with this file, do the following:
|
||||
#
|
||||
# - Open portainer Stacks list and click 'Add stack'
|
||||
# - Paste the contents of this file and assign a name, e.g. 'paperless'
|
||||
# - Upload 'docker-compose.env' by clicking on 'Load variables from .env file'
|
||||
# - Modify the environment variables as needed
|
||||
# - Paste the contents of this file and assign a name, e.g. 'Paperless'
|
||||
# - Click 'Deploy the stack' and wait for it to be deployed
|
||||
# - Open the list of containers, select paperless_webserver_1
|
||||
# - Click 'Console' and then 'Connect' to open the command line inside the container
|
||||
# - Run 'python3 manage.py createsuperuser' to create a user
|
||||
# - Exit the console
|
||||
#
|
||||
# For more extensive installation and update instructions, refer to the
|
||||
# documentation.
|
||||
|
||||
version: "3.4"
|
||||
services:
|
||||
broker:
|
||||
image: docker.io/library/redis:8
|
||||
image: docker.io/library/redis:7
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redisdata:/data
|
||||
|
||||
db:
|
||||
image: docker.io/library/postgres:17
|
||||
image: docker.io/library/postgres:13
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
@ -41,6 +45,7 @@ services:
|
||||
POSTGRES_DB: paperless
|
||||
POSTGRES_USER: paperless
|
||||
POSTGRES_PASSWORD: paperless
|
||||
|
||||
webserver:
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:latest
|
||||
restart: unless-stopped
|
||||
@ -48,7 +53,12 @@ services:
|
||||
- db
|
||||
- broker
|
||||
ports:
|
||||
- "8010:8000"
|
||||
- 8010:8000
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- data:/usr/src/paperless/data
|
||||
- media:/usr/src/paperless/media
|
||||
@ -57,8 +67,29 @@ services:
|
||||
environment:
|
||||
PAPERLESS_REDIS: redis://broker:6379
|
||||
PAPERLESS_DBHOST: db
|
||||
env_file:
|
||||
- stack.env
|
||||
# The UID and GID of the user used to run paperless in the container. Set this
|
||||
# to your UID and GID on the host so that you have write access to the
|
||||
# consumption directory.
|
||||
USERMAP_UID: 1000
|
||||
USERMAP_GID: 100
|
||||
# Additional languages to install for text recognition, separated by a
|
||||
# whitespace. Note that this is
|
||||
# different from PAPERLESS_OCR_LANGUAGE (default=eng), which defines the
|
||||
# language used for OCR.
|
||||
# The container installs English, German, Italian, Spanish and French by
|
||||
# default.
|
||||
# See https://packages.debian.org/search?keywords=tesseract-ocr-&searchon=names&suite=buster
|
||||
# for available languages.
|
||||
#PAPERLESS_OCR_LANGUAGES: tur ces
|
||||
# Adjust this key if you plan to make paperless available publicly. It should
|
||||
# be a very long sequence of random characters. You don't need to remember it.
|
||||
#PAPERLESS_SECRET_KEY: change-me
|
||||
# Use this variable to set a timezone for the Paperless Docker containers. If not specified, defaults to UTC.
|
||||
#PAPERLESS_TIME_ZONE: America/Los_Angeles
|
||||
# The default language to use for OCR. Set this to the language most of your
|
||||
# documents are written in.
|
||||
#PAPERLESS_OCR_LANGUAGE: eng
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Docker Compose file for running paperless from the docker container registry.
|
||||
# docker-compose file for running paperless from the docker container registry.
|
||||
# This file contains everything paperless needs to run.
|
||||
# Paperless supports amd64, arm and arm64 hardware.
|
||||
#
|
||||
@ -10,7 +10,7 @@
|
||||
# as this file and mounted to the correct folders inside the container.
|
||||
# - Paperless listens on port 8000.
|
||||
#
|
||||
# In addition to that, this Docker Compose file adds the following optional
|
||||
# In addition to that, this docker-compose file adds the following optional
|
||||
# configurations:
|
||||
#
|
||||
# - Instead of SQLite (default), PostgreSQL is used as the database server.
|
||||
@ -23,20 +23,23 @@
|
||||
#
|
||||
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
|
||||
# and '.env' into a folder.
|
||||
# - Run 'docker compose pull'.
|
||||
# - Run 'docker compose up -d'.
|
||||
# - Run 'docker-compose pull'.
|
||||
# - Run 'docker-compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker-compose up -d'.
|
||||
#
|
||||
# For more extensive installation and update instructions, refer to the
|
||||
# documentation.
|
||||
|
||||
version: "3.4"
|
||||
services:
|
||||
broker:
|
||||
image: docker.io/library/redis:8
|
||||
image: docker.io/library/redis:7
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redisdata:/data
|
||||
|
||||
db:
|
||||
image: docker.io/library/postgres:17
|
||||
image: docker.io/library/postgres:13
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
@ -44,6 +47,7 @@ services:
|
||||
POSTGRES_DB: paperless
|
||||
POSTGRES_USER: paperless
|
||||
POSTGRES_PASSWORD: paperless
|
||||
|
||||
webserver:
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:latest
|
||||
restart: unless-stopped
|
||||
@ -53,7 +57,12 @@ services:
|
||||
- gotenberg
|
||||
- tika
|
||||
ports:
|
||||
- "8000:8000"
|
||||
- 8000:8000
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- data:/usr/src/paperless/data
|
||||
- media:/usr/src/paperless/media
|
||||
@ -66,18 +75,18 @@ services:
|
||||
PAPERLESS_TIKA_ENABLED: 1
|
||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:8.20
|
||||
image: docker.io/gotenberg/gotenberg:7.6
|
||||
restart: unless-stopped
|
||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||
# want to allow external content like tracking pixels or even javascript.
|
||||
command:
|
||||
- "gotenberg"
|
||||
- "--chromium-disable-javascript=true"
|
||||
- "--chromium-allow-list=file:///tmp/.*"
|
||||
- "--chromium-disable-routes=true"
|
||||
|
||||
tika:
|
||||
image: docker.io/apache/tika:latest
|
||||
image: ghcr.io/paperless-ngx/tika:latest
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Docker Compose file for running paperless from the Docker Hub.
|
||||
# docker-compose file for running paperless from the Docker Hub.
|
||||
# This file contains everything paperless needs to run.
|
||||
# Paperless supports amd64, arm and arm64 hardware.
|
||||
#
|
||||
@ -10,7 +10,7 @@
|
||||
# as this file and mounted to the correct folders inside the container.
|
||||
# - Paperless listens on port 8000.
|
||||
#
|
||||
# In addition to that, this Docker Compose file adds the following optional
|
||||
# In addition to that, this docker-compose file adds the following optional
|
||||
# configurations:
|
||||
#
|
||||
# - Instead of SQLite (default), PostgreSQL is used as the database server.
|
||||
@ -19,20 +19,23 @@
|
||||
#
|
||||
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
|
||||
# and '.env' into a folder.
|
||||
# - Run 'docker compose pull'.
|
||||
# - Run 'docker compose up -d'.
|
||||
# - Run 'docker-compose pull'.
|
||||
# - Run 'docker-compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker-compose up -d'.
|
||||
#
|
||||
# For more extensive installation and update instructions, refer to the
|
||||
# documentation.
|
||||
|
||||
version: "3.4"
|
||||
services:
|
||||
broker:
|
||||
image: docker.io/library/redis:8
|
||||
image: docker.io/library/redis:7
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redisdata:/data
|
||||
|
||||
db:
|
||||
image: docker.io/library/postgres:17
|
||||
image: docker.io/library/postgres:13
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
@ -40,6 +43,7 @@ services:
|
||||
POSTGRES_DB: paperless
|
||||
POSTGRES_USER: paperless
|
||||
POSTGRES_PASSWORD: paperless
|
||||
|
||||
webserver:
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:latest
|
||||
restart: unless-stopped
|
||||
@ -47,7 +51,12 @@ services:
|
||||
- db
|
||||
- broker
|
||||
ports:
|
||||
- "8000:8000"
|
||||
- 8000:8000
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- data:/usr/src/paperless/data
|
||||
- media:/usr/src/paperless/media
|
||||
@ -57,6 +66,8 @@ services:
|
||||
environment:
|
||||
PAPERLESS_REDIS: redis://broker:6379
|
||||
PAPERLESS_DBHOST: db
|
||||
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Docker Compose file for running paperless from the docker container registry.
|
||||
# docker-compose file for running paperless from the docker container registry.
|
||||
# This file contains everything paperless needs to run.
|
||||
# Paperless supports amd64, arm and arm64 hardware.
|
||||
# All compose files of paperless configure paperless in the following way:
|
||||
@ -11,7 +11,7 @@
|
||||
#
|
||||
# SQLite is used as the database. The SQLite file is stored in the data volume.
|
||||
#
|
||||
# In addition to that, this Docker Compose file adds the following optional
|
||||
# In addition to that, this docker-compose file adds the following optional
|
||||
# configurations:
|
||||
#
|
||||
# - Apache Tika and Gotenberg servers are started with paperless and paperless
|
||||
@ -23,18 +23,21 @@
|
||||
#
|
||||
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
|
||||
# and '.env' into a folder.
|
||||
# - Run 'docker compose pull'.
|
||||
# - Run 'docker compose up -d'.
|
||||
# - Run 'docker-compose pull'.
|
||||
# - Run 'docker-compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker-compose up -d'.
|
||||
#
|
||||
# For more extensive installation and update instructions, refer to the
|
||||
# documentation.
|
||||
|
||||
version: "3.4"
|
||||
services:
|
||||
broker:
|
||||
image: docker.io/library/redis:8
|
||||
image: docker.io/library/redis:7
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redisdata:/data
|
||||
|
||||
webserver:
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:latest
|
||||
restart: unless-stopped
|
||||
@ -43,7 +46,12 @@ services:
|
||||
- gotenberg
|
||||
- tika
|
||||
ports:
|
||||
- "8000:8000"
|
||||
- 8000:8000
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- data:/usr/src/paperless/data
|
||||
- media:/usr/src/paperless/media
|
||||
@ -55,18 +63,18 @@ services:
|
||||
PAPERLESS_TIKA_ENABLED: 1
|
||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:8.20
|
||||
image: docker.io/gotenberg/gotenberg:7.6
|
||||
restart: unless-stopped
|
||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||
# want to allow external content like tracking pixels or even javascript.
|
||||
command:
|
||||
- "gotenberg"
|
||||
- "--chromium-disable-javascript=true"
|
||||
- "--chromium-allow-list=file:///tmp/.*"
|
||||
- "--chromium-disable-routes=true"
|
||||
|
||||
tika:
|
||||
image: docker.io/apache/tika:latest
|
||||
image: ghcr.io/paperless-ngx/tika:latest
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Docker Compose file for running paperless from the Docker Hub.
|
||||
# docker-compose file for running paperless from the Docker Hub.
|
||||
# This file contains everything paperless needs to run.
|
||||
# Paperless supports amd64, arm and arm64 hardware.
|
||||
#
|
||||
@ -16,25 +16,33 @@
|
||||
#
|
||||
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
|
||||
# and '.env' into a folder.
|
||||
# - Run 'docker compose pull'.
|
||||
# - Run 'docker compose up -d'.
|
||||
# - Run 'docker-compose pull'.
|
||||
# - Run 'docker-compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker-compose up -d'.
|
||||
#
|
||||
# For more extensive installation and update instructions, refer to the
|
||||
# documentation.
|
||||
|
||||
version: "3.4"
|
||||
services:
|
||||
broker:
|
||||
image: docker.io/library/redis:8
|
||||
image: docker.io/library/redis:7
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redisdata:/data
|
||||
|
||||
webserver:
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- broker
|
||||
ports:
|
||||
- "8000:8000"
|
||||
- 8000:8000
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- data:/usr/src/paperless/data
|
||||
- media:/usr/src/paperless/media
|
||||
@ -43,6 +51,8 @@ services:
|
||||
env_file: docker-compose.env
|
||||
environment:
|
||||
PAPERLESS_REDIS: redis://broker:6379
|
||||
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
||||
|
193
docker/docker-entrypoint.sh
Executable file
193
docker/docker-entrypoint.sh
Executable file
@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# Adapted from:
|
||||
# https://github.com/docker-library/postgres/blob/master/docker-entrypoint.sh
|
||||
# usage: file_env VAR
|
||||
# ie: file_env 'XYZ_DB_PASSWORD' will allow for "$XYZ_DB_PASSWORD_FILE" to
|
||||
# fill in the value of "$XYZ_DB_PASSWORD" from a file, especially for Docker's
|
||||
# secrets feature
|
||||
file_env() {
|
||||
local -r var="$1"
|
||||
local -r fileVar="${var}_FILE"
|
||||
|
||||
# Basic validation
|
||||
if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
|
||||
echo >&2 "error: both $var and $fileVar are set (but are exclusive)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Only export var if the _FILE exists
|
||||
if [ "${!fileVar:-}" ]; then
|
||||
# And the file exists
|
||||
if [[ -f ${!fileVar} ]]; then
|
||||
echo "Setting ${var} from file"
|
||||
val="$(< "${!fileVar}")"
|
||||
export "$var"="$val"
|
||||
else
|
||||
echo "File ${!fileVar} doesn't exist"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
# Source: https://github.com/sameersbn/docker-gitlab/
|
||||
map_uidgid() {
|
||||
local -r usermap_original_uid=$(id -u paperless)
|
||||
local -r usermap_original_gid=$(id -g paperless)
|
||||
local -r usermap_new_uid=${USERMAP_UID:-$usermap_original_uid}
|
||||
local -r usermap_new_gid=${USERMAP_GID:-${usermap_original_gid:-$usermap_new_uid}}
|
||||
if [[ ${usermap_new_uid} != "${usermap_original_uid}" || ${usermap_new_gid} != "${usermap_original_gid}" ]]; then
|
||||
echo "Mapping UID and GID for paperless:paperless to $usermap_new_uid:$usermap_new_gid"
|
||||
usermod -o -u "${usermap_new_uid}" paperless
|
||||
groupmod -o -g "${usermap_new_gid}" paperless
|
||||
fi
|
||||
}
|
||||
|
||||
map_folders() {
|
||||
# Export these so they can be used in docker-prepare.sh
|
||||
export DATA_DIR="${PAPERLESS_DATA_DIR:-/usr/src/paperless/data}"
|
||||
export MEDIA_ROOT_DIR="${PAPERLESS_MEDIA_ROOT:-/usr/src/paperless/media}"
|
||||
export CONSUME_DIR="${PAPERLESS_CONSUMPTION_DIR:-/usr/src/paperless/consume}"
|
||||
}
|
||||
|
||||
nltk_data () {
|
||||
# Store the NLTK data outside the Docker container
|
||||
local -r nltk_data_dir="${DATA_DIR}/nltk"
|
||||
local -r truthy_things=("yes y 1 t true")
|
||||
|
||||
# If not set, or it looks truthy
|
||||
if [[ -z "${PAPERLESS_ENABLE_NLTK}" ]] || [[ "${truthy_things[*]}" =~ ${PAPERLESS_ENABLE_NLTK,} ]]; then
|
||||
|
||||
# Download or update the snowball stemmer data
|
||||
python3 -W ignore::RuntimeWarning -m nltk.downloader -d "${nltk_data_dir}" snowball_data
|
||||
|
||||
# Download or update the stopwords corpus
|
||||
python3 -W ignore::RuntimeWarning -m nltk.downloader -d "${nltk_data_dir}" stopwords
|
||||
|
||||
# Download or update the punkt tokenizer data
|
||||
python3 -W ignore::RuntimeWarning -m nltk.downloader -d "${nltk_data_dir}" punkt
|
||||
|
||||
else
|
||||
echo "Skipping NLTK data download"
|
||||
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
initialize() {
|
||||
|
||||
# Setup environment from secrets before anything else
|
||||
for env_var in \
|
||||
PAPERLESS_DBUSER \
|
||||
PAPERLESS_DBPASS \
|
||||
PAPERLESS_SECRET_KEY \
|
||||
PAPERLESS_AUTO_LOGIN_USERNAME \
|
||||
PAPERLESS_ADMIN_USER \
|
||||
PAPERLESS_ADMIN_MAIL \
|
||||
PAPERLESS_ADMIN_PASSWORD \
|
||||
PAPERLESS_REDIS; do
|
||||
# Check for a version of this var with _FILE appended
|
||||
# and convert the contents to the env var value
|
||||
file_env ${env_var}
|
||||
done
|
||||
|
||||
# Change the user and group IDs if needed
|
||||
map_uidgid
|
||||
|
||||
# Check for overrides of certain folders
|
||||
map_folders
|
||||
|
||||
local -r export_dir="/usr/src/paperless/export"
|
||||
|
||||
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}"; do
|
||||
if [[ ! -d "${dir}" ]]; then
|
||||
echo "Creating directory ${dir}"
|
||||
mkdir "${dir}"
|
||||
fi
|
||||
done
|
||||
|
||||
local -r tmp_dir="/tmp/paperless"
|
||||
echo "Creating directory ${tmp_dir}"
|
||||
mkdir -p "${tmp_dir}"
|
||||
|
||||
nltk_data
|
||||
|
||||
set +e
|
||||
echo "Adjusting permissions of paperless files. This may take a while."
|
||||
chown -R paperless:paperless ${tmp_dir}
|
||||
for dir in \
|
||||
"${export_dir}" \
|
||||
"${DATA_DIR}" \
|
||||
"${MEDIA_ROOT_DIR}" \
|
||||
"${CONSUME_DIR}"; do
|
||||
find "${dir}" -not \( -user paperless -and -group paperless \) -exec chown paperless:paperless {} +
|
||||
done
|
||||
set -e
|
||||
|
||||
"${gosu_cmd[@]}" /sbin/docker-prepare.sh
|
||||
}
|
||||
|
||||
install_languages() {
|
||||
echo "Installing languages..."
|
||||
|
||||
read -ra langs <<<"$1"
|
||||
|
||||
# Check that it is not empty
|
||||
if [ ${#langs[@]} -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
apt-get update
|
||||
|
||||
for lang in "${langs[@]}"; do
|
||||
pkg="tesseract-ocr-$lang"
|
||||
# English is installed by default
|
||||
#if [[ "$lang" == "eng" ]]; then
|
||||
# continue
|
||||
#fi
|
||||
|
||||
if dpkg -s "$pkg" &>/dev/null; then
|
||||
echo "Package $pkg already installed!"
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! apt-cache show "$pkg" &>/dev/null; then
|
||||
echo "Package $pkg not found! :("
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Installing package $pkg..."
|
||||
if ! apt-get -y install "$pkg" &>/dev/null; then
|
||||
echo "Could not install $pkg"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
echo "Paperless-ngx docker container starting..."
|
||||
|
||||
gosu_cmd=(gosu paperless)
|
||||
if [ "$(id -u)" == "$(id -u paperless)" ]; then
|
||||
gosu_cmd=()
|
||||
fi
|
||||
|
||||
# Install additional languages if specified
|
||||
if [[ -n "$PAPERLESS_OCR_LANGUAGES" ]]; then
|
||||
install_languages "$PAPERLESS_OCR_LANGUAGES"
|
||||
fi
|
||||
|
||||
initialize
|
||||
|
||||
if [[ "$1" != "/"* ]]; then
|
||||
echo Executing management command "$@"
|
||||
exec "${gosu_cmd[@]}" python3 manage.py "$@"
|
||||
else
|
||||
echo Executing "$@"
|
||||
exec "$@"
|
||||
fi
|
152
docker/docker-prepare.sh
Executable file
152
docker/docker-prepare.sh
Executable file
@ -0,0 +1,152 @@
|
||||
#!/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 -h ${host} -p ${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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
) 200>"${DATA_DIR}/migration_lock"
|
||||
}
|
||||
|
||||
search_index() {
|
||||
|
||||
local -r index_version=1
|
||||
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
|
||||
}
|
||||
|
||||
custom_container_init() {
|
||||
# Mostly borrowed from the LinuxServer.io base image
|
||||
# https://github.com/linuxserver/docker-baseimage-ubuntu/tree/bionic/root/etc/cont-init.d
|
||||
local -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 "**** Potential tampering with custom scripts detected ****"
|
||||
echo "**** The folder '${custom_script_dir}' must be owned by root ****"
|
||||
return 0
|
||||
fi
|
||||
if [ -n "$(/usr/bin/find "${custom_script_dir}" -maxdepth 1 -perm -o+w)" ]; then
|
||||
echo "**** The folder '${custom_script_dir}' or some of contents have write permissions for others, which is a security risk. ****"
|
||||
echo "**** Please review the permissions and their contents to make sure they are owned by root, and can only be modified by root. ****"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Make sure custom init directory has files in it
|
||||
if [ -n "$(/bin/ls -A "${custom_script_dir}" 2>/dev/null)" ]; then
|
||||
echo "[custom-init] 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 "[custom-init] ${NAME}: executing..."
|
||||
/bin/bash "${SCRIPT}"
|
||||
echo "[custom-init] ${NAME}: exited $?"
|
||||
elif [ ! -f "${SCRIPT}" ]; then
|
||||
echo "[custom-init] ${NAME}: is not a file"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "[custom-init] no custom files found exiting..."
|
||||
fi
|
||||
|
||||
fi
|
||||
}
|
||||
|
||||
do_work() {
|
||||
if [[ "${PAPERLESS_DBENGINE}" == "mariadb" ]]; then
|
||||
wait_for_mariadb
|
||||
elif [[ -n "${PAPERLESS_DBHOST}" ]]; then
|
||||
wait_for_postgres
|
||||
fi
|
||||
|
||||
wait_for_redis
|
||||
|
||||
migrations
|
||||
|
||||
search_index
|
||||
|
||||
superuser
|
||||
|
||||
# Leave this last thing
|
||||
custom_container_init
|
||||
|
||||
}
|
||||
|
||||
do_work
|
7
docker/flower-conditional.sh
Normal file
7
docker/flower-conditional.sh
Normal file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "Checking if we should start flower..."
|
||||
|
||||
if [[ -n "${PAPERLESS_ENABLE_FLOWER}" ]]; then
|
||||
celery --app paperless flower
|
||||
fi
|
Binary file not shown.
Before Width: | Height: | Size: 30 KiB |
@ -1,7 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Run this script to generate the management commands again (for example if a new command is create or the template is updated)
|
||||
|
||||
set -eu
|
||||
|
||||
for command in decrypt_documents \
|
||||
@ -15,13 +13,9 @@ for command in decrypt_documents \
|
||||
document_retagger \
|
||||
document_thumbnails \
|
||||
document_sanity_checker \
|
||||
document_fuzzy_match \
|
||||
manage_superuser \
|
||||
convert_mariadb_uuid \
|
||||
prune_audit_logs \
|
||||
createsuperuser;
|
||||
manage_superuser;
|
||||
do
|
||||
echo "installing $command..."
|
||||
sed "s/management_command/$command/g" management_script.sh >"$PWD/rootfs/usr/local/bin/$command"
|
||||
chmod u=rwx,g=rwx,o=rx "$PWD/rootfs/usr/local/bin/$command"
|
||||
sed "s/management_command/$command/g" management_script.sh > /usr/local/bin/$command
|
||||
chmod +x /usr/local/bin/$command
|
||||
done
|
||||
|
@ -1,13 +1,14 @@
|
||||
#!/command/with-contenv /usr/bin/bash
|
||||
# shellcheck shell=bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
cd "${PAPERLESS_SRC_DIR}"
|
||||
cd /usr/src/paperless/src/
|
||||
|
||||
if [[ $(id -u) == 0 ]]; then
|
||||
s6-setuidgid paperless python3 manage.py management_command "$@"
|
||||
elif [[ $(id -un) == "paperless" ]]; then
|
||||
if [[ $(id -u) == 0 ]] ;
|
||||
then
|
||||
gosu paperless python3 manage.py management_command "$@"
|
||||
elif [[ $(id -un) == "paperless" ]] ;
|
||||
then
|
||||
python3 manage.py management_command "$@"
|
||||
else
|
||||
echo "Unknown user."
|
||||
|
15
docker/paperless_cmd.sh
Executable file
15
docker/paperless_cmd.sh
Executable file
@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
rootless_args=()
|
||||
if [ "$(id -u)" == "$(id -u paperless)" ]; then
|
||||
rootless_args=(
|
||||
--user
|
||||
paperless
|
||||
--logfile
|
||||
supervisord.log
|
||||
--pidfile
|
||||
supervisord.pid
|
||||
)
|
||||
fi
|
||||
|
||||
exec /usr/local/bin/supervisord -c /etc/supervisord.conf "${rootless_args[@]}"
|
@ -1,8 +0,0 @@
|
||||
#!/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"
|
@ -1 +0,0 @@
|
||||
oneshot
|
@ -1 +0,0 @@
|
||||
/etc/s6-overlay/s6-rc.d/init-complete/run
|
@ -1,44 +0,0 @@
|
||||
#!/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
|
@ -1 +0,0 @@
|
||||
oneshot
|
@ -1 +0,0 @@
|
||||
/etc/s6-overlay/s6-rc.d/init-custom-init/run
|
@ -1,33 +0,0 @@
|
||||
#!/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/}
|
||||
if [[ $(tail -n1 "${SECRETFILE}" | wc -l) != 0 ]]; then
|
||||
echo "${log_prefix} Your secret: ${FILENAME##*/} contains a trailing newline and may not work as expected"
|
||||
fi
|
||||
# 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
|
@ -1 +0,0 @@
|
||||
oneshot
|
@ -1 +0,0 @@
|
||||
/etc/s6-overlay/s6-rc.d/init-env-file/run
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user