Compare commits

...
Author SHA1 Message Date
GitHub Actions ee34a6598e Auto translate strings 2026-10-06 15:13:08 +00:00
Trenton H 3a3b3ef66a Chore: Upgrade runners to Ubuntu 26.04 (#14347)
* Moves runners to 26.04 and a few jobs to -slim variant

* Probably fixing the imagemagik 7 problems and maybe the frontend playwright thing?

* Compare thumbnails, but allow a little difference in the perceptual hash
2026-10-06 08:11:44 -07:00
16 changed files with 54 additions and 75 deletions

No files matched your search

+3 -3
View File
@@ -80,7 +80,7 @@ jobs:
needs: changes needs: changes
if: needs.changes.outputs.backend_changed == 'true' if: needs.changes.outputs.backend_changed == 'true'
name: "Python ${{ matrix.python-version }}" name: "Python ${{ matrix.python-version }}"
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
permissions: permissions:
contents: read contents: read
strategy: strategy:
@@ -114,7 +114,7 @@ jobs:
packages: unpaper tesseract-ocr imagemagick ghostscript poppler-utils packages: unpaper tesseract-ocr imagemagick ghostscript poppler-utils
- name: Configure ImageMagick - name: Configure ImageMagick
run: | run: |
sudo cp docker/rootfs/etc/ImageMagick-6/paperless-policy.xml /etc/ImageMagick-6/policy.xml sudo cp docker/rootfs/etc/ImageMagick-6/paperless-policy.xml /etc/ImageMagick-7/policy.xml
- name: Install Python dependencies - name: Install Python dependencies
env: env:
PYTHON_VERSION: ${{ steps.setup-python.outputs.python-version }} PYTHON_VERSION: ${{ steps.setup-python.outputs.python-version }}
@@ -158,7 +158,7 @@ jobs:
needs: changes needs: changes
if: needs.changes.outputs.backend_changed == 'true' if: needs.changes.outputs.backend_changed == 'true'
name: Check project typing name: Check project typing
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
permissions: permissions:
contents: read contents: read
env: env:
+3 -3
View File
@@ -24,10 +24,10 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- runner: ubuntu-24.04 - runner: ubuntu-26.04
arch: amd64 arch: amd64
platform: linux/amd64 platform: linux/amd64
- runner: ubuntu-24.04-arm - runner: ubuntu-26.04-arm
arch: arm64 arch: arm64
platform: linux/arm64 platform: linux/arm64
runs-on: ${{ matrix.runner }} runs-on: ${{ matrix.runner }}
@@ -163,7 +163,7 @@ jobs:
archive: false archive: false
merge-and-push: merge-and-push:
name: Merge and Push Manifest name: Merge and Push Manifest
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
needs: build-arch needs: build-arch
if: needs.build-arch.outputs.should-push == 'true' if: needs.build-arch.outputs.should-push == 'true'
environment: image-publishing environment: image-publishing
+2 -2
View File
@@ -65,7 +65,7 @@ jobs:
needs: changes needs: changes
if: needs.changes.outputs.docs_changed == 'true' if: needs.changes.outputs.docs_changed == 'true'
name: Build Documentation name: Build Documentation
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
steps: steps:
- uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
- name: Checkout - name: Checkout
@@ -102,7 +102,7 @@ jobs:
name: Deploy Documentation name: Deploy Documentation
needs: [changes, build] needs: [changes, build]
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.changes.outputs.docs_changed == 'true' if: github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.changes.outputs.docs_changed == 'true'
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
permissions: permissions:
pages: write pages: write
id-token: write id-token: write
+6 -6
View File
@@ -72,7 +72,7 @@ jobs:
needs: changes needs: changes
if: needs.changes.outputs.frontend_changed == 'true' if: needs.changes.outputs.frontend_changed == 'true'
name: Install Dependencies name: Install Dependencies
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
permissions: permissions:
contents: read contents: read
steps: steps:
@@ -104,7 +104,7 @@ jobs:
name: Lint name: Lint
needs: [changes, install-dependencies] needs: [changes, install-dependencies]
if: needs.changes.outputs.frontend_changed == 'true' if: needs.changes.outputs.frontend_changed == 'true'
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
permissions: permissions:
contents: read contents: read
steps: steps:
@@ -137,7 +137,7 @@ jobs:
name: "Unit Tests (${{ matrix.shard-index }}/${{ matrix.shard-count }})" name: "Unit Tests (${{ matrix.shard-index }}/${{ matrix.shard-count }})"
needs: [changes, install-dependencies] needs: [changes, install-dependencies]
if: needs.changes.outputs.frontend_changed == 'true' if: needs.changes.outputs.frontend_changed == 'true'
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
permissions: permissions:
contents: read contents: read
strategy: strategy:
@@ -188,10 +188,10 @@ jobs:
name: E2E Tests name: E2E Tests
needs: [changes, install-dependencies] needs: [changes, install-dependencies]
if: needs.changes.outputs.frontend_changed == 'true' if: needs.changes.outputs.frontend_changed == 'true'
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
permissions: permissions:
contents: read contents: read
container: mcr.microsoft.com/playwright:v1.62.1-noble container: mcr.microsoft.com/playwright:v1.62.1-resolute
env: env:
PLAYWRIGHT_BROWSERS_PATH: /ms-playwright PLAYWRIGHT_BROWSERS_PATH: /ms-playwright
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
@@ -246,7 +246,7 @@ jobs:
name: Frontend Build name: Frontend Build
needs: [changes, unit-tests, e2e-tests] needs: [changes, unit-tests, e2e-tests]
if: needs.changes.outputs.frontend_changed == 'true' if: needs.changes.outputs.frontend_changed == 'true'
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
permissions: permissions:
contents: read contents: read
steps: steps:
+5 -5
View File
@@ -14,7 +14,7 @@ permissions: {}
jobs: jobs:
wait-for-docker: wait-for-docker:
name: Wait for Docker Build name: Wait for Docker Build
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
permissions: permissions:
checks: read checks: read
statuses: read statuses: read
@@ -30,7 +30,7 @@ jobs:
build-release: build-release:
name: Build Release name: Build Release
needs: wait-for-docker needs: wait-for-docker
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
permissions: permissions:
contents: read contents: read
steps: steps:
@@ -73,7 +73,7 @@ jobs:
timeout-minutes: 12 timeout-minutes: 12
uses: $/.github/actions/apt-install uses: $/.github/actions/apt-install
with: with:
packages: gettext liblept5 packages: gettext libleptonica6
# ---- Build Documentation ---- # ---- Build Documentation ----
- name: Build documentation - name: Build documentation
env: env:
@@ -145,7 +145,7 @@ jobs:
publish-release: publish-release:
name: Publish Release name: Publish Release
needs: build-release needs: build-release
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
permissions: permissions:
contents: write contents: write
pull-requests: write pull-requests: write
@@ -197,7 +197,7 @@ jobs:
name: Append Changelog name: Append Changelog
needs: publish-release needs: publish-release
if: needs.publish-release.outputs.prerelease == 'false' if: needs.publish-release.outputs.prerelease == 'false'
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
permissions: permissions:
contents: write contents: write
pull-requests: write pull-requests: write
+2 -2
View File
@@ -15,7 +15,7 @@ permissions:
jobs: jobs:
zizmor: zizmor:
name: Run zizmor name: Run zizmor
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
permissions: permissions:
contents: read contents: read
actions: read actions: read
@@ -29,7 +29,7 @@ jobs:
uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4 uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
semgrep: semgrep:
name: Semgrep CE name: Semgrep CE
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
container: container:
image: semgrep/semgrep:1.155.0@sha256:cc869c685dcc0fe497c86258da9f205397d8108e56d21a86082ea4886e52784d image: semgrep/semgrep:1.155.0@sha256:cc869c685dcc0fe497c86258da9f205397d8108e56d21a86082ea4886e52784d
if: github.actor != 'dependabot[bot]' if: github.actor != 'dependabot[bot]'
+2 -2
View File
@@ -17,7 +17,7 @@ jobs:
cleanup-images: cleanup-images:
name: Cleanup Image Tags for ${{ matrix.primary-name }} name: Cleanup Image Tags for ${{ matrix.primary-name }}
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
environment: registry-maintenance environment: registry-maintenance
strategy: strategy:
fail-fast: false fail-fast: false
@@ -42,7 +42,7 @@ jobs:
cleanup-untagged-images: cleanup-untagged-images:
name: Cleanup Untagged Images Tags for ${{ matrix.primary-name }} name: Cleanup Untagged Images Tags for ${{ matrix.primary-name }}
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
needs: needs:
- cleanup-images - cleanup-images
environment: registry-maintenance environment: registry-maintenance
+1 -1
View File
@@ -21,7 +21,7 @@ on:
jobs: jobs:
analyze: analyze:
name: Analyze name: Analyze
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
permissions: permissions:
actions: read actions: read
contents: read contents: read
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
synchronize-with-crowdin: synchronize-with-crowdin:
name: Crowdin Sync name: Crowdin Sync
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
environment: translation-sync environment: translation-sync
steps: steps:
- name: Checkout - name: Checkout
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
# Note: peakoss/anti-slop does not support the `issues` event yet (all of its # Note: peakoss/anti-slop does not support the `issues` event yet (all of its
# issue inputs are still commented out upstream), so the checks that the PR Bot # issue inputs are still commented out upstream), so the checks that the PR Bot
# workflow gets from the action are implemented manually here. # workflow gets from the action are implemented manually here.
runs-on: ubuntu-latest runs-on: ubuntu-slim
permissions: permissions:
issues: write issues: write
steps: steps:
+2 -2
View File
@@ -4,7 +4,7 @@ on:
types: [opened] types: [opened]
jobs: jobs:
Anti-slop: Anti-slop:
runs-on: ubuntu-latest runs-on: ubuntu-slim
permissions: permissions:
contents: read contents: read
issues: read issues: read
@@ -24,7 +24,7 @@ jobs:
ASLOP-PR-VERIFY ASLOP-PR-VERIFY
pr-bot: pr-bot:
name: Automated PR Bot name: Automated PR Bot
runs-on: ubuntu-latest runs-on: ubuntu-slim
# Runs after Anti-slop so the welcome comment can see whether the PR was closed # Runs after Anti-slop so the welcome comment can see whether the PR was closed
# instead of racing it. Still runs if that job fails, so labeling is not lost. # instead of racing it. Still runs if that job fails, so labeling is not lost.
needs: Anti-slop needs: Anti-slop
+1 -1
View File
@@ -12,7 +12,7 @@ permissions:
jobs: jobs:
pr_opened_or_reopened: pr_opened_or_reopened:
name: pr_opened_or_reopened name: pr_opened_or_reopened
runs-on: ubuntu-24.04 runs-on: ubuntu-slim
permissions: permissions:
# write permission is required for autolabeler # write permission is required for autolabeler
pull-requests: write pull-requests: write
+5 -5
View File
@@ -9,7 +9,7 @@ jobs:
stale: stale:
name: 'Stale' name: 'Stale'
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
permissions: permissions:
issues: write issues: write
pull-requests: write pull-requests: write
@@ -34,7 +34,7 @@ jobs:
lock-threads: lock-threads:
name: 'Lock Old Threads' name: 'Lock Old Threads'
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-24.04 runs-on: ubuntu-26.04
permissions: permissions:
issues: write issues: write
pull-requests: write pull-requests: write
@@ -58,7 +58,7 @@ jobs:
close-answered-discussions: close-answered-discussions:
name: 'Close Answered Discussions' name: 'Close Answered Discussions'
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-24.04 runs-on: ubuntu-slim
permissions: permissions:
discussions: write discussions: write
steps: steps:
@@ -117,7 +117,7 @@ jobs:
close-outdated-discussions: close-outdated-discussions:
name: 'Close Outdated Discussions' name: 'Close Outdated Discussions'
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-24.04 runs-on: ubuntu-slim
permissions: permissions:
discussions: write discussions: write
steps: steps:
@@ -211,7 +211,7 @@ jobs:
close-unsupported-feature-requests: close-unsupported-feature-requests:
name: 'Close Unsupported Feature Requests' name: 'Close Unsupported Feature Requests'
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-24.04 runs-on: ubuntu-slim
permissions: permissions:
discussions: write discussions: write
steps: steps:
+1 -1
View File
@@ -8,7 +8,7 @@ env:
jobs: jobs:
generate-translate-strings: generate-translate-strings:
name: Generate Translation Strings name: Generate Translation Strings
runs-on: ubuntu-latest runs-on: ubuntu-26.04
environment: translation-sync environment: translation-sync
permissions: permissions:
contents: write contents: write
+2 -19
View File
@@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: paperless-ngx\n" "Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-05 16:26+0000\n" "POT-Creation-Date: 2026-10-06 15:12+0000\n"
"PO-Revision-Date: 2022-02-17 04:17\n" "PO-Revision-Date: 2022-02-17 04:17\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: English\n" "Language-Team: English\n"
@@ -1941,25 +1941,8 @@ msgstr ""
msgid "As a final step, please complete the following form:" msgid "As a final step, please complete the following form:"
msgstr "" msgstr ""
#: documents/validators.py:24
#, python-brace-format
msgid "Unable to parse URI {value}, missing scheme"
msgstr ""
#: documents/validators.py:29
#, python-brace-format
msgid "Unable to parse URI {value}, missing net location or path"
msgstr ""
#: documents/validators.py:36 #: documents/validators.py:36
msgid "" msgid ", "
"URI scheme '{parts.scheme}' is not allowed. Allowed schemes: {', '."
"join(allowed_schemes)}"
msgstr ""
#: documents/validators.py:45
#, python-brace-format
msgid "Unable to parse URI {value}"
msgstr "" msgstr ""
#: documents/views.py:336 documents/views.py:2729 #: documents/views.py:336 documents/views.py:2729
@@ -137,9 +137,20 @@ class TestNginxService:
reason="No Gotenberg/Tika servers to test with", reason="No Gotenberg/Tika servers to test with",
) )
class TestParserLive: class TestParserLive:
@staticmethod # Rasterizer versions shift a few pixels, so compare perceptual hashes by
def imagehash(file: Path, hash_size: int = 18) -> str: # Hamming distance (out of 18 * 18 = 324 bits) rather than for equality
return f"{average_hash(Image.open(file), hash_size)}" MAX_HASH_DISTANCE = 8
@classmethod
def assert_thumbnails_similar(cls, generated: Path, expected: Path) -> None:
distance = average_hash(Image.open(generated), 18) - average_hash(
Image.open(expected),
18,
)
assert distance <= cls.MAX_HASH_DISTANCE, (
f"Thumbnail {generated} differs from {expected} by {distance} bits "
f"(max {cls.MAX_HASH_DISTANCE})"
)
def test_get_thumbnail( def test_get_thumbnail(
self, self,
@@ -168,12 +179,7 @@ class TestParserLive:
assert thumb.exists() assert thumb.exists()
assert thumb.is_file() assert thumb.is_file()
assert self.imagehash(thumb) == self.imagehash( self.assert_thumbnails_similar(thumb, simple_txt_email_thumbnail_file)
simple_txt_email_thumbnail_file,
), (
f"Created thumbnail {thumb} differs from expected file "
f"{simple_txt_email_thumbnail_file}"
)
def test_tika_parse_successful(self, mail_parser: MailDocumentParser) -> None: def test_tika_parse_successful(self, mail_parser: MailDocumentParser) -> None:
""" """
@@ -255,7 +261,7 @@ class TestParserLive:
THEN: THEN:
- Gotenberg shall be called to generate the PDF - Gotenberg shall be called to generate the PDF
- The archive PDF shall contain the expected content - The archive PDF shall contain the expected content
- The generated thumbnail shall match the expected image hash - The generated thumbnail shall be perceptually close to the expected image
""" """
util_call_with_backoff(mail_parser.parse, [html_email_file, "message/rfc822"]) util_call_with_backoff(mail_parser.parse, [html_email_file, "message/rfc822"])
@@ -272,14 +278,4 @@ class TestParserLive:
html_email_file, html_email_file,
"message/rfc822", "message/rfc822",
) )
generated_thumbnail_hash = self.imagehash(generated_thumbnail) self.assert_thumbnails_similar(generated_thumbnail, html_email_thumbnail_file)
# The created PDF is not reproducible, but the converted image
# should always look the same
expected_hash = self.imagehash(html_email_thumbnail_file)
assert generated_thumbnail_hash == expected_hash, (
f"PDF thumbnail differs from expected. "
f"Generated: {generated_thumbnail}, "
f"Hash: {generated_thumbnail_hash} vs {expected_hash}"
)