Add specific View Scope For Selected Files #7234
Workflow file for this run
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: Auto PR V2 Deployment | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, closed] | |
| workflow_dispatch: | |
| inputs: | |
| pr: | |
| description: "PR number to deploy" | |
| required: true | |
| allow_fork: | |
| description: "Allow deploying fork PR?" | |
| required: false | |
| type: choice | |
| options: | |
| - "true" | |
| - "false" | |
| default: "false" | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| jobs: | |
| check-pr: | |
| if: (github.event_name == 'pull_request' && github.event.action != 'closed') || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_deploy: ${{ steps.decide.outputs.should_deploy }} | |
| is_fork: ${{ steps.resolve.outputs.is_fork }} | |
| allow_fork: ${{ steps.decide.outputs.allow_fork }} | |
| pr_number: ${{ steps.resolve.outputs.pr_number }} | |
| pr_repository: ${{ steps.resolve.outputs.repository }} | |
| pr_ref: ${{ steps.resolve.outputs.ref }} | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 | |
| with: | |
| egress-policy: audit | |
| - name: Resolve PR info | |
| id: resolve | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| with: | |
| script: | | |
| const { owner, repo } = context.repo; | |
| let prNumber = context.eventName === 'workflow_dispatch' | |
| ? parseInt(context.payload.inputs.pr, 10) | |
| : context.payload.number; | |
| if (!Number.isInteger(prNumber)) { core.setFailed('Invalid PR number'); return; } | |
| const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber }); | |
| core.setOutput('pr_number', String(prNumber)); | |
| core.setOutput('repository', pr.head.repo.full_name); | |
| core.setOutput('ref', pr.head.ref); | |
| core.setOutput('is_fork', String(pr.head.repo.fork)); | |
| core.setOutput('author', pr.user.login); | |
| core.setOutput('state', pr.state); | |
| - name: Decide deploy | |
| id: decide | |
| shell: bash | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| STATE: ${{ steps.resolve.outputs.state }} | |
| IS_FORK: ${{ steps.resolve.outputs.is_fork }} | |
| # nur bei workflow_dispatch gesetzt: | |
| ALLOW_FORK_INPUT: ${{ inputs.allow_fork }} | |
| PR_AUTHOR: ${{ steps.resolve.outputs.author }} | |
| run: | | |
| set -e | |
| # Standard: nichts deployen | |
| should=false | |
| allow_fork="$(echo "${ALLOW_FORK_INPUT:-false}" | tr '[:upper:]' '[:lower:]')" | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | |
| if [ "$STATE" != "open" ]; then | |
| echo "PR not open -> skip" | |
| else | |
| if [ "$IS_FORK" = "true" ] && [ "$allow_fork" != "true" ]; then | |
| echo "Fork PR and allow_fork=false -> skip" | |
| else | |
| should=true | |
| fi | |
| fi | |
| else | |
| auth_users=("Frooodle" "sf298" "Ludy87" "LaserKaspar" "sbplat" "reecebrowne" "DarioGii" "ConnorYoh" "EthanHealy01" "jbrunton96" "balazs-szucs") | |
| is_auth=false; for u in "${auth_users[@]}"; do [ "$u" = "$PR_AUTHOR" ] && is_auth=true && break; done | |
| if [ "$is_auth" = true ]; then | |
| should=true | |
| fi | |
| fi | |
| echo "should_deploy=$should" >> $GITHUB_OUTPUT | |
| echo "allow_fork=${allow_fork:-false}" >> $GITHUB_OUTPUT | |
| deploy-v2-pr: | |
| needs: check-pr | |
| runs-on: ubuntu-latest | |
| if: needs.check-pr.outputs.should_deploy == 'true' && (needs.check-pr.outputs.is_fork == 'false' || needs.check-pr.outputs.allow_fork == 'true') | |
| # Concurrency control - only one deployment per PR at a time | |
| concurrency: | |
| group: v2-deploy-pr-${{ needs.check-pr.outputs.pr_number }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout main repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| repository: ${{ github.repository }} | |
| ref: main | |
| - name: Setup GitHub App Bot | |
| if: github.actor != 'dependabot[bot]' | |
| id: setup-bot | |
| uses: ./.github/actions/setup-bot | |
| continue-on-error: true | |
| with: | |
| app-id: ${{ secrets.GH_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| - name: Add deployment started comment | |
| id: deployment-started | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| with: | |
| github-token: ${{ steps.setup-bot.outputs.token }} | |
| script: | | |
| const { owner, repo } = context.repo; | |
| const prNumber = ${{ needs.check-pr.outputs.pr_number }}; | |
| // Delete previous V2 deployment comments to avoid clutter | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner, | |
| repo, | |
| issue_number: prNumber, | |
| per_page: 100 | |
| }); | |
| const v2Comments = comments.filter(comment => | |
| comment.body.includes('🚀 **Auto-deploying V2 version**') || | |
| comment.body.includes('## 🚀 V2 Auto-Deployment Complete!') || | |
| comment.body.includes('❌ **V2 Auto-deployment failed**') | |
| ); | |
| for (const comment of v2Comments) { | |
| console.log(`Deleting old V2 comment: ${comment.id}`); | |
| await github.rest.issues.deleteComment({ | |
| owner, | |
| repo, | |
| comment_id: comment.id | |
| }); | |
| } | |
| // Create new deployment started comment | |
| const { data: newComment } = await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: prNumber, | |
| body: `🚀 **Auto-deploying V2 version** for PR #${prNumber}...\n\n_This is an automated deployment for approved V2 contributors._\n\n⚠️ **Note:** If new commits are pushed during deployment, this build will be cancelled and replaced with the latest version.` | |
| }); | |
| return newComment.id; | |
| - name: Checkout PR | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| repository: ${{ needs.check-pr.outputs.pr_repository }} | |
| ref: ${{ needs.check-pr.outputs.pr_ref }} | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| fetch-depth: 0 # Fetch full history for commit hash detection | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 | |
| - name: Get version number | |
| id: versionNumber | |
| run: | | |
| VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}') | |
| echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT | |
| - name: Login to Docker Hub | |
| uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 | |
| with: | |
| username: ${{ secrets.DOCKER_HUB_USERNAME }} | |
| password: ${{ secrets.DOCKER_HUB_API }} | |
| - name: Get commit hash for app | |
| id: commit-hash | |
| run: | | |
| # Get last commit that touched the application code | |
| APP_HASH=$(git log -1 --format="%H" -- . 2>/dev/null || echo "") | |
| if [ -z "$APP_HASH" ]; then | |
| APP_HASH="no-changes" | |
| fi | |
| echo "App hash: $APP_HASH" | |
| echo "app_hash=$APP_HASH" >> $GITHUB_OUTPUT | |
| # Short hash for tags | |
| if [ "$APP_HASH" = "no-changes" ]; then | |
| echo "app_short=no-changes" >> $GITHUB_OUTPUT | |
| else | |
| echo "app_short=${APP_HASH:0:8}" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Check if image exists | |
| id: check-image | |
| run: | | |
| if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-${{ steps.commit-hash.outputs.app_short }} >/dev/null 2>&1; then | |
| echo "exists=true" >> $GITHUB_OUTPUT | |
| echo "Image already exists, skipping build" | |
| else | |
| echo "exists=false" >> $GITHUB_OUTPUT | |
| echo "Image needs to be built" | |
| fi | |
| - name: Build and push V2 image | |
| if: steps.check-image.outputs.exists == 'false' | |
| uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 | |
| with: | |
| context: . | |
| file: ./docker/embedded/Dockerfile | |
| push: true | |
| cache-from: type=gha,scope=stirling-pdf-latest | |
| cache-to: type=gha,mode=max,scope=stirling-pdf-latest | |
| tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-${{ steps.commit-hash.outputs.app_short }} | |
| build-args: VERSION_TAG=v2-alpha | |
| platforms: linux/amd64 | |
| - name: Set up SSH | |
| run: | | |
| mkdir -p ~/.ssh/ | |
| echo "${{ secrets.NEW_VPS_SSH_KEY }}" > ../private.key | |
| sudo chmod 600 ../private.key | |
| - name: Deploy V2 to VPS | |
| id: deploy | |
| run: | | |
| # Use same port strategy as regular PRs - just the PR number | |
| V2_PORT=${{ needs.check-pr.outputs.pr_number }} | |
| # Create docker-compose for V2 with unified embedded image | |
| cat > docker-compose.yml << EOF | |
| version: '3.3' | |
| services: | |
| stirling-pdf-v2: | |
| container_name: stirling-pdf-v2-pr-${{ needs.check-pr.outputs.pr_number }} | |
| image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-${{ steps.commit-hash.outputs.app_short }} | |
| ports: | |
| - "${V2_PORT}:8080" | |
| volumes: | |
| - /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/data:/usr/share/tessdata:rw | |
| - /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/config:/configs:rw | |
| - /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/logs:/logs:rw | |
| environment: | |
| DISABLE_ADDITIONAL_FEATURES: "false" | |
| SECURITY_ENABLELOGIN: "true" | |
| SECURITY_INITIALLOGIN_USERNAME: "${{ secrets.TEST_LOGIN_USERNAME }}" | |
| SECURITY_INITIALLOGIN_PASSWORD: "${{ secrets.TEST_LOGIN_PASSWORD }}" | |
| SYSTEM_DEFAULTLOCALE: en-GB | |
| UI_APPNAME: "Stirling-PDF V2 PR#${{ needs.check-pr.outputs.pr_number }}" | |
| UI_HOMEDESCRIPTION: "V2 PR#${{ needs.check-pr.outputs.pr_number }} - Embedded Architecture" | |
| UI_APPNAMENAVBAR: "V2 PR#${{ needs.check-pr.outputs.pr_number }}" | |
| SYSTEM_MAXFILESIZE: "100" | |
| METRICS_ENABLED: "true" | |
| SYSTEM_GOOGLEVISIBILITY: "false" | |
| SWAGGER_SERVER_URL: "https://${V2_PORT}.ssl.stirlingpdf.cloud" | |
| baseUrl: "https://${V2_PORT}.ssl.stirlingpdf.cloud" | |
| restart: on-failure:5 | |
| EOF | |
| # Deploy to VPS | |
| scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }}:/tmp/docker-compose-v2.yml | |
| ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }} << ENDSSH | |
| # Create V2 PR-specific directories | |
| mkdir -p /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/{data,config,logs} | |
| # Move docker-compose file to correct location | |
| mv /tmp/docker-compose-v2.yml /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/docker-compose.yml | |
| # Stop any existing container and clean up | |
| cd /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }} | |
| docker-compose down --remove-orphans 2>/dev/null || true | |
| # Start the new container | |
| docker-compose pull | |
| docker-compose up -d | |
| # Clean up unused Docker resources to save space | |
| docker system prune -af --volumes || true | |
| # Clean up old images (older than 2 weeks) | |
| docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true | |
| ENDSSH | |
| # Set port for output | |
| echo "v2_port=${V2_PORT}" >> $GITHUB_OUTPUT | |
| - name: Post V2 deployment URL to PR | |
| if: success() | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| with: | |
| github-token: ${{ steps.setup-bot.outputs.token }} | |
| script: | | |
| const { owner, repo } = context.repo; | |
| const prNumber = ${{ needs.check-pr.outputs.pr_number }}; | |
| const v2Port = ${{ steps.deploy.outputs.v2_port }}; | |
| // Delete the "deploying..." comment since we're posting the final result | |
| const deploymentStartedId = ${{ steps.deployment-started.outputs.result }}; | |
| if (deploymentStartedId) { | |
| console.log(`Deleting deployment started comment: ${deploymentStartedId}`); | |
| try { | |
| await github.rest.issues.deleteComment({ | |
| owner, | |
| repo, | |
| comment_id: deploymentStartedId | |
| }); | |
| } catch (error) { | |
| console.log(`Could not delete deployment started comment: ${error.message}`); | |
| } | |
| } | |
| const deploymentUrl = `http://${{ secrets.NEW_VPS_HOST }}:${v2Port}`; | |
| const httpsUrl = `https://${v2Port}.ssl.stirlingpdf.cloud`; | |
| const commentBody = `## 🚀 V2 Auto-Deployment Complete!\n\n` + | |
| `Your V2 PR with embedded architecture has been deployed!\n\n` + | |
| `🔗 **Direct Test URL (non-SSL)** [${deploymentUrl}](${deploymentUrl})\n\n` + | |
| `🔐 **Secure HTTPS URL**: [${httpsUrl}](${httpsUrl})\n\n` + | |
| `_This deployment will be automatically cleaned up when the PR is closed._\n\n` + | |
| `🔄 **Auto-deployed** for approved V2 contributors.`; | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: prNumber, | |
| body: commentBody | |
| }); | |
| cleanup-v2-deployment: | |
| if: github.event.action == 'closed' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Setup GitHub App Bot | |
| if: github.actor != 'dependabot[bot]' | |
| id: setup-bot | |
| uses: ./.github/actions/setup-bot | |
| continue-on-error: true | |
| with: | |
| app-id: ${{ secrets.GH_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| - name: Clean up V2 deployment comments | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 | |
| with: | |
| github-token: ${{ steps.setup-bot.outputs.token }} | |
| script: | | |
| const { owner, repo } = context.repo; | |
| const prNumber = ${{ github.event.pull_request.number }}; | |
| // Find and delete V2 deployment comments | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner, | |
| repo, | |
| issue_number: prNumber | |
| }); | |
| const v2Comments = comments.filter(c => | |
| c.body?.includes("## 🚀 V2 Auto-Deployment Complete!") && | |
| c.user?.type === "Bot" | |
| ); | |
| for (const comment of v2Comments) { | |
| await github.rest.issues.deleteComment({ | |
| owner, | |
| repo, | |
| comment_id: comment.id | |
| }); | |
| console.log(`Deleted V2 deployment comment (ID: ${comment.id})`); | |
| } | |
| - name: Set up SSH | |
| run: | | |
| mkdir -p ~/.ssh/ | |
| echo "${{ secrets.NEW_VPS_SSH_KEY }}" > ../private.key | |
| sudo chmod 600 ../private.key | |
| - name: Cleanup V2 deployment | |
| run: | | |
| ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }} << 'ENDSSH' | |
| if [ -d "/stirling/V2-PR-${{ github.event.pull_request.number }}" ]; then | |
| echo "Found V2 PR directory, proceeding with cleanup..." | |
| # Stop and remove V2 containers | |
| cd /stirling/V2-PR-${{ github.event.pull_request.number }} | |
| docker-compose down || true | |
| # Go back to root before removal | |
| cd / | |
| # Remove V2 PR-specific directories | |
| rm -rf /stirling/V2-PR-${{ github.event.pull_request.number }} | |
| # Clean up V2 container by name (in case compose cleanup missed it) | |
| docker rm -f stirling-pdf-v2-pr-${{ github.event.pull_request.number }} || true | |
| echo "V2 cleanup completed" | |
| else | |
| echo "V2 PR directory not found, nothing to clean up" | |
| fi | |
| # Clean up old unused images (older than 2 weeks) but keep recent ones for reuse | |
| docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true | |
| # Note: We don't remove the commit-based images since they can be reused across PRs | |
| # Only remove PR-specific containers and directories | |
| ENDSSH | |
| - name: Cleanup temporary files | |
| if: always() | |
| run: | | |
| rm -f ../private.key | |
| continue-on-error: true |