mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-10-02 22:30:31 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d8d3882dc | ||
|
|
8707faba29 | ||
|
|
bd84394bf2 |
@@ -1,53 +0,0 @@
|
||||
name: Install apt packages
|
||||
description: >
|
||||
Installs apt packages with a per-attempt timeout and retries. The hosted runner's Azure apt mirror intermittently serves archives at a crawl, which apt's own timeout does not catch. A stuck attempt is killed and retried, which usually lands on a healthy mirror node.
|
||||
|
||||
inputs:
|
||||
packages:
|
||||
description: Space separated list of packages to install
|
||||
required: true
|
||||
attempts:
|
||||
description: Maximum number of attempts
|
||||
default: "3"
|
||||
attempt-timeout:
|
||||
description: Seconds before a single attempt is killed
|
||||
default: "180"
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install ${{ inputs.packages }}
|
||||
shell: bash
|
||||
env:
|
||||
PACKAGES: ${{ inputs.packages }}
|
||||
ATTEMPTS: ${{ inputs.attempts }}
|
||||
ATTEMPT_TIMEOUT: ${{ inputs.attempt-timeout }}
|
||||
run: |
|
||||
SUDO=""
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
SUDO="sudo"
|
||||
fi
|
||||
|
||||
attempt_install() {
|
||||
$SUDO apt-get update -q
|
||||
# Intentionally unquoted, this is a list of package names
|
||||
$SUDO apt-get install -q --yes --no-install-recommends $PACKAGES
|
||||
}
|
||||
export -f attempt_install
|
||||
export SUDO PACKAGES
|
||||
|
||||
for attempt in $(seq 1 "${ATTEMPTS}"); do
|
||||
echo "::group::apt install attempt ${attempt}/${ATTEMPTS}"
|
||||
start=$(date +%s)
|
||||
if timeout --kill-after=10 "${ATTEMPT_TIMEOUT}" bash -c attempt_install; then
|
||||
echo "::endgroup::"
|
||||
echo "Installed in $(( $(date +%s) - start ))s"
|
||||
exit 0
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
echo "::warning::apt install attempt ${attempt}/${ATTEMPTS} failed or exceeded ${ATTEMPT_TIMEOUT}s"
|
||||
# A killed attempt can leave dpkg half configured
|
||||
$SUDO dpkg --configure -a || true
|
||||
done
|
||||
|
||||
echo "::error::apt install failed after ${ATTEMPTS} attempts"
|
||||
exit 1
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.backend_changed == 'true'
|
||||
name: "Python ${{ matrix.python-version }}"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
@@ -108,13 +108,14 @@ jobs:
|
||||
enable-cache: true
|
||||
python-version: ${{ steps.setup-python.outputs.python-version }}
|
||||
- name: Install system dependencies
|
||||
timeout-minutes: 12
|
||||
uses: $/.github/actions/apt-install
|
||||
with:
|
||||
packages: unpaper tesseract-ocr imagemagick ghostscript poppler-utils
|
||||
timeout-minutes: 10
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq --no-install-recommends \
|
||||
unpaper tesseract-ocr imagemagick ghostscript poppler-utils
|
||||
- name: Configure ImageMagick
|
||||
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
|
||||
env:
|
||||
PYTHON_VERSION: ${{ steps.setup-python.outputs.python-version }}
|
||||
@@ -158,7 +159,7 @@ jobs:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.backend_changed == 'true'
|
||||
name: Check project typing
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
|
||||
@@ -24,10 +24,10 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: ubuntu-24.04
|
||||
- runner: ubuntu-26.04
|
||||
arch: amd64
|
||||
platform: linux/amd64
|
||||
- runner: ubuntu-24.04-arm
|
||||
- runner: ubuntu-26.04-arm
|
||||
arch: arm64
|
||||
platform: linux/arm64
|
||||
runs-on: ${{ matrix.runner }}
|
||||
@@ -163,7 +163,7 @@ jobs:
|
||||
archive: false
|
||||
merge-and-push:
|
||||
name: Merge and Push Manifest
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
needs: build-arch
|
||||
if: needs.build-arch.outputs.should-push == 'true'
|
||||
environment: image-publishing
|
||||
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.docs_changed == 'true'
|
||||
name: Build Documentation
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
||||
- name: Checkout
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
name: Deploy Documentation
|
||||
needs: [changes, build]
|
||||
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:
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.frontend_changed == 'true'
|
||||
name: Install Dependencies
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
name: Lint
|
||||
needs: [changes, install-dependencies]
|
||||
if: needs.changes.outputs.frontend_changed == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
@@ -137,7 +137,7 @@ jobs:
|
||||
name: "Unit Tests (${{ matrix.shard-index }}/${{ matrix.shard-count }})"
|
||||
needs: [changes, install-dependencies]
|
||||
if: needs.changes.outputs.frontend_changed == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
@@ -188,10 +188,10 @@ jobs:
|
||||
name: E2E Tests
|
||||
needs: [changes, install-dependencies]
|
||||
if: needs.changes.outputs.frontend_changed == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
container: mcr.microsoft.com/playwright:v1.62.1-noble
|
||||
container: mcr.microsoft.com/playwright:v1.62.1-resolute
|
||||
env:
|
||||
PLAYWRIGHT_BROWSERS_PATH: /ms-playwright
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
@@ -231,10 +231,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: cd src-ui && pnpm install --frozen-lockfile
|
||||
- name: Install backend system dependencies
|
||||
timeout-minutes: 12
|
||||
uses: $/.github/actions/apt-install
|
||||
with:
|
||||
packages: libmagic1
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install --yes --quiet --no-install-recommends libmagic1
|
||||
- name: Install backend dependencies
|
||||
env:
|
||||
PYTHON_VERSION: ${{ steps.setup-python.outputs.python-version }}
|
||||
@@ -246,7 +245,7 @@ jobs:
|
||||
name: Frontend Build
|
||||
needs: [changes, unit-tests, e2e-tests]
|
||||
if: needs.changes.outputs.frontend_changed == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
|
||||
@@ -14,7 +14,7 @@ permissions: {}
|
||||
jobs:
|
||||
wait-for-docker:
|
||||
name: Wait for Docker Build
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
checks: read
|
||||
statuses: read
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
build-release:
|
||||
name: Build Release
|
||||
needs: wait-for-docker
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
@@ -70,10 +70,9 @@ jobs:
|
||||
run: |
|
||||
uv sync --python "${PYTHON_VERSION}" --dev --frozen
|
||||
- name: Install system dependencies
|
||||
timeout-minutes: 12
|
||||
uses: $/.github/actions/apt-install
|
||||
with:
|
||||
packages: gettext liblept5
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq --no-install-recommends gettext libleptonica6
|
||||
# ---- Build Documentation ----
|
||||
- name: Build documentation
|
||||
env:
|
||||
@@ -145,7 +144,7 @@ jobs:
|
||||
publish-release:
|
||||
name: Publish Release
|
||||
needs: build-release
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
@@ -197,7 +196,7 @@ jobs:
|
||||
name: Append Changelog
|
||||
needs: publish-release
|
||||
if: needs.publish-release.outputs.prerelease == 'false'
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
@@ -15,7 +15,7 @@ permissions:
|
||||
jobs:
|
||||
zizmor:
|
||||
name: Run zizmor
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
|
||||
semgrep:
|
||||
name: Semgrep CE
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
container:
|
||||
image: semgrep/semgrep:1.155.0@sha256:cc869c685dcc0fe497c86258da9f205397d8108e56d21a86082ea4886e52784d
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
|
||||
@@ -17,7 +17,7 @@ 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-26.04
|
||||
environment: registry-maintenance
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
cleanup-untagged-images:
|
||||
name: Cleanup Untagged Images Tags for ${{ matrix.primary-name }}
|
||||
if: github.repository_owner == 'paperless-ngx'
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
needs:
|
||||
- cleanup-images
|
||||
environment: registry-maintenance
|
||||
|
||||
@@ -21,7 +21,7 @@ on:
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
synchronize-with-crowdin:
|
||||
name: Crowdin Sync
|
||||
if: github.repository_owner == 'paperless-ngx'
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
environment: translation-sync
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -7,7 +7,7 @@ jobs:
|
||||
# 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
|
||||
# workflow gets from the action are implemented manually here.
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
|
||||
@@ -4,7 +4,7 @@ on:
|
||||
types: [opened]
|
||||
jobs:
|
||||
Anti-slop:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
issues: read
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
ASLOP-PR-VERIFY
|
||||
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
|
||||
# instead of racing it. Still runs if that job fails, so labeling is not lost.
|
||||
needs: Anti-slop
|
||||
|
||||
@@ -12,7 +12,7 @@ permissions:
|
||||
jobs:
|
||||
pr_opened_or_reopened:
|
||||
name: pr_opened_or_reopened
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
# write permission is required for autolabeler
|
||||
pull-requests: write
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
stale:
|
||||
name: 'Stale'
|
||||
if: github.repository_owner == 'paperless-ngx'
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
lock-threads:
|
||||
name: 'Lock Old Threads'
|
||||
if: github.repository_owner == 'paperless-ngx'
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
close-answered-discussions:
|
||||
name: 'Close Answered Discussions'
|
||||
if: github.repository_owner == 'paperless-ngx'
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
discussions: write
|
||||
steps:
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
close-outdated-discussions:
|
||||
name: 'Close Outdated Discussions'
|
||||
if: github.repository_owner == 'paperless-ngx'
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
discussions: write
|
||||
steps:
|
||||
@@ -211,7 +211,7 @@ jobs:
|
||||
close-unsupported-feature-requests:
|
||||
name: 'Close Unsupported Feature Requests'
|
||||
if: github.repository_owner == 'paperless-ngx'
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
discussions: write
|
||||
steps:
|
||||
|
||||
@@ -8,7 +8,7 @@ env:
|
||||
jobs:
|
||||
generate-translate-strings:
|
||||
name: Generate Translation Strings
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
environment: translation-sync
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -25,10 +25,9 @@ jobs:
|
||||
id: setup-python
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
- name: Install system dependencies
|
||||
timeout-minutes: 12
|
||||
uses: $/.github/actions/apt-install
|
||||
with:
|
||||
packages: gettext
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq --no-install-recommends gettext
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
||||
with:
|
||||
|
||||
@@ -137,9 +137,20 @@ class TestNginxService:
|
||||
reason="No Gotenberg/Tika servers to test with",
|
||||
)
|
||||
class TestParserLive:
|
||||
@staticmethod
|
||||
def imagehash(file: Path, hash_size: int = 18) -> str:
|
||||
return f"{average_hash(Image.open(file), hash_size)}"
|
||||
# Rasterizer versions shift a few pixels, so compare perceptual hashes by
|
||||
# Hamming distance (out of 18 * 18 = 324 bits) rather than for equality
|
||||
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(
|
||||
self,
|
||||
@@ -168,12 +179,7 @@ class TestParserLive:
|
||||
assert thumb.exists()
|
||||
assert thumb.is_file()
|
||||
|
||||
assert self.imagehash(thumb) == self.imagehash(
|
||||
simple_txt_email_thumbnail_file,
|
||||
), (
|
||||
f"Created thumbnail {thumb} differs from expected file "
|
||||
f"{simple_txt_email_thumbnail_file}"
|
||||
)
|
||||
self.assert_thumbnails_similar(thumb, simple_txt_email_thumbnail_file)
|
||||
|
||||
def test_tika_parse_successful(self, mail_parser: MailDocumentParser) -> None:
|
||||
"""
|
||||
@@ -255,7 +261,7 @@ class TestParserLive:
|
||||
THEN:
|
||||
- Gotenberg shall be called to generate the PDF
|
||||
- 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"])
|
||||
|
||||
@@ -272,14 +278,4 @@ class TestParserLive:
|
||||
html_email_file,
|
||||
"message/rfc822",
|
||||
)
|
||||
generated_thumbnail_hash = self.imagehash(generated_thumbnail)
|
||||
|
||||
# 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}"
|
||||
)
|
||||
self.assert_thumbnails_similar(generated_thumbnail, html_email_thumbnail_file)
|
||||
|
||||
Reference in New Issue
Block a user