mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
# Description of Changes Removes the feature flags for enabling policies on both the backend and frontend. We shouldn't be releasing another self-hosted release that doesn't include policies, so it makes sense to do this now. Builds that don't have the Processor will just not run policies because they won't have any. Beyond that, the API should always be available, but checks whether the user actually has the entitlements to run policies (whether they have credits/a payment method available)
577 lines
24 KiB
YAML
577 lines
24 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:
|
|
pick:
|
|
uses: ./.github/workflows/_runner-pick.yml
|
|
|
|
check-pr:
|
|
if: (github.event_name == 'pull_request' && github.event.action != 'closed') || github.event_name == 'workflow_dispatch'
|
|
needs: pick
|
|
runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }}
|
|
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@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
|
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" "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: [pick, check-pr]
|
|
runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }}
|
|
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
|
|
id-token: write
|
|
env:
|
|
USE_DEPOT: ${{ needs.pick.outputs.is_fork != 'true' }}
|
|
DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }}
|
|
# 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@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
|
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@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.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 Depot CLI
|
|
if: env.USE_DEPOT == 'true'
|
|
uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.0.0
|
|
|
|
- name: Set up Docker Buildx
|
|
if: env.USE_DEPOT != 'true'
|
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.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@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.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 (Depot)
|
|
if: env.USE_DEPOT == 'true' && steps.check-image.outputs.exists == 'false'
|
|
uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1.16.0
|
|
with:
|
|
project: ${{ vars.DEPOT_PROJECT_ID }}
|
|
context: .
|
|
file: ./docker/embedded/Dockerfile
|
|
push: true
|
|
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-${{ steps.commit-hash.outputs.app_short }}
|
|
build-args: |
|
|
VERSION_TAG=v2-alpha
|
|
BUILD_PORTAL=${{ env.BUILD_PORTAL }}
|
|
platforms: linux/amd64
|
|
|
|
- name: Build and push V2 image (Docker fork fallback)
|
|
if: env.USE_DEPOT != 'true' && 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: ${{ secrets.DOCKER_HUB_USERNAME }}/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 "${{ 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
|
|
- /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: "${{ secrets.TEST_LOGIN_USERNAME }}"
|
|
SECURITY_INITIALLOGIN_PASSWORD: "${{ secrets.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 ${{ 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,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
|
|
|
|
# ---- 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@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
|
|
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@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.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 }}
|
|
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}`;
|
|
|
|
// 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:
|
|
if: github.event.action == 'closed'
|
|
needs: pick
|
|
runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }}
|
|
permissions:
|
|
contents: read
|
|
issues: write
|
|
pull-requests: write
|
|
|
|
steps:
|
|
- name: Harden Runner
|
|
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
|
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@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.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
|
|
|
|
# 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
|
|
|
|
- name: Cleanup temporary files
|
|
if: always()
|
|
run: |
|
|
rm -f ../private.key
|
|
continue-on-error: true
|