diff --git a/.github/workflows/ci-backend.yml b/.github/workflows/ci-backend.yml new file mode 100644 index 000000000..98c10396c --- /dev/null +++ b/.github/workflows/ci-backend.yml @@ -0,0 +1,104 @@ +name: Backend Tests +on: + push: + branches-ignore: + - 'translations**' + paths: + - 'src/**' + - 'pyproject.toml' + - 'uv.lock' + - 'docker/compose/docker-compose.ci-test.yml' + - '.github/workflows/ci-backend.yml' + pull_request: + branches-ignore: + - 'translations**' + paths: + - 'src/**' + - 'pyproject.toml' + - 'uv.lock' + - 'docker/compose/docker-compose.ci-test.yml' + - '.github/workflows/ci-backend.yml' + workflow_dispatch: +concurrency: + group: backend-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +env: + DEFAULT_UV_VERSION: "0.9.x" + NLTK_DATA: "/usr/share/nltk_data" +jobs: + test: + name: "Python ${{ matrix.python-version }}" + runs-on: ubuntu-24.04 + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12'] + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Start containers + run: | + docker compose --file docker/compose/docker-compose.ci-test.yml pull --quiet + docker compose --file docker/compose/docker-compose.ci-test.yml up --detach + - name: Set up Python + id: setup-python + uses: actions/setup-python@v6 + with: + python-version: "${{ matrix.python-version }}" + - name: Install uv + uses: astral-sh/setup-uv@v7 + 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: Install NLTK data + run: | + uv run python -m nltk.downloader punkt punkt_tab snowball_data stopwords -d ${{ env.NLTK_DATA }} + - name: Run tests + env: + NLTK_DATA: ${{ env.NLTK_DATA }} + PAPERLESS_CI_TEST: 1 + 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 test results to Codecov + if: always() + uses: codecov/codecov-action@v5 + with: + flags: backend-python-${{ matrix.python-version }} + files: junit.xml + report_type: test_results + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + flags: backend-python-${{ matrix.python-version }} + files: coverage.xml + report_type: coverage + - name: Stop containers + if: always() + run: | + docker compose --file docker/compose/docker-compose.ci-test.yml logs + docker compose --file docker/compose/docker-compose.ci-test.yml down diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml new file mode 100644 index 000000000..5793ecfa1 --- /dev/null +++ b/.github/workflows/ci-docker.yml @@ -0,0 +1,233 @@ +name: Docker Build +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + - 'v[0-9]+.[0-9]+.[0-9]+-beta.rc[0-9]+' + branches: + - dev + - beta + pull_request: + branches: + - dev + - main + workflow_dispatch: +concurrency: + group: docker-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +env: + REGISTRY: ghcr.io +jobs: + build-arch: + name: Build ${{ matrix.arch }} + strategy: + fail-fast: false + matrix: + include: + - runner: ubuntu-24.04 + arch: amd64 + platform: linux/amd64 + - runner: ubuntu-24.04-arm + arch: arm64 + platform: linux/arm64 + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + outputs: + can-push: ${{ steps.check-push.outputs.can-push }} + push-external: ${{ steps.check-push.outputs.push-external }} + repository: ${{ steps.repo.outputs.name }} + ref-name: ${{ steps.ref.outputs.name }} + steps: + - name: Checkout + uses: actions/checkout@v6.0.1 + - name: Determine ref name + id: ref + run: | + ref_name="${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}" + # Sanitize by replacing / with - for cache keys + cache_ref="${ref_name//\//-}" + + echo "ref_name=${ref_name}" + echo "cache_ref=${cache_ref}" + + echo "name=${ref_name}" >> $GITHUB_OUTPUT + echo "cache-ref=${cache_ref}" >> $GITHUB_OUTPUT + - name: Check push permissions + id: check-push + env: + REF_NAME: ${{ steps.ref.outputs.name }} + run: | + # can-push: Can we push to GHCR? + # True for: pushes, or PRs from the same repo (not forks) + can_push=${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }} + echo "can-push=${can_push}" + echo "can-push=${can_push}" >> $GITHUB_OUTPUT + + # push-external: Should we also push to Docker Hub and Quay.io? + # Only for main repo on dev/beta branches or version tags + push_external="false" + if [[ "${can_push}" == "true" && "${{ github.repository_owner }}" == "paperless-ngx" ]]; then + case "${REF_NAME}" in + dev|beta) + push_external="true" + ;; + esac + case "${{ github.ref }}" in + refs/tags/v*|*beta.rc*) + push_external="true" + ;; + esac + fi + echo "push-external=${push_external}" + echo "push-external=${push_external}" >> $GITHUB_OUTPUT + - name: Set repository name + id: repo + run: | + repo_name="${{ github.repository }}" + repo_name="${repo_name,,}" + + echo "repository=${repo_name}" + echo "name=${repo_name}" >> $GITHUB_OUTPUT + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.12.0 + - name: Login to GitHub Container Registry + uses: docker/login-action@v3.6.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Docker metadata + id: docker-meta + uses: docker/metadata-action@v5.10.0 + with: + images: | + ${{ env.REGISTRY }}/${{ steps.repo.outputs.name }} + tags: | + type=ref,event=branch + type=raw,value=${{ steps.ref.outputs.name }},enable=${{ github.event_name == 'pull_request' }} + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + - name: Build and push by digest + id: build + uses: docker/build-push-action@v6.18.0 + with: + context: . + file: ./Dockerfile + platforms: ${{ matrix.platform }} + labels: ${{ steps.docker-meta.outputs.labels }} + build-args: | + PNGX_TAG_VERSION=${{ steps.docker-meta.outputs.version }} + outputs: type=image,name=${{ env.REGISTRY }}/${{ steps.repo.outputs.name }},push-by-digest=true,name-canonical=true,push=${{ steps.check-push.outputs.can-push }} + cache-from: | + type=registry,ref=${{ env.REGISTRY }}/${{ steps.repo.outputs.name }}/cache/app:${{ steps.ref.outputs.cache-ref }}-${{ matrix.arch }} + type=registry,ref=${{ env.REGISTRY }}/${{ steps.repo.outputs.name }}/cache/app:dev-${{ matrix.arch }} + cache-to: ${{ steps.check-push.outputs.can-push == 'true' && format('type=registry,mode=max,ref={0}/{1}/cache/app:{2}-{3}', env.REGISTRY, steps.repo.outputs.name, steps.ref.outputs.cache-ref, matrix.arch) || '' }} + - name: Export digest + if: steps.check-push.outputs.can-push == 'true' + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + echo "digest=${digest}" + touch "/tmp/digests/${digest#sha256:}" + - name: Upload digest + if: steps.check-push.outputs.can-push == 'true' + uses: actions/upload-artifact@v6.0.0 + with: + name: digests-${{ matrix.arch }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + merge-and-push: + name: Merge and Push Manifest + runs-on: ubuntu-24.04 + needs: build-arch + if: needs.build-arch.outputs.can-push == 'true' + permissions: + contents: read + packages: write + steps: + - name: Download digests + uses: actions/download-artifact@v7.0.0 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + - name: List digests + run: | + echo "Downloaded digests:" + ls -la /tmp/digests/ + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.12.0 + - name: Login to GitHub Container Registry + uses: docker/login-action@v3.6.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Login to Docker Hub + if: needs.build-arch.outputs.push-external == 'true' + uses: docker/login-action@v3.6.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to Quay.io + if: needs.build-arch.outputs.push-external == 'true' + uses: docker/login-action@v3.6.0 + with: + registry: quay.io + username: ${{ secrets.QUAY_USERNAME }} + password: ${{ secrets.QUAY_ROBOT_TOKEN }} + - name: Docker metadata + id: docker-meta + uses: docker/metadata-action@v5.10.0 + with: + images: | + ${{ env.REGISTRY }}/${{ needs.build-arch.outputs.repository }} + tags: | + type=ref,event=branch + type=raw,value=${{ needs.build-arch.outputs.ref-name }},enable=${{ github.event_name == 'pull_request' }} + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + - name: Create manifest list and push + working-directory: /tmp/digests + env: + REPOSITORY: ${{ needs.build-arch.outputs.repository }} + run: | + tags=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "${DOCKER_METADATA_OUTPUT_JSON}") + + digests="" + for digest in *; do + digests+="${{ env.REGISTRY }}/${REPOSITORY}@sha256:${digest} " + done + + echo "Creating manifest with tags: ${tags}" + echo "From digests: ${digests}" + + docker buildx imagetools create ${tags} ${digests} + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ fromJSON(steps.docker-meta.outputs.json).tags[0] }} + - name: Copy to Docker Hub + if: needs.build-arch.outputs.push-external == 'true' + env: + TAGS: ${{ steps.docker-meta.outputs.tags }} + GHCR_REPO: ${{ env.REGISTRY }}/${{ needs.build-arch.outputs.repository }} + run: | + for tag in ${TAGS}; do + dockerhub_tag="${tag/${GHCR_REPO}/docker.io/paperlessngx/paperless-ngx}" + echo "Copying ${tag} to ${dockerhub_tag}" + skopeo copy --all "docker://${tag}" "docker://${dockerhub_tag}" + done + - name: Copy to Quay.io + if: needs.build-arch.outputs.push-external == 'true' + env: + TAGS: ${{ steps.docker-meta.outputs.tags }} + GHCR_REPO: ${{ env.REGISTRY }}/${{ needs.build-arch.outputs.repository }} + run: | + for tag in ${TAGS}; do + quay_tag="${tag/${GHCR_REPO}/quay.io/paperlessngx/paperless-ngx}" + echo "Copying ${tag} to ${quay_tag}" + skopeo copy --all "docker://${tag}" "docker://${quay_tag}" + done diff --git a/.github/workflows/ci-docs.yml b/.github/workflows/ci-docs.yml new file mode 100644 index 000000000..4c7cf453c --- /dev/null +++ b/.github/workflows/ci-docs.yml @@ -0,0 +1,88 @@ +name: Documentation +on: + push: + branches: + - main + - dev + paths: + - 'docs/**' + - 'mkdocs.yml' + - '.github/workflows/ci-docs.yml' + pull_request: + paths: + - 'docs/**' + - 'mkdocs.yml' + - '.github/workflows/ci-docs.yml' + workflow_dispatch: +concurrency: + group: docs-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +env: + DEFAULT_UV_VERSION: "0.9.x" + DEFAULT_PYTHON_VERSION: "3.11" +jobs: + build: + name: Build Documentation + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Set up Python + id: setup-python + uses: actions/setup-python@v6 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: ${{ env.DEFAULT_UV_VERSION }} + enable-cache: true + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + - name: Install Python dependencies + run: | + uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen + - name: Build documentation + run: | + uv run \ + --python ${{ steps.setup-python.outputs.python-version }} \ + --dev \ + --frozen \ + mkdocs build --config-file ./mkdocs.yml + - name: Upload artifact + uses: actions/upload-artifact@v6 + with: + name: documentation + path: site/ + retention-days: 7 + deploy: + name: Deploy Documentation + needs: build + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Set up Python + id: setup-python + uses: actions/setup-python@v6 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: ${{ env.DEFAULT_UV_VERSION }} + enable-cache: true + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + - name: Install Python dependencies + run: | + uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen + - name: Deploy 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 diff --git a/.github/workflows/ci-frontend.yml b/.github/workflows/ci-frontend.yml new file mode 100644 index 000000000..b66a6dd77 --- /dev/null +++ b/.github/workflows/ci-frontend.yml @@ -0,0 +1,189 @@ +name: Frontend Tests +on: + push: + branches-ignore: + - 'translations**' + paths: + - 'src-ui/**' + - '.github/workflows/ci-frontend.yml' + pull_request: + branches-ignore: + - 'translations**' + paths: + - 'src-ui/**' + - '.github/workflows/ci-frontend.yml' + workflow_dispatch: +concurrency: + group: frontend-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +jobs: + install-dependencies: + name: Install Dependencies + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Use Node.js 20 + uses: actions/setup-node@v6 + 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@v5 + with: + path: | + ~/.pnpm-store + ~/.cache + key: ${{ runner.os }}-frontend-${{ hashFiles('src-ui/pnpm-lock.yaml') }} + - name: Install dependencies + run: cd src-ui && pnpm install + lint: + name: Lint + needs: install-dependencies + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Use Node.js 20 + uses: actions/setup-node@v6 + with: + node-version: 20.x + cache: 'pnpm' + cache-dependency-path: 'src-ui/pnpm-lock.yaml' + - name: Cache frontend dependencies + uses: actions/cache@v5 + with: + path: | + ~/.pnpm-store + ~/.cache + key: ${{ runner.os }}-frontend-${{ hashFiles('src-ui/pnpm-lock.yaml') }} + - name: Re-link Angular CLI + run: cd src-ui && pnpm link @angular/cli + - name: Run lint + run: cd src-ui && pnpm run lint + unit-tests: + name: "Unit Tests (${{ matrix.shard-index }}/${{ matrix.shard-count }})" + needs: install-dependencies + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + node-version: [20.x] + shard-index: [1, 2, 3, 4] + shard-count: [4] + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Use Node.js 20 + uses: actions/setup-node@v6 + with: + node-version: 20.x + cache: 'pnpm' + cache-dependency-path: 'src-ui/pnpm-lock.yaml' + - name: Cache frontend dependencies + uses: actions/cache@v5 + with: + path: | + ~/.pnpm-store + ~/.cache + key: ${{ runner.os }}-frontend-${{ hashFiles('src-ui/pnpm-lock.yaml') }} + - name: Re-link Angular CLI + run: cd src-ui && pnpm link @angular/cli + - name: Run Jest unit tests + run: cd src-ui && pnpm run test --max-workers=2 --shard=${{ matrix.shard-index }}/${{ matrix.shard-count }} + - name: Upload test results to Codecov + if: always() + uses: codecov/codecov-action@v5 + with: + flags: frontend-node-${{ matrix.node-version }} + directory: src-ui/ + report_type: test_results + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + flags: frontend-node-${{ matrix.node-version }} + directory: src-ui/coverage/ + e2e-tests: + name: "E2E Tests (${{ matrix.shard-index }}/${{ matrix.shard-count }})" + needs: install-dependencies + runs-on: ubuntu-24.04 + container: mcr.microsoft.com/playwright:v1.57.0-noble + env: + PLAYWRIGHT_BROWSERS_PATH: /ms-playwright + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + strategy: + fail-fast: false + matrix: + node-version: [20.x] + shard-index: [1, 2] + shard-count: [2] + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Use Node.js 20 + uses: actions/setup-node@v6 + with: + node-version: 20.x + cache: 'pnpm' + cache-dependency-path: 'src-ui/pnpm-lock.yaml' + - name: Cache frontend dependencies + uses: actions/cache@v5 + with: + path: | + ~/.pnpm-store + ~/.cache + key: ${{ runner.os }}-frontend-${{ hashFiles('src-ui/pnpm-lock.yaml') }} + - name: Re-link Angular CLI + run: cd src-ui && pnpm link @angular/cli + - name: Install dependencies + run: cd src-ui && pnpm install --no-frozen-lockfile + - name: Run Playwright E2E tests + run: cd src-ui && pnpm exec playwright test --shard ${{ matrix.shard-index }}/${{ matrix.shard-count }} + bundle-analysis: + name: Bundle Analysis + needs: [unit-tests, e2e-tests] + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Use Node.js 20 + uses: actions/setup-node@v6 + with: + node-version: 20.x + cache: 'pnpm' + cache-dependency-path: 'src-ui/pnpm-lock.yaml' + - name: Cache frontend dependencies + uses: actions/cache@v5 + with: + path: | + ~/.pnpm-store + ~/.cache + key: ${{ runner.os }}-frontend-${{ hashFiles('src-ui/pnpm-lock.yaml') }} + - name: Re-link Angular CLI + run: cd src-ui && pnpm link @angular/cli + - name: Build and analyze + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: cd src-ui && pnpm run build --configuration=production diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml new file mode 100644 index 000000000..91f0b0e33 --- /dev/null +++ b/.github/workflows/ci-lint.yml @@ -0,0 +1,24 @@ +name: Lint +on: + push: + branches-ignore: + - 'translations**' + pull_request: + branches-ignore: + - 'translations**' +concurrency: + group: lint-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +jobs: + pre-commit: + name: Pre-commit Checks + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Install Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + - name: Run pre-commit + uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci-release.yml new file mode 100644 index 000000000..3f1a58e02 --- /dev/null +++ b/.github/workflows/ci-release.yml @@ -0,0 +1,237 @@ +name: Release +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + - 'v[0-9]+.[0-9]+.[0-9]+-beta.rc[0-9]+' +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false +env: + DEFAULT_UV_VERSION: "0.9.x" + DEFAULT_PYTHON_VERSION: "3.11" +jobs: + wait-for-docker: + name: Wait for Docker Build + runs-on: ubuntu-24.04 + steps: + - name: Wait for Docker build + uses: lewagon/wait-on-check-action@v1.4.1 + with: + ref: ${{ github.sha }} + check-name: 'Build Docker Image' + repo-token: ${{ secrets.GITHUB_TOKEN }} + wait-interval: 60 + build-release: + name: Build Release + needs: wait-for-docker + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v6 + # ---- Frontend Build ---- + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Use Node.js 20 + uses: actions/setup-node@v6 + with: + node-version: 20.x + cache: 'pnpm' + cache-dependency-path: 'src-ui/pnpm-lock.yaml' + - name: Install frontend dependencies + run: cd src-ui && pnpm install + - name: Build frontend + run: cd src-ui && pnpm run build --configuration production + # ---- Backend Setup ---- + - name: Set up Python + id: setup-python + uses: actions/setup-python@v6 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: ${{ env.DEFAULT_UV_VERSION }} + enable-cache: true + python-version: ${{ steps.setup-python.outputs.python-version }} + - name: Install Python dependencies + run: | + uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen + - name: Install system dependencies + run: | + sudo apt-get update -qq + sudo apt-get install -qq --no-install-recommends gettext liblept5 + # ---- Build Documentation ---- + - name: Build documentation + run: | + uv run \ + --python ${{ steps.setup-python.outputs.python-version }} \ + --dev \ + --frozen \ + mkdocs build --config-file ./mkdocs.yml + # ---- Prepare Release ---- + - name: Generate requirements file + run: | + uv export --quiet --no-dev --all-extras --format requirements-txt --output-file requirements.txt + - name: Compile messages + run: | + cd src/ + uv run \ + --python ${{ steps.setup-python.outputs.python-version }} \ + manage.py compilemessages + - name: Collect static files + run: | + cd src/ + uv run \ + --python ${{ steps.setup-python.outputs.python-version }} \ + manage.py collectstatic --no-input --clear + - name: Assemble release package + run: | + mkdir -p dist/paperless-ngx/scripts + + 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 dist/paperless-ngx/paperless.conf.example dist/paperless-ngx/paperless.conf + + cp --recursive docker/ dist/paperless-ngx/docker + cp scripts/*.service scripts/*.sh scripts/*.socket dist/paperless-ngx/scripts/ + cp --recursive src/ dist/paperless-ngx/src + cp --recursive site/ dist/paperless-ngx/docs + mv static dist/paperless-ngx/ + + find dist/paperless-ngx -name "__pycache__" -type d -exec rm -rf {} + + - name: Create release archive + run: | + 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@v6 + with: + name: release + path: dist/paperless-ngx.tar.xz + retention-days: 7 + publish-release: + name: Publish Release + needs: build-release + runs-on: ubuntu-24.04 + outputs: + prerelease: ${{ steps.get-version.outputs.prerelease }} + changelog: ${{ steps.create-release.outputs.body }} + version: ${{ steps.get-version.outputs.version }} + steps: + - name: Download release artifact + uses: actions/download-artifact@v7 + with: + name: release + path: ./ + - name: Get version info + id: get-version + run: | + echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT + if [[ "${{ github.ref_name }}" == *"-beta.rc"* ]]; then + echo "prerelease=true" >> $GITHUB_OUTPUT + else + echo "prerelease=false" >> $GITHUB_OUTPUT + fi + - name: Create release and changelog + id: create-release + uses: release-drafter/release-drafter@v6 + with: + name: Paperless-ngx ${{ steps.get-version.outputs.version }} + tag: ${{ steps.get-version.outputs.version }} + version: ${{ steps.get-version.outputs.version }} + prerelease: ${{ steps.get-version.outputs.prerelease }} + publish: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload release archive + uses: shogo82148/actions-upload-release-asset@v1 + 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 to docs (only on non-prerelease) + # --------------------------------------------------------------------------- + append-changelog: + name: Append Changelog + needs: publish-release + if: needs.publish-release.outputs.prerelease == 'false' + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: main + - name: Set up Python + id: setup-python + uses: actions/setup-python@v6 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: ${{ env.DEFAULT_UV_VERSION }} + enable-cache: true + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + - name: Update changelog + working-directory: docs + run: | + git branch ${{ needs.publish-release.outputs.version }}-changelog + 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 + + 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 + + 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@v8 + with: + script: | + const { repo, owner } = context.repo; + const result = await github.rest.pulls.create({ + title: 'Documentation: Add ${{ needs.publish-release.outputs.version }} changelog', + owner, + repo, + head: '${{ needs.publish-release.outputs.version }}-changelog', + base: 'main', + body: 'This PR is auto-generated by CI.' + }); + github.rest.issues.addLabels({ + owner, + repo, + issue_number: result.data.number, + labels: ['documentation', 'skip-changelog'] + }); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 63ec57f16..000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,699 +0,0 @@ -name: ci -on: - push: - tags: - # https://semver.org/#spec-item-2 - - 'v[0-9]+.[0-9]+.[0-9]+' - # https://semver.org/#spec-item-9 - - 'v[0-9]+.[0-9]+.[0-9]+-beta.rc[0-9]+' - branches-ignore: - - 'translations**' - pull_request: - branches-ignore: - - 'translations**' -env: - DEFAULT_UV_VERSION: "0.9.x" - # This is the default version of Python to use in most steps which aren't specific - DEFAULT_PYTHON_VERSION: "3.11" - NLTK_DATA: "/usr/share/nltk_data" -jobs: - detect-duplicate: - name: Detect Duplicate Run - runs-on: ubuntu-24.04 - outputs: - should_run: ${{ steps.check.outputs.should_run }} - steps: - - name: Check if workflow should run - id: check - uses: actions/github-script@v8 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - if (context.eventName !== 'push') { - core.info('Not a push event; running workflow.'); - core.setOutput('should_run', 'true'); - return; - } - - const ref = context.ref || ''; - if (!ref.startsWith('refs/heads/')) { - core.info('Push is not to a branch; running workflow.'); - core.setOutput('should_run', 'true'); - return; - } - - const branch = ref.substring('refs/heads/'.length); - const { owner, repo } = context.repo; - const prs = await github.paginate(github.rest.pulls.list, { - owner, - repo, - state: 'open', - head: `${owner}:${branch}`, - per_page: 100, - }); - - if (prs.length === 0) { - core.info(`No open PR found for ${branch}; running workflow.`); - core.setOutput('should_run', 'true'); - } else { - core.info(`Found ${prs.length} open PR(s) for ${branch}; skipping duplicate push run.`); - core.setOutput('should_run', 'false'); - } - pre-commit: - needs: - - detect-duplicate - if: needs.detect-duplicate.outputs.should_run == 'true' - name: Linting Checks - runs-on: ubuntu-24.04 - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - name: Install python - uses: actions/setup-python@v6 - with: - python-version: ${{ env.DEFAULT_PYTHON_VERSION }} - - name: Check files - uses: pre-commit/action@v3.0.1 - documentation: - name: "Build & Deploy Documentation" - runs-on: ubuntu-24.04 - needs: - - pre-commit - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Set up Python - id: setup-python - uses: actions/setup-python@v6 - with: - python-version: ${{ env.DEFAULT_PYTHON_VERSION }} - - name: Install uv - uses: astral-sh/setup-uv@v7 - with: - version: ${{ env.DEFAULT_UV_VERSION }} - enable-cache: true - python-version: ${{ env.DEFAULT_PYTHON_VERSION }} - - name: Install Python dependencies - run: | - uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen - - name: Make documentation - 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' - 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@v5 - with: - name: documentation - path: site/ - retention-days: 7 - tests-backend: - name: "Backend Tests (Python ${{ matrix.python-version }})" - runs-on: ubuntu-24.04 - needs: - - pre-commit - strategy: - matrix: - python-version: ['3.10', '3.11', '3.12'] - fail-fast: false - steps: - - name: Checkout - uses: actions/checkout@v6 - - 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@v6 - with: - python-version: "${{ matrix.python-version }}" - - name: Install uv - uses: astral-sh/setup-uv@v7 - 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: Install or update NLTK dependencies - run: uv run python -m nltk.downloader punkt punkt_tab snowball_data stopwords -d ${{ env.NLTK_DATA }} - - name: Tests - env: - NLTK_DATA: ${{ env.NLTK_DATA }} - PAPERLESS_CI_TEST: 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/codecov-action@v5 - with: - flags: backend-python-${{ matrix.python-version }} - files: junit.xml - report_type: test_results - - name: Upload backend coverage to Codecov - uses: codecov/codecov-action@v5 - with: - flags: backend-python-${{ matrix.python-version }} - files: coverage.xml - - name: Stop containers - if: always() - 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 - needs: - - pre-commit - steps: - - uses: actions/checkout@v6 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - name: Use Node.js 20 - uses: actions/setup-node@v6 - 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] - steps: - - uses: actions/checkout@v6 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - name: Use Node.js 20 - uses: actions/setup-node@v6 - 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 - if: always() - uses: codecov/codecov-action@v5 - with: - flags: frontend-node-${{ matrix.node-version }} - directory: src-ui/ - report_type: test_results - - name: Upload frontend coverage to Codecov - uses: codecov/codecov-action@v5 - with: - 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 - container: mcr.microsoft.com/playwright:v1.57.0-noble - needs: - - install-frontend-dependencies - env: - PLAYWRIGHT_BROWSERS_PATH: /ms-playwright - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - strategy: - fail-fast: false - matrix: - node-version: [20.x] - shard-index: [1, 2] - shard-count: [2] - steps: - - uses: actions/checkout@v6 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - name: Use Node.js 20 - uses: actions/setup-node@v6 - 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: Install dependencies - run: cd src-ui && pnpm install --no-frozen-lockfile - - 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 - needs: - - tests-frontend - - tests-frontend-e2e - steps: - - uses: actions/checkout@v6 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - name: Use Node.js 20 - uses: actions/setup-node@v6 - 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 - build-docker-image: - name: Build Docker image for ${{ github.event_name == 'pull_request' && github.head_ref || 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') || startsWith(github.ref, 'refs/heads/l10n_'))) || (github.event_name == 'pull_request' && (startsWith(github.head_ref, 'feature-') || startsWith(github.head_ref, 'fix-') || github.head_ref == 'dev' || github.head_ref == 'beta' || contains(github.head_ref, 'beta.rc') || startsWith(github.head_ref, 'l10n_'))) - concurrency: - group: ${{ github.workflow }}-build-docker-image-${{ github.ref_name }} - cancel-in-progress: true - needs: - - tests-backend - - tests-frontend - - tests-frontend-e2e - steps: - - name: Prepare build variables - id: build-vars - uses: actions/github-script@v8 - with: - result-encoding: string - script: | - const isPR = context.eventName === 'pull_request'; - const defaultRefName = context.ref.replace('refs/heads/', ''); - const headRef = isPR ? context.payload.pull_request.head.ref : defaultRefName; - const buildRef = isPR ? `refs/heads/${headRef}` : context.ref; - const buildCacheKey = headRef.split('/').join('-'); - const canPush = context.eventName === 'push' || (isPR && context.payload.pull_request.head.repo.full_name === `${context.repo.owner}/${context.repo.repo}`); - - core.setOutput('build-ref', buildRef); - core.setOutput('build-ref-name', headRef); - core.setOutput('build-cache-key', buildCacheKey); - core.setOutput('can-push', canPush ? 'true' : 'false'); - - name: Check pushing to Docker Hub - id: push-other-places - # Only push to Dockerhub from the main repo AND the ref is either: - # main - # dev - # beta - # a tag - # Otherwise forks would require a Docker Hub account and secrets setup - env: - BUILD_REF: ${{ steps.build-vars.outputs.build-ref }} - BUILD_REF_NAME: ${{ steps.build-vars.outputs.build-ref-name }} - run: | - if [[ ${{ github.repository_owner }} == "paperless-ngx" && ( "$BUILD_REF_NAME" == "dev" || "$BUILD_REF_NAME" == "beta" || $BUILD_REF == refs/tags/v* || $BUILD_REF == *beta.rc* ) ]] ; 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 - id: docker-meta - uses: docker/metadata-action@v5 - 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 }} - tags: | - # Tag branches with branch name - type=ref,event=branch - # Pull requests need a sanitized branch tag for pushing images - type=raw,value=${{ steps.build-vars.outputs.build-cache-key }},enable=${{ github.event_name == 'pull_request' }} - # Process semver tags - # 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@v6 - # 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 - 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' - 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: Maximize space - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf /usr/local/share/boost - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - - name: Build and push - uses: docker/build-push-action@v6 - with: - context: . - file: ./Dockerfile - platforms: linux/amd64,linux/arm64 - push: ${{ steps.build-vars.outputs.can-push == 'true' }} - 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 - # 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:${{ steps.build-vars.outputs.build-cache-key }} - type=registry,ref=ghcr.io/${{ steps.set-ghcr-repository.outputs.ghcr-repository }}/builder/cache/app:dev - cache-to: ${{ steps.build-vars.outputs.can-push == 'true' && format('type=registry,mode=max,ref=ghcr.io/{0}/builder/cache/app:{1}', steps.set-ghcr-repository.outputs.ghcr-repository, steps.build-vars.outputs.build-cache-key) || '' }} - - name: Inspect image - if: steps.build-vars.outputs.can-push == 'true' - run: | - docker buildx imagetools inspect ${{ fromJSON(steps.docker-meta.outputs.json).tags[0] }} - - name: Export frontend artifact from docker - if: steps.build-vars.outputs.can-push == 'true' - 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 - if: steps.build-vars.outputs.can-push == 'true' - uses: actions/upload-artifact@v5 - with: - name: frontend-compiled - path: src/documents/static/frontend/ - retention-days: 7 - build-release: - name: "Build Release" - needs: - - build-docker-image - - documentation - if: github.event_name == 'push' - runs-on: ubuntu-24.04 - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Set up Python - id: setup-python - uses: actions/setup-python@v6 - with: - python-version: ${{ env.DEFAULT_PYTHON_VERSION }} - - name: Install uv - uses: astral-sh/setup-uv@v7 - with: - version: ${{ env.DEFAULT_UV_VERSION }} - enable-cache: true - python-version: ${{ steps.setup-python.outputs.python-version }} - - name: Install Python dependencies - run: | - uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen - - 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@v6 - with: - name: frontend-compiled - path: src/documents/static/frontend/ - - name: Download documentation artifact - uses: actions/download-artifact@v6 - with: - name: documentation - path: docs/_build/html/ - - name: Generate requirements file - run: | - uv export --quiet --no-dev --all-extras --format requirements-txt --output-file requirements.txt - - name: Compile messages - run: | - cd src/ - uv run \ - --python ${{ steps.setup-python.outputs.python-version }} \ - 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 - 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 - 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@v5 - with: - name: release - path: dist/paperless-ngx.tar.xz - retention-days: 7 - publish-release: - name: "Publish Release" - runs-on: ubuntu-24.04 - outputs: - prerelease: ${{ steps.get_version.outputs.prerelease }} - changelog: ${{ steps.create-release.outputs.body }} - version: ${{ steps.get_version.outputs.version }} - needs: - - 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@v6 - with: - name: release - path: ./ - - name: Get version - id: get_version - run: | - echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT - if [[ ${{ contains(github.ref_name, '-beta.rc') }} == 'true' ]]; then - echo "prerelease=true" >> $GITHUB_OUTPUT - else - echo "prerelease=false" >> $GITHUB_OUTPUT - fi - - name: Create Release and Changelog - id: create-release - uses: release-drafter/release-drafter@v6 - with: - name: Paperless-ngx ${{ steps.get_version.outputs.version }} - tag: ${{ steps.get_version.outputs.version }} - version: ${{ steps.get_version.outputs.version }} - prerelease: ${{ steps.get_version.outputs.prerelease }} - publish: true # ensures release is not marked as draft - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Upload release archive - id: upload-release-asset - uses: shogo82148/actions-upload-release-asset@v1 - 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 - needs: - - publish-release - if: needs.publish-release.outputs.prerelease == 'false' - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - ref: main - - name: Set up Python - id: setup-python - uses: actions/setup-python@v6 - with: - python-version: ${{ env.DEFAULT_PYTHON_VERSION }} - - name: Install uv - uses: astral-sh/setup-uv@v7 - with: - version: ${{ env.DEFAULT_UV_VERSION }} - enable-cache: true - python-version: ${{ env.DEFAULT_PYTHON_VERSION }} - - name: Append Changelog to docs - id: append-Changelog - working-directory: docs - run: | - git branch ${{ needs.publish-release.outputs.version }}-changelog - 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 - 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 - 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@v8 - with: - script: | - const { repo, owner } = context.repo; - const result = await github.rest.pulls.create({ - title: 'Documentation: Add ${{ needs.publish-release.outputs.version }} changelog', - owner, - repo, - head: '${{ needs.publish-release.outputs.version }}-changelog', - base: 'main', - body: 'This PR is auto-generated by CI.' - }); - github.rest.issues.addLabels({ - owner, - repo, - issue_number: result.data.number, - labels: ['documentation', 'skip-changelog'] - }); diff --git a/.github/workflows/repo-maintenance.yml b/.github/workflows/repo-maintenance.yml index 75314918b..342165476 100644 --- a/.github/workflows/repo-maintenance.yml +++ b/.github/workflows/repo-maintenance.yml @@ -37,7 +37,7 @@ jobs: if: github.repository_owner == 'paperless-ngx' runs-on: ubuntu-24.04 steps: - - uses: dessant/lock-threads@v5 + - uses: dessant/lock-threads@v6 with: issue-inactive-days: '30' pr-inactive-days: '30' diff --git a/.github/workflows/translate-strings.yml b/.github/workflows/translate-strings.yml index 1a82a5cee..c2a2fdf1c 100644 --- a/.github/workflows/translate-strings.yml +++ b/.github/workflows/translate-strings.yml @@ -47,7 +47,7 @@ jobs: cache-dependency-path: 'src-ui/pnpm-lock.yaml' - name: Cache frontend dependencies id: cache-frontend-deps - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | ~/.pnpm-store diff --git a/src/locale/en_US/LC_MESSAGES/django.po b/src/locale/en_US/LC_MESSAGES/django.po index 53f2b32f5..75cd392ad 100644 --- a/src/locale/en_US/LC_MESSAGES/django.po +++ b/src/locale/en_US/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: paperless-ngx\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-29 14:49+0000\n" +"POT-Creation-Date: 2026-01-06 17:11+0000\n" "PO-Revision-Date: 2022-02-17 04:17\n" "Last-Translator: \n" "Language-Team: English\n" @@ -1702,151 +1702,151 @@ msgstr "" msgid "paperless application settings" msgstr "" -#: paperless/settings.py:773 +#: paperless/settings.py:767 msgid "English (US)" msgstr "" -#: paperless/settings.py:774 +#: paperless/settings.py:768 msgid "Arabic" msgstr "" -#: paperless/settings.py:775 +#: paperless/settings.py:769 msgid "Afrikaans" msgstr "" -#: paperless/settings.py:776 +#: paperless/settings.py:770 msgid "Belarusian" msgstr "" -#: paperless/settings.py:777 +#: paperless/settings.py:771 msgid "Bulgarian" msgstr "" -#: paperless/settings.py:778 +#: paperless/settings.py:772 msgid "Catalan" msgstr "" -#: paperless/settings.py:779 +#: paperless/settings.py:773 msgid "Czech" msgstr "" -#: paperless/settings.py:780 +#: paperless/settings.py:774 msgid "Danish" msgstr "" -#: paperless/settings.py:781 +#: paperless/settings.py:775 msgid "German" msgstr "" -#: paperless/settings.py:782 +#: paperless/settings.py:776 msgid "Greek" msgstr "" -#: paperless/settings.py:783 +#: paperless/settings.py:777 msgid "English (GB)" msgstr "" -#: paperless/settings.py:784 +#: paperless/settings.py:778 msgid "Spanish" msgstr "" -#: paperless/settings.py:785 +#: paperless/settings.py:779 msgid "Persian" msgstr "" -#: paperless/settings.py:786 +#: paperless/settings.py:780 msgid "Finnish" msgstr "" -#: paperless/settings.py:787 +#: paperless/settings.py:781 msgid "French" msgstr "" -#: paperless/settings.py:788 +#: paperless/settings.py:782 msgid "Hungarian" msgstr "" -#: paperless/settings.py:789 +#: paperless/settings.py:783 msgid "Indonesian" msgstr "" -#: paperless/settings.py:790 +#: paperless/settings.py:784 msgid "Italian" msgstr "" -#: paperless/settings.py:791 +#: paperless/settings.py:785 msgid "Japanese" msgstr "" -#: paperless/settings.py:792 +#: paperless/settings.py:786 msgid "Korean" msgstr "" -#: paperless/settings.py:793 +#: paperless/settings.py:787 msgid "Luxembourgish" msgstr "" -#: paperless/settings.py:794 +#: paperless/settings.py:788 msgid "Norwegian" msgstr "" -#: paperless/settings.py:795 +#: paperless/settings.py:789 msgid "Dutch" msgstr "" -#: paperless/settings.py:796 +#: paperless/settings.py:790 msgid "Polish" msgstr "" -#: paperless/settings.py:797 +#: paperless/settings.py:791 msgid "Portuguese (Brazil)" msgstr "" -#: paperless/settings.py:798 +#: paperless/settings.py:792 msgid "Portuguese" msgstr "" -#: paperless/settings.py:799 +#: paperless/settings.py:793 msgid "Romanian" msgstr "" -#: paperless/settings.py:800 +#: paperless/settings.py:794 msgid "Russian" msgstr "" -#: paperless/settings.py:801 +#: paperless/settings.py:795 msgid "Slovak" msgstr "" -#: paperless/settings.py:802 +#: paperless/settings.py:796 msgid "Slovenian" msgstr "" -#: paperless/settings.py:803 +#: paperless/settings.py:797 msgid "Serbian" msgstr "" -#: paperless/settings.py:804 +#: paperless/settings.py:798 msgid "Swedish" msgstr "" -#: paperless/settings.py:805 +#: paperless/settings.py:799 msgid "Turkish" msgstr "" -#: paperless/settings.py:806 +#: paperless/settings.py:800 msgid "Ukrainian" msgstr "" -#: paperless/settings.py:807 +#: paperless/settings.py:801 msgid "Vietnamese" msgstr "" -#: paperless/settings.py:808 +#: paperless/settings.py:802 msgid "Chinese Simplified" msgstr "" -#: paperless/settings.py:809 +#: paperless/settings.py:803 msgid "Chinese Traditional" msgstr "" diff --git a/src/paperless/settings.py b/src/paperless/settings.py index bb5842fa2..d335a0d92 100644 --- a/src/paperless/settings.py +++ b/src/paperless/settings.py @@ -8,7 +8,6 @@ import os import tempfile from os import PathLike from pathlib import Path -from platform import machine from typing import Final from urllib.parse import urlparse @@ -449,14 +448,9 @@ ASGI_APPLICATION = "paperless.asgi.application" STATIC_URL = os.getenv("PAPERLESS_STATIC_URL", BASE_URL + "static/") WHITENOISE_STATIC_PREFIX = "/static/" -if machine().lower() == "aarch64": # pragma: no cover - _static_backend = "django.contrib.staticfiles.storage.StaticFilesStorage" -else: - _static_backend = "whitenoise.storage.CompressedStaticFilesStorage" - STORAGES = { "staticfiles": { - "BACKEND": _static_backend, + "BACKEND": "whitenoise.storage.CompressedStaticFilesStorage", }, "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"}, }