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.11.x" DEFAULT_PYTHON_VERSION: "3.12" permissions: {} jobs: wait-for-docker: name: Wait for Docker Build runs-on: ubuntu-24.04 permissions: checks: read statuses: read steps: - name: Wait for Docker build uses: lewagon/wait-on-check-action@9312864dfbc9fd208e9c0417843430751c042800 # v1.7.0 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 permissions: contents: read steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false # ---- Frontend Build ---- - name: Install pnpm uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3 with: version: 10 - name: Use Node.js 24 uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x package-manager-cache: false - 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@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} - name: Install uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: ${{ env.DEFAULT_UV_VERSION }} enable-cache: false python-version: ${{ steps.setup-python.outputs.python-version }} - name: Install Python dependencies env: PYTHON_VERSION: ${{ steps.setup-python.outputs.python-version }} run: | uv sync --python "${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 env: PYTHON_VERSION: ${{ steps.setup-python.outputs.python-version }} run: | uv run \ --python "${PYTHON_VERSION}" \ --dev \ --frozen \ zensical build --clean # ---- Prepare Release ---- - name: Generate requirements file run: | uv export --quiet --no-dev --all-extras --format requirements-txt --output-file requirements.txt - name: Compile messages env: PAPERLESS_SECRET_KEY: "ci-release-not-a-real-secret" PYTHON_VERSION: ${{ steps.setup-python.outputs.python-version }} run: | cd src/ uv run \ --python "${PYTHON_VERSION}" \ manage.py compilemessages - name: Collect static files env: PAPERLESS_SECRET_KEY: "ci-release-not-a-real-secret" PYTHON_VERSION: ${{ steps.setup-python.outputs.python-version }} run: | cd src/ uv run \ --python "${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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 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 permissions: contents: write pull-requests: write 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@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: release path: ./ - name: Get version info id: get-version env: REF_NAME: ${{ github.ref_name }} run: | echo "version=${REF_NAME}" >> $GITHUB_OUTPUT if [[ "${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@5de93583980a40bd78603b6dfdcda5b4df377b32 # v7.2.0 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 commitish: main env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload release archive uses: shogo82148/actions-upload-release-asset@ee2ae851dc5d938b90075b3ef12c540abfd1ee72 # v1.10.1 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 permissions: contents: write pull-requests: write steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: main persist-credentials: true # for pushing changelog branch - name: Set up Python id: setup-python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} - name: Install uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: ${{ env.DEFAULT_UV_VERSION }} enable-cache: false python-version: ${{ env.DEFAULT_PYTHON_VERSION }} - name: Update changelog working-directory: docs env: CHANGELOG: ${{ needs.publish-release.outputs.changelog }} PYTHON_VERSION: ${{ steps.setup-python.outputs.python-version }} VERSION: ${{ needs.publish-release.outputs.version }} run: | branch_name="${VERSION}-changelog" git branch "${branch_name}" git checkout "${branch_name}" printf '# Changelog\n\n%s\n' "${CHANGELOG}" > 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 "${PYTHON_VERSION}" \ --dev \ prek 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 ${VERSION} - GHA" git push origin "${branch_name}" - name: Create pull request uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: VERSION: ${{ needs.publish-release.outputs.version }} with: script: | const { repo, owner } = context.repo; const version = process.env.VERSION; const head = `${version}-changelog`; const result = await github.rest.pulls.create({ title: `Documentation: Add ${version} changelog`, owner, repo, head, 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'] });