Merge pull request #14560 from nextcloud/docs/admin-occ-encryption-co⦠#33574
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: "Build documentation" | |
| on: | |
| pull_request: | |
| push: | |
| branches: | |
| - master | |
| - stable* | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: build-documentation-${{ github.head_ref || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # ============================================================================ | |
| # BUILD HTML | |
| # ============================================================================ | |
| # Builds the HTML documentation for all manuals. No LaTeX required. | |
| # Starts immediately without waiting for any setup job. | |
| # ============================================================================ | |
| build-html: | |
| name: Building ${{ matrix.manual.name }} HTML | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| manual: | |
| - name: "user_manual" | |
| directory: "user_manual" | |
| make_target: "html" | |
| build_path: "_build/html" | |
| publish: true | |
| - name: "user_manual-en" | |
| directory: "user_manual" | |
| make_target: "html-lang-en" | |
| build_path: "_build/html" | |
| publish: false | |
| - name: "developer_manual" | |
| directory: "developer_manual" | |
| make_target: "html" | |
| build_path: "_build/html/com" | |
| publish: true | |
| - name: "admin_manual" | |
| directory: "admin_manual" | |
| make_target: "html" | |
| build_path: "_build/html/com" | |
| publish: true | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.13" | |
| cache: "pip" | |
| - name: Install pip dependencies | |
| run: python -m pip install -r requirements.txt | |
| - name: Build html documentation | |
| run: cd ${{ matrix.manual.directory }} && make ${{ matrix.manual.make_target }} | |
| - name: Upload static documentation | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| if: ${{ matrix.manual.publish }} | |
| with: | |
| name: ${{ matrix.manual.name }} | |
| path: ${{ matrix.manual.directory }}/${{ matrix.manual.build_path }} | |
| # ============================================================================ | |
| # BUILD PDF | |
| # ============================================================================ | |
| # Builds the PDF documentation using the pre-built sphinx-latex Docker image. | |
| # The image already contains all LaTeX packages, so no apt install is needed. | |
| # Starts immediately without waiting for any setup job. | |
| # ============================================================================ | |
| build-pdf: | |
| name: Building ${{ matrix.manual.name }} PDF | |
| runs-on: ubuntu-latest | |
| # Use the pre-built sphinx-latex image which has all LaTeX packages pre-installed. | |
| # The image is built from .docker/sphinx-latex/Dockerfile by the docker-build.yml workflow | |
| # and published to GHCR. Using latest ensures we always use the current image for this repo. | |
| container: ghcr.io/nextcloud/documentation/sphinx-latex:latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| manual: | |
| - name: "user_manual" | |
| directory: "user_manual" | |
| build_pdf_path: "_build/latex/Nextcloud_User_Manual.pdf" | |
| - name: "admin_manual" | |
| directory: "admin_manual" | |
| build_pdf_path: "_build/latex/Nextcloud_Server_Administration_Manual.pdf" | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.13" | |
| # pip cache is not compatible with the Docker container | |
| # cache: "pip" | |
| - name: Install pip dependencies | |
| run: python -m pip install -r requirements.txt | |
| - name: Compute PDF release version | |
| id: pdf_version | |
| run: | | |
| branch="${GITHUB_REF#refs/heads/}" | |
| if [[ "$branch" == stable* ]]; then | |
| echo "release=${branch#stable}" >> $GITHUB_OUTPUT | |
| else | |
| echo "release=latest" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Build pdf documentation | |
| env: | |
| DOCS_RELEASE: ${{ steps.pdf_version.outputs.release }} | |
| run: | | |
| set -e | |
| cd ${{ matrix.manual.directory }} | |
| make latexpdf | |
| ls -la ${{ matrix.manual.build_pdf_path }} | |
| - name: Upload PDF documentation | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ${{ matrix.manual.name }}-pdf | |
| path: ${{ matrix.manual.directory }}/${{ matrix.manual.build_pdf_path }} | |
| # ============================================================================ | |
| # STAGE AND VALIDATE | |
| # ============================================================================ | |
| # This job is responsible for: | |
| # 1. Determining deployment target folder names (branch_name/version_name) | |
| # 2. Organizing build artifacts into a clean structure | |
| # 3. Validating the documentation (link checking) | |
| # 4. Uploading a minimal staging artifact for the deploy job | |
| # | |
| # IMPORTANT: This job does NOT modify gh-pages. It only prepares and validates | |
| # the artifacts that will be deployed. The actual deployment happens in the | |
| # deploy job. | |
| # ============================================================================ | |
| stage-and-check: | |
| name: Stage and check documentation | |
| needs: [build-html, build-pdf] | |
| runs-on: ubuntu-latest | |
| outputs: | |
| # branch_name: The primary deployment folder name for this branch | |
| # - master β "latest" | |
| # - stable<N> (if highest) β "stable" | |
| # - stable<N> (if not highest) β "<N>" (numeric version) | |
| branch_name: ${{ steps.branch.outputs.branch_name }} | |
| # additional_deployment: ONLY set if deploying the highest stable branch | |
| # - If this IS the highest stable β "<N>" (numeric version, e.g. "32") | |
| # - Otherwise β "" (empty string) | |
| # | |
| # This allows the highest stable to be deployed to TWO locations: | |
| # server/stable/ (via branch_name) | |
| # server/<N>/ (via additional_deployment) | |
| additional_deployment: ${{ steps.branch.outputs.additional_deployment }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| path: artifacts/ | |
| # ======================================================================== | |
| # DETERMINE DEPLOYMENT TARGETS (branch_name and version_name) | |
| # ======================================================================== | |
| # Logic: | |
| # 1. Determine current_branch: use GITHUB_REF if push, GITHUB_BASE_REF if PR | |
| # 2. Find the highest numbered stable branch from git remotes | |
| # 3. Map the current branch to deployment folder names: | |
| # | |
| # master β branch_name=latest (no version_name) | |
| # | |
| # stable<N> where N is highest β branch_name=stable, version_name=<N> | |
| # (deployed to both server/stable/ and server/<N>/) | |
| # | |
| # stable<N> where N is not highest β branch_name=<N> (no version_name) | |
| # (deployed only to server/<N>/) | |
| # | |
| # Any other branch β branch_name=<branch> (no version_name) | |
| # ======================================================================== | |
| - name: Determine deployment targets (branch_name and version_name) | |
| id: branch | |
| run: | | |
| # Determine which branch we're building from | |
| current_branch=${GITHUB_REF#refs/heads/} | |
| if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then | |
| current_branch=${GITHUB_BASE_REF} | |
| fi | |
| # Find the highest numbered stable branch from the remote | |
| # e.g., "stable30", "stable31", "stable32" β extract "32" | |
| highest_stable=$(git ls-remote --heads origin | sed -n 's?.*refs/heads/stable\([0-9]\{2\}\)$?\1?p' | sort -n | tail -1) | |
| highest_stable_branch="stable${highest_stable}" | |
| echo "Current branch: $current_branch" | |
| echo "Highest stable branch found: $highest_stable_branch" | |
| # Map branch to deployment folder names | |
| case "$current_branch" in | |
| "master") | |
| # master always deploys to "latest" | |
| echo "branch_name=latest" >> $GITHUB_OUTPUT | |
| ;; | |
| "$highest_stable_branch") | |
| # Highest stable gets TWO locations: both "stable" and "<N>" | |
| echo "branch_name=stable" >> $GITHUB_OUTPUT | |
| echo "additional_deployment=${highest_stable}" >> $GITHUB_OUTPUT | |
| ;; | |
| *) | |
| # Other branches (including older stable branches) get their branch name | |
| # For stable<N> where N is not highest: strip "stable" prefix to get just "<N>" | |
| branch_for_deploy="${current_branch#stable}" | |
| echo "branch_name=$branch_for_deploy" >> $GITHUB_OUTPUT | |
| ;; | |
| esac | |
| - name: Log deployment targets | |
| run: | | |
| echo "Deployment target folder: ${{ steps.branch.outputs.branch_name }}" | |
| echo "Additional deployment folder (if applicable): ${{ steps.branch.outputs.additional_deployment }}" | |
| # ======================================================================== | |
| # ORGANIZE ARTIFACTS FOR DEPLOYMENT | |
| # ======================================================================== | |
| # Create a clean, minimal staging structure: | |
| # - Deploy only the NEW artifacts for this branch | |
| # - No need to include existing versions (we'll merge them during deploy) | |
| # ======================================================================== | |
| - name: Organize artifacts for deployment | |
| id: organize | |
| run: | | |
| branch="${{ steps.branch.outputs.branch_name }}" | |
| # Create the branch folder directly | |
| mkdir -p "stage/${branch}" | |
| # Copy artifacts preserving their manual folder structure | |
| # Each artifact (user_manual, admin_manual, developer_manual) contains | |
| # the build output that should be placed in a folder named after the artifact | |
| for artifact in artifacts/*; do | |
| if [ -d "$artifact" ]; then | |
| manual_name="$(basename "$artifact")" | |
| # Create the manual-specific folder | |
| mkdir -p "stage/${branch}/${manual_name}" | |
| # Copy artifact contents into the manual folder | |
| cp -r "$artifact/"* "stage/${branch}/${manual_name}/" | |
| fi | |
| done | |
| # Move PDF files to the root of the branch folder for cleaner structure | |
| echo "Looking for PDF files to move..." | |
| find "stage/${branch}/" -maxdepth 2 -name "*.pdf" -type f | |
| for pdf in "stage/${branch}"/*/*.pdf; do | |
| if [ -f "$pdf" ]; then | |
| echo "Moving PDF: $pdf" | |
| mv "$pdf" "stage/${branch}/" | |
| fi | |
| done | |
| # Clean up empty directories | |
| find stage -type d -empty -delete | |
| echo "Staged artifacts for ${branch}:" | |
| find stage -type f | head -20 | |
| # ======================================================================== | |
| # UPLOAD STAGING ARTIFACTS | |
| # ======================================================================== | |
| # Upload the staging folder for use in both the deploy and link-check jobs. | |
| # ======================================================================== | |
| - name: Upload staged artifacts | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: staged-docs | |
| path: stage/ | |
| retention-days: 1 | |
| # ============================================================================ | |
| # LINK CHECK | |
| # ============================================================================ | |
| # Runs in parallel with deploy. Downloads the staged artifacts, strips | |
| # canonical links, then runs lychee against the new content only. | |
| # ============================================================================ | |
| link-check: | |
| name: Check for broken links | |
| needs: stage-and-check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download staged artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: staged-docs | |
| path: stage/ | |
| - name: Strip canonical links from validation HTML | |
| run: | | |
| find "stage/${{ needs.stage-and-check.outputs.branch_name }}" -name '*.html' -print0 | while IFS= read -r -d '' f; do | |
| perl -0pi -e 's{^\s*<link rel="canonical" href="https://docs\.nextcloud\.com/server/[^"]*" />\n}{}m' "$f" | |
| done | |
| ls -la stage/* | |
| # We need to exclude certain links from the check: | |
| # - go.php: This is a special redirect page | |
| # - mailto: links: These are not valid URLs and will always fail | |
| # - 404.html: This is not necessary | |
| # - latest/stable/xx links from the version selector | |
| - name: Check for broken links with lychee | |
| uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2.8.0 | |
| with: | |
| fail: true | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| jobSummary: true | |
| args: | | |
| --root-dir "$(pwd)/stage" | |
| --offline --no-progress | |
| --remap "https://docs.nextcloud.com/server/ file://$(pwd)/stage/" | |
| --exclude 'go\.php' --exclude 'mailto:' --exclude-path '.*/404\.html' --exclude-path '.*/_static/.*' | |
| --exclude "/user_manual/" --include "/user_manual/en/" | |
| --exclude '^file://.*/stage/(latest|stable|[0-9]+)/(developer_manual|admin_manual|user_manual)/?$' | |
| 'stage/${{ needs.stage-and-check.outputs.branch_name }}/user_manual/en/**/*.html' | |
| 'stage/${{ needs.stage-and-check.outputs.branch_name }}/admin_manual/**/*.html' | |
| 'stage/${{ needs.stage-and-check.outputs.branch_name }}/developer_manual/**/*.html' | |
| # ============================================================================ | |
| # DEPLOY | |
| # ============================================================================ | |
| # This job is responsible for: | |
| # 1. Downloading the staged artifacts from stage-and-check | |
| # 2. Applying them to the gh-pages branch | |
| # 3. Creating a pull request for the deployment | |
| # | |
| # This job ONLY runs on pushes (not on pull requests), since we only want | |
| # to deploy when code is merged to master or a stable branch. | |
| # ============================================================================ | |
| deploy: | |
| name: Deploy documentation for gh-pages | |
| needs: stage-and-check | |
| if: github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout gh-pages branch | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| ref: gh-pages | |
| fetch-depth: 1 | |
| persist-credentials: false | |
| - name: Download staged artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: staged-docs | |
| path: stage/ | |
| # ======================================================================== | |
| # APPLY STAGED ARTIFACTS TO GH-PAGES | |
| # ======================================================================== | |
| # Strategy: | |
| # - Copy from stage/<branch_name>/ to server/<branch_name>/ | |
| # - If version_name is set, ALSO copy to server/<version_name>/ | |
| # - This allows the highest stable to live in both locations | |
| # ======================================================================== | |
| - name: Apply staged artifacts to gh-pages | |
| id: apply | |
| run: | | |
| branch="${{ needs.stage-and-check.outputs.branch_name }}" | |
| additional="${{ needs.stage-and-check.outputs.additional_deployment }}" | |
| # Copy built documentation into server folder | |
| mkdir -p server/${branch} | |
| for artifact in stage/${branch}/*; do | |
| if [ -d "$artifact" ]; then | |
| manual_name="$(basename "$artifact")" | |
| rm -rf "server/${branch}/${manual_name}" | |
| cp -r "$artifact" "server/${branch}/${manual_name}" | |
| fi | |
| done | |
| # Move pdf files to the root of the branch folder | |
| echo "Looking for PDF files to copy..." | |
| find stage/${branch}/ -name "*.pdf" -type f | |
| for pdf in stage/${branch}/*.pdf; do | |
| if [ -f "$pdf" ]; then | |
| echo "Copying PDF: $pdf" | |
| cp "$pdf" server/${branch}/ | |
| fi | |
| done | |
| # If this is the highest stable branch, also deploy to its versioned folder | |
| if [ -n "${additional}" ]; then | |
| rm -rf server/${additional} | |
| cp -r server/${branch} server/${additional} | |
| fi | |
| # Cleanup empty directories | |
| find . -type d -empty -delete | |
| # Check if there are actual changes | |
| if git diff --quiet HEAD; then | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| fi | |
| # Remove the stage/ directory BEFORE creating the PR so it doesn't get committed | |
| - name: Clean up staging cache before commit | |
| run: rm -rf stage/ | |
| # ======================================================================== | |
| # ADD REDIRECT FILES | |
| # ======================================================================== | |
| - name: Add various redirects for go.php and user_manual english version | |
| run: | | |
| branch="${{ needs.stage-and-check.outputs.branch_name }}" | |
| additional="${{ needs.stage-and-check.outputs.additional_deployment }}" | |
| git fetch origin ${{ github.event.repository.default_branch }} ${{ github.ref_name }} | |
| # Add go.php redirect from main branch | |
| git checkout origin/${{ github.event.repository.default_branch }} -- go.php/index.html | |
| mkdir -p server/${branch}/go.php | |
| mv go.php/index.html server/${branch}/go.php/index.html | |
| # Add user_manual english redirect | |
| git checkout origin/${{ github.ref_name }} -- user_manual/index.html | |
| mkdir -p server/${branch}/user_manual | |
| mv user_manual/index.html server/${branch}/user_manual/index.html | |
| # Also copy to versioned folder if applicable | |
| if [ -n "${additional}" ]; then | |
| mkdir -p server/${additional}/go.php server/${additional}/user_manual | |
| cp server/${branch}/go.php/index.html server/${additional}/go.php/ | |
| cp server/${branch}/user_manual/index.html server/${additional}/user_manual/ | |
| fi | |
| - name: Create Pull Request for documentation deployment | |
| uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 | |
| id: cpr | |
| if: steps.apply.outputs.has_changes == 'true' | |
| with: | |
| token: ${{ secrets.COMMAND_BOT_PAT }} | |
| commit-message: "chore: update documentation for `${{ needs.stage-and-check.outputs.branch_name }}`" | |
| committer: nextcloud-command <nextcloud-command@users.noreply.github.com> | |
| author: nextcloud-command <nextcloud-command@users.noreply.github.com> | |
| signoff: true | |
| branch: "automated/deploy/documentation-${{ needs.stage-and-check.outputs.branch_name }}" | |
| base: gh-pages | |
| title: "Documentation update for `${{ needs.stage-and-check.outputs.branch_name }}`" | |
| body: | | |
| This PR was automatically generated by the CI workflow and | |
| includes the latest changes for the `${{ needs.stage-and-check.outputs.branch_name }}` branch. | |
| delete-branch: true | |
| labels: "automated, 3. to review" | |
| - name: Enable Pull Request Automerge | |
| run: gh pr merge --merge --auto "${{ steps.cpr.outputs.pull-request-number }}" | |
| if: steps.cpr.outputs.pull-request-number != '' | |
| env: | |
| GH_TOKEN: ${{ secrets.COMMAND_BOT_PAT }} | |
| summary: | |
| needs: [stage-and-check, link-check, deploy] | |
| runs-on: ubuntu-latest-low | |
| if: always() | |
| permissions: | |
| contents: read | |
| name: build-deploy-summary | |
| steps: | |
| - name: Summary status | |
| run: | | |
| if ${{ github.event_name == 'pull_request' }} | |
| then | |
| echo "This workflow ran for a pull request. We need stage-and-check and link-check to succeed, but deploy will be skipped" | |
| if ${{ needs.stage-and-check.result != 'success' || needs.link-check.result != 'success' || needs.deploy.result != 'skipped' }}; then exit 1; fi | |
| else | |
| echo "This workflow ran for a push. We need all jobs to succeed, including deploy" | |
| if ${{ needs.stage-and-check.result != 'success' || needs.link-check.result != 'success' || needs.deploy.result != 'success' }}; then exit 1; fi | |
| fi |