mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
Bumps [dorny/paths-filter](https://github.com/dorny/paths-filter) from 4.0.2 to 4.0.3. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/dorny/paths-filter/releases">dorny/paths-filter's releases</a>.</em></p> <blockquote> <h2>v4.0.3</h2> <h2>What's Changed</h2> <ul> <li>Update Outputs in readme to account for the 'every' predicate-quantifier by <a href="https://github.com/hintron"><code>@hintron</code></a> in <a href="https://redirect.github.com/dorny/paths-filter/pull/247">dorny/paths-filter#247</a></li> <li>fix: scope base-ignored warning to API path by <a href="https://github.com/saschabratton"><code>@saschabratton</code></a> in <a href="https://redirect.github.com/dorny/paths-filter/pull/319">dorny/paths-filter#319</a></li> <li>docs: add contents permission to PR example by <a href="https://github.com/134130"><code>@134130</code></a> in <a href="https://redirect.github.com/dorny/paths-filter/pull/248">dorny/paths-filter#248</a></li> <li>feat: add 'some-with-excludes' predicate quantifier by <a href="https://github.com/arxeiss"><code>@arxeiss</code></a> in <a href="https://redirect.github.com/dorny/paths-filter/pull/322">dorny/paths-filter#322</a></li> <li>Document safe handling of file list outputs in workflows by <a href="https://github.com/dorny"><code>@dorny</code></a> in <a href="https://redirect.github.com/dorny/paths-filter/pull/326">dorny/paths-filter#326</a></li> </ul> <h2>Security</h2> <ul> <li>Escape multi-line filenames in list-files shell and csv output] by <a href="https://github.com/ken-matsui"><code>@ken-matsui</code></a> and <a href="https://github.com/tjswlsgg"><code>@tjswlsgg</code></a> in <a href="https://github.com/advisories/GHSA-7hc6-8hq5-9q2m">https://github.com/advisories/GHSA-7hc6-8hq5-9q2m</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/hintron"><code>@hintron</code></a> made their first contribution in <a href="https://redirect.github.com/dorny/paths-filter/pull/247">dorny/paths-filter#247</a></li> <li><a href="https://github.com/134130"><code>@134130</code></a> made their first contribution in <a href="https://redirect.github.com/dorny/paths-filter/pull/248">dorny/paths-filter#248</a></li> <li><a href="https://github.com/arxeiss"><code>@arxeiss</code></a> made their first contribution in <a href="https://redirect.github.com/dorny/paths-filter/pull/322">dorny/paths-filter#322</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/dorny/paths-filter/compare/v4...v4.0.3">https://github.com/dorny/paths-filter/compare/v4...v4.0.3</a></p> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md">dorny/paths-filter's changelog</a>.</em></p> <blockquote> <h1>Changelog</h1> <h2>v4.0.3</h2> <ul> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/326">Document safe handling of file list outputs in workflows</a></li> <li><a href="https://github.com/advisories/GHSA-7hc6-8hq5-9q2m">Escape multi-line filenames in list-files shell and csv output</a></li> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/322">Add 'some-with-excludes' predicate quantifier</a></li> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/248">Add contents permission to PR example</a></li> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/319">Scope base-ignored warning to API path</a></li> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/247">Update outputs in readme to account for the 'every' predicate-quantifier</a></li> </ul> <h2>v4.0.2</h2> <ul> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/317">Work around git dubious ownership errors in container jobs</a></li> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/303">Use rev-parse instead of branch --show-current for older git compat</a></li> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/282">Fix warning message</a></li> </ul> <h2>v4.0.1</h2> <ul> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/255">Support merge queue</a></li> </ul> <h2>v4.0.0</h2> <ul> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/294">Update action runtime to node24</a></li> </ul> <h2>v3.0.4</h2> <ul> <li><a href="https://github.com/advisories/GHSA-7hc6-8hq5-9q2m">Escape multi-line filenames in list-files shell and csv output</a></li> </ul> <h2>v3.0.3</h2> <ul> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/279">Add missing predicate-quantifier</a></li> </ul> <h2>v3.0.2</h2> <ul> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/224">Add config parameter for predicate quantifier</a></li> </ul> <h2>v3.0.1</h2> <ul> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/133">Compare base and ref when token is empty</a></li> </ul> <h2>v3.0.0</h2> <ul> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/210">Update to Node.js 20</a></li> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/215">Update all dependencies</a></li> </ul> <h2>v2.11.1</h2> <ul> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/167">Update @actions/core to v1.10.0 - Fixes warning about deprecated set-output</a></li> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/168">Document need for pull-requests: read permission</a></li> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/164">Updating to actions/checkout@v3</a></li> </ul> <h2>v2.11.0</h2> <ul> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/157">Set list-files input parameter as not required</a></li> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/161">Update Node.js</a></li> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/162">Fix incorrect handling of Unicode characters in exec()</a></li> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/163">Use Octokit pagination</a></li> <li><a href="https://redirect.github.com/dorny/paths-filter/pull/160">Updates real world links</a></li> </ul> <h2>v2.10.2</h2> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/dorny/paths-filter/commit/ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d"><code>ceb8a2b</code></a> Update CHANGELOG.md for v4.0.3 and v3.0.4 (<a href="https://redirect.github.com/dorny/paths-filter/issues/327">#327</a>)</li> <li><a href="https://github.com/dorny/paths-filter/commit/ef09b88f3eacdbec6ce135a7c9a193a6849545c1"><code>ef09b88</code></a> Document safe handling of file list outputs in workflows (<a href="https://redirect.github.com/dorny/paths-filter/issues/326">#326</a>)</li> <li><a href="https://github.com/dorny/paths-filter/commit/44adc5b06dc135dba334efce9bf3cf0624512d2d"><code>44adc5b</code></a> Merge commit from fork</li> <li><a href="https://github.com/dorny/paths-filter/commit/4711b7a31b4aa89103d8c6ffab2e3b8e7b6381c7"><code>4711b7a</code></a> feat: add 'some-with-excludes' predicate quantifier (<a href="https://redirect.github.com/dorny/paths-filter/issues/322">#322</a>)</li> <li><a href="https://github.com/dorny/paths-filter/commit/93c889f9e58fca66f35a0c83d8673ac7e88bb70a"><code>93c889f</code></a> fix: escape multi-line filenames in list-files shell and csv output</li> <li><a href="https://github.com/dorny/paths-filter/commit/b41dfa943b1939b9b646f67753bfe35cf6e4de03"><code>b41dfa9</code></a> docs: add contents permission to PR example (<a href="https://redirect.github.com/dorny/paths-filter/issues/248">#248</a>)</li> <li><a href="https://github.com/dorny/paths-filter/commit/9af6e5a9d010d1ae8ec570390b3d793e2b70a402"><code>9af6e5a</code></a> fix: scope base-ignored warning to API path (<a href="https://redirect.github.com/dorny/paths-filter/issues/319">#319</a>)</li> <li><a href="https://github.com/dorny/paths-filter/commit/cae9006b65a1a53044b518c68e13e835c54948a7"><code>cae9006</code></a> docs: update outputs in readme to account for the 'every' predicate-quantifie...</li> <li>See full diff in <a href="https://github.com/dorny/paths-filter/compare/7b450fff21473bca461d4b92ce414b9d0420d706...ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
562 lines
23 KiB
YAML
562 lines
23 KiB
YAML
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
|
|
# Only reads the PR via pulls.get with the default GITHUB_TOKEN.
|
|
permissions:
|
|
contents: read
|
|
pull-requests: read
|
|
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@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
|
with:
|
|
egress-policy: audit
|
|
|
|
- name: Resolve PR info
|
|
id: resolve
|
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.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" "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:
|
|
environment: pr-preview
|
|
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
|
|
packages: write
|
|
pull-requests: write
|
|
env:
|
|
# Single source of truth for whether this preview embeds the admin portal:
|
|
# drives the image build-arg and the deployment comment.
|
|
BUILD_PORTAL: "true"
|
|
|
|
steps:
|
|
- name: Harden Runner
|
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
|
with:
|
|
egress-policy: audit
|
|
|
|
- name: Checkout main repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
repository: ${{ github.repository }}
|
|
ref: main
|
|
|
|
- name: Add deployment started comment
|
|
id: deployment-started
|
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
with:
|
|
github-token: ${{ github.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 }}
|
|
# untrusted tree is built below - never leave credentials in .git/config
|
|
persist-credentials: false
|
|
fetch-depth: 0 # Fetch full history for commit hash detection
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.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 GitHub Container Registry
|
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ github.token }}
|
|
|
|
- name: Convert repository owner to lowercase
|
|
id: repoowner
|
|
run: echo "lowercase=$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" >> $GITHUB_OUTPUT
|
|
|
|
- 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 ${IMAGE_BASE}: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
|
|
|
|
env:
|
|
IMAGE_BASE: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test
|
|
- name: Build and push V2 image
|
|
if: steps.check-image.outputs.exists == 'false'
|
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.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: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:v2-${{ steps.commit-hash.outputs.app_short }}
|
|
build-args: |
|
|
VERSION_TAG=v2-alpha
|
|
BUILD_PORTAL=${{ env.BUILD_PORTAL }}
|
|
platforms: linux/amd64
|
|
|
|
- name: Set up SSH
|
|
run: |
|
|
mkdir -p ~/.ssh/
|
|
echo "${NEW_VPS_SSH_KEY}" > ../private.key
|
|
sudo chmod 600 ../private.key
|
|
|
|
env:
|
|
NEW_VPS_SSH_KEY: ${{ secrets.NEW_VPS_SSH_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: ${IMAGE_BASE}: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
|
|
- /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/storage:/storage:rw
|
|
environment:
|
|
DISABLE_ADDITIONAL_FEATURES: "false"
|
|
STIRLING_BILLING_ACCOUNT_LINK_ENABLED: "true"
|
|
SECURITY_ENABLELOGIN: "true"
|
|
SECURITY_INITIALLOGIN_USERNAME: "${TEST_LOGIN_USERNAME}"
|
|
SECURITY_INITIALLOGIN_PASSWORD: "${TEST_LOGIN_PASSWORD}"
|
|
SYSTEM_DEFAULTLOCALE: en-US
|
|
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 ${NEW_VPS_USERNAME}@${NEW_VPS_HOST}:/tmp/docker-compose-v2.yml
|
|
|
|
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${NEW_VPS_USERNAME}@${NEW_VPS_HOST} << ENDSSH
|
|
# Create V2 PR-specific directories
|
|
mkdir -p /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/{data,config,logs,storage}
|
|
|
|
# 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
|
|
|
|
env:
|
|
IMAGE_BASE: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test
|
|
TEST_LOGIN_USERNAME: ${{ secrets.TEST_LOGIN_USERNAME }}
|
|
TEST_LOGIN_PASSWORD: ${{ secrets.TEST_LOGIN_PASSWORD }}
|
|
NEW_VPS_USERNAME: ${{ secrets.NEW_VPS_USERNAME }}
|
|
NEW_VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
|
|
|
|
# ---- Storybook preview (only when this PR touches stories/.storybook) ----
|
|
# Runs inside the same approved-contributor-gated deploy job, so it deploys
|
|
# under the exact same access rules as the app preview.
|
|
- name: Detect Storybook changes
|
|
id: sb-changes
|
|
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
|
|
with:
|
|
list-files: json
|
|
filters: |
|
|
storybook:
|
|
- 'frontend/**/*.stories.@(ts|tsx|mdx)'
|
|
- 'frontend/**/*.mdx'
|
|
- 'frontend/.storybook/**'
|
|
|
|
- name: Set up Node.js for Storybook
|
|
if: steps.sb-changes.outputs.storybook == 'true'
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: "22"
|
|
cache: "npm"
|
|
cache-dependency-path: frontend/package-lock.json
|
|
|
|
- name: Install Task for Storybook
|
|
if: steps.sb-changes.outputs.storybook == 'true'
|
|
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
|
|
|
|
- name: Build and deploy Storybook
|
|
id: storybook
|
|
if: steps.sb-changes.outputs.storybook == 'true'
|
|
env:
|
|
VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
|
|
VPS_USER: ${{ secrets.NEW_VPS_USERNAME }}
|
|
run: |
|
|
set -euo pipefail
|
|
# `prepare` generates the icon set stories import (not committed).
|
|
task frontend:prepare
|
|
task frontend:storybook:build
|
|
PR=${{ needs.check-pr.outputs.pr_number }}
|
|
# Served at the ROOT of its own port so Storybook's global MSW worker
|
|
# (/mockServiceWorker.js) resolves. Port = PR + 20000 (bijective, offset
|
|
# from the app preview's bare-PR-number port).
|
|
SB_PORT=$((PR + 20000))
|
|
DIR=/stirling/SB-PR-$PR
|
|
tar czf storybook.tgz -C frontend/storybook-static .
|
|
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
|
storybook.tgz "$VPS_USER@$VPS_HOST:/tmp/storybook-$PR.tgz"
|
|
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T \
|
|
"$VPS_USER@$VPS_HOST" << ENDSSH
|
|
set -e
|
|
rm -rf "$DIR" && mkdir -p "$DIR"
|
|
tar xzf /tmp/storybook-$PR.tgz -C "$DIR"
|
|
rm -f /tmp/storybook-$PR.tgz
|
|
docker rm -f storybook-pr-$PR 2>/dev/null || true
|
|
docker run -d --name storybook-pr-$PR --restart unless-stopped \
|
|
-p $SB_PORT:80 -v "$DIR":/usr/share/nginx/html:ro nginx:alpine
|
|
ENDSSH
|
|
echo "url=http://$VPS_HOST:$SB_PORT/" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Post V2 deployment URL to PR
|
|
if: success()
|
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
env:
|
|
SB_URL: ${{ steps.storybook.outputs.url }}
|
|
SB_FILES: ${{ steps.sb-changes.outputs.storybook_files }}
|
|
NEW_VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
|
|
with:
|
|
github-token: ${{ github.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://${process.env.NEW_VPS_HOST}:${v2Port}`;
|
|
|
|
// Only mention the portal when this image actually embeds it.
|
|
// Use the direct IP URL - the SSL hostname isn't supported yet.
|
|
const withPortal = "${{ env.BUILD_PORTAL }}" === "true";
|
|
const portalNote = withPortal
|
|
? `🧩 **Admin portal** included - try it at [${deploymentUrl}/portal](${deploymentUrl}/portal).\n\n`
|
|
: ``;
|
|
|
|
// Storybook preview: only present when this PR changed stories/config.
|
|
const sbUrl = process.env.SB_URL;
|
|
let storybookNote = "";
|
|
if (sbUrl) {
|
|
const files = JSON.parse(process.env.SB_FILES || "[]");
|
|
const stories = files.filter((f) => /\.stories\.(ts|tsx|mdx)$/.test(f));
|
|
const config = files.filter((f) => f.startsWith("frontend/.storybook/"));
|
|
const shorten = (f) =>
|
|
f.replace(/^frontend\/editor\/src\//, "").replace(/^frontend\//, "");
|
|
const storyList = stories.map((f) => `- \`${shorten(f)}\``).join("\n");
|
|
const configList = config.map((f) => `- \`${shorten(f)}\``).join("\n");
|
|
const summary =
|
|
`${stories.length} stor${stories.length === 1 ? "y" : "ies"} changed` +
|
|
(config.length ? ` (+${config.length} config file${config.length === 1 ? "" : "s"})` : "");
|
|
storybookNote =
|
|
`📚 **Storybook:** [${sbUrl}](${sbUrl})\n\n` +
|
|
`<details>\n<summary>${summary}</summary>\n\n` +
|
|
(storyList ? `**Stories**\n${storyList}\n\n` : "") +
|
|
(configList ? `**Config**\n${configList}\n` : "") +
|
|
`</details>\n\n`;
|
|
}
|
|
|
|
const commentBody = `## 🚀 V2 Auto-Deployment Complete!\n\n` +
|
|
`🔗 **Direct Test URL (non-SSL)** [${deploymentUrl}](${deploymentUrl})\n\n` +
|
|
portalNote +
|
|
storybookNote +
|
|
`_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:
|
|
environment: pr-preview
|
|
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@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
|
with:
|
|
egress-policy: audit
|
|
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Clean up V2 deployment comments
|
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
with:
|
|
github-token: ${{ github.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 "${NEW_VPS_SSH_KEY}" > ../private.key
|
|
sudo chmod 600 ../private.key
|
|
|
|
env:
|
|
NEW_VPS_SSH_KEY: ${{ secrets.NEW_VPS_SSH_KEY }}
|
|
- name: Cleanup V2 deployment
|
|
run: |
|
|
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${NEW_VPS_USERNAME}@${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
|
|
|
|
# Remove this PR's Storybook preview (container + files), if any.
|
|
docker rm -f storybook-pr-${{ github.event.pull_request.number }} 2>/dev/null || true
|
|
rm -rf /stirling/SB-PR-${{ github.event.pull_request.number }}
|
|
|
|
# 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
|
|
|
|
env:
|
|
NEW_VPS_USERNAME: ${{ secrets.NEW_VPS_USERNAME }}
|
|
NEW_VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
|
|
- name: Cleanup temporary files
|
|
if: always()
|
|
run: |
|
|
rm -f ../private.key
|
|
continue-on-error: true
|