mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
# Description of Changes ## Harden GitHub Actions secret handling Moves secrets behind deployment environments, removes the GitHub App token from workflows that only comment and label, and moves PR preview images to GHCR so the preview path needs no registry credential. Builds on #6005 by @dagecko — that commit is preserved with original authorship, rebased onto current main. ### Extract secrets from `run:` blocks (@dagecko, #6005 rebased) - Secrets referenced in shell bodies moved to step-level `env:` so values never reach a rendered command line - Two `workflow_dispatch` inputs moved out of shell interpolation (`multiOSReleases`, `push-docker-base`) - Dropped the hunks main has since solved — `setup-uv`, `reviewdog`, `build-push-action` and `github-script` are all pinned newer on main now - Fixed a bug in the original: `PR-Demo-cleanup.yml` uses a **quoted** `<< 'ENDSSH'` heredoc, so rewriting `${{ secrets.DOCKER_HUB_USERNAME }}` to `${DOCKER_HUB_USERNAME}` would have sent the literal string to the VPS and expanded to empty, silently orphaning preview images behind `|| true` ### Gate secret-bearing jobs behind environments - `environment:` added to 15 jobs across 10 workflows, mapping to `release-signing`, `docker-publish`, `package-publish`, `pr-preview` and `bot-identity` - Environment branch/tag policies are enforced by GitHub before the job starts, so editing the workflow file cannot bypass them - Four jobs deliberately **not** gated — `tauri-build`, `frontend-backend-licenses-update`, `swagger` and `push-docker-base` would fail their own triggers under the current policies and need restructuring first - Removed the `testMain` trigger from `push-docker` — the branch doesn't exist and isn't in the environment's policy ### Publish PR previews to GHCR instead of Docker Hub - Preview images now go to `ghcr.io/stirling-tools/stirling-pdf-test`, authenticated with `GITHUB_TOKEN` rather than `DOCKER_HUB_API` - Docker Hub personal access tokens cannot be scoped to a single repository, so the preview path was holding the same credential that publishes `s-pdf` and `stirling-pdf` - `DOCKER_HUB_API` no longer appears in any PR-reachable workflow - Login now precedes every `docker manifest inspect` — `deploy-on-v2-commit` had them reversed, which only worked because the Docker Hub repo was public ### Use `GITHUB_TOKEN` for comment and label workflows - Seven workflows no longer mint a GitHub App token; only `sync_files_v2`, `sync-portal-docs` and `frontend-backend-licenses-update` still do, so unattended auto-merge is unaffected - `permissions:` blocks derived per job from the API calls each actually makes — these were previously inert, since an App installation token ignores them, and one job had no block at all - Comment-threading matchers updated to `github-actions[bot]` so workflows still edit their own previous comment instead of posting duplicates - Removed the App token from the `refs/pull/N/merge` checkout in `PR-Demo-Comment-with-react` and set `persist-credentials: false` — it was written into `.git/config` of an untrusted tree that the same job then builds - Fixed a script injection in `check_toml.yml`: a fork-controlled branch name was interpolated into `actions/github-script` JS source, with validation running after the injected code had already executed. Values now come from `process.env` and are validated before use. --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have run `task check` to verify linters, typechecks, and tests pass - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details. --------- Co-authored-by: dagecko <cnyhuis@vigilantnow.com>
210 lines
8.0 KiB
YAML
210 lines
8.0 KiB
YAML
name: Auto V2 Deploy on Push
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- V2
|
|
- deploy-on-v2-commit
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
deploy-v2-on-push:
|
|
environment: pr-preview
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
concurrency:
|
|
group: deploy-v2-push-V2
|
|
cancel-in-progress: true
|
|
|
|
steps:
|
|
- name: Harden Runner
|
|
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
|
with:
|
|
egress-policy: audit
|
|
|
|
- name: Checkout code
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
|
|
|
- name: Get commit hashes for frontend and backend
|
|
id: commit-hashes
|
|
run: |
|
|
# Get last commit that touched the frontend folder, docker/frontend, or docker/compose
|
|
FRONTEND_HASH=$(git log -1 --format="%H" -- frontend/ docker/frontend/ docker/compose/ 2>/dev/null || echo "")
|
|
if [ -z "$FRONTEND_HASH" ]; then
|
|
FRONTEND_HASH="no-frontend-changes"
|
|
fi
|
|
|
|
# Get last commit that touched backend code, docker/backend, or docker/compose
|
|
BACKEND_HASH=$(git log -1 --format="%H" -- app/ docker/backend/ docker/compose/ 2>/dev/null || echo "")
|
|
if [ -z "$BACKEND_HASH" ]; then
|
|
BACKEND_HASH="no-backend-changes"
|
|
fi
|
|
|
|
echo "Frontend hash: $FRONTEND_HASH"
|
|
echo "Backend hash: $BACKEND_HASH"
|
|
|
|
echo "frontend_hash=$FRONTEND_HASH" >> $GITHUB_OUTPUT
|
|
echo "backend_hash=$BACKEND_HASH" >> $GITHUB_OUTPUT
|
|
|
|
# Short hashes for tags
|
|
if [ "$FRONTEND_HASH" = "no-frontend-changes" ]; then
|
|
echo "frontend_short=no-frontend" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "frontend_short=${FRONTEND_HASH:0:8}" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
if [ "$BACKEND_HASH" = "no-backend-changes" ]; then
|
|
echo "backend_short=no-backend" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "backend_short=${BACKEND_HASH:0:8}" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
- name: Convert repository owner to lowercase
|
|
id: repoowner
|
|
run: echo "lowercase=$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" >> $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: Check if frontend image exists
|
|
id: check-frontend
|
|
run: |
|
|
if docker manifest inspect ${IMAGE_BASE}:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }} >/dev/null 2>&1; then
|
|
echo "exists=true" >> $GITHUB_OUTPUT
|
|
echo "Frontend image already exists, skipping build"
|
|
else
|
|
echo "exists=false" >> $GITHUB_OUTPUT
|
|
echo "Frontend image needs to be built"
|
|
fi
|
|
|
|
env:
|
|
IMAGE_BASE: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test
|
|
- name: Check if backend image exists
|
|
id: check-backend
|
|
run: |
|
|
if docker manifest inspect ${IMAGE_BASE}:v2-backend-${{ steps.commit-hashes.outputs.backend_short }} >/dev/null 2>&1; then
|
|
echo "exists=true" >> $GITHUB_OUTPUT
|
|
echo "Backend image already exists, skipping build"
|
|
else
|
|
echo "exists=false" >> $GITHUB_OUTPUT
|
|
echo "Backend image needs to be built"
|
|
fi
|
|
|
|
env:
|
|
IMAGE_BASE: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test
|
|
|
|
- name: Build and push frontend image
|
|
if: steps.check-frontend.outputs.exists == 'false'
|
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
|
with:
|
|
context: .
|
|
file: ./docker/frontend/Dockerfile
|
|
push: true
|
|
cache-from: type=gha,scope=stirling-v2-frontend
|
|
cache-to: type=gha,mode=max,scope=stirling-v2-frontend
|
|
tags: |
|
|
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
|
|
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:v2-frontend-latest
|
|
build-args: VERSION_TAG=v2-alpha
|
|
platforms: linux/amd64
|
|
|
|
- name: Build and push backend image
|
|
if: steps.check-backend.outputs.exists == 'false'
|
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
|
with:
|
|
context: .
|
|
file: ./docker/backend/Dockerfile
|
|
push: true
|
|
cache-from: type=gha,scope=stirling-v2-backend
|
|
cache-to: type=gha,mode=max,scope=stirling-v2-backend
|
|
tags: |
|
|
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
|
|
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:v2-backend-latest
|
|
build-args: VERSION_TAG=v2-alpha
|
|
platforms: linux/amd64
|
|
|
|
- name: Set up SSH
|
|
run: |
|
|
mkdir -p ~/.ssh/
|
|
echo "${NEW_VPS_SSH_KEY}" > ../private.key
|
|
chmod 600 ../private.key
|
|
|
|
env:
|
|
NEW_VPS_SSH_KEY: ${{ secrets.NEW_VPS_SSH_KEY }}
|
|
- name: Deploy to VPS on port 3000
|
|
run: |
|
|
export UNIQUE_NAME=docker-compose-v2-$GITHUB_RUN_ID.yml
|
|
|
|
cat > $UNIQUE_NAME << EOF
|
|
version: '3.3'
|
|
services:
|
|
backend:
|
|
container_name: stirling-v2-backend
|
|
image: ${IMAGE_BASE}:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
|
|
ports:
|
|
- "13000:8080"
|
|
volumes:
|
|
- /stirling/V2/data:/usr/share/tessdata:rw
|
|
- /stirling/V2/config:/configs:rw
|
|
- /stirling/V2/logs:/logs:rw
|
|
environment:
|
|
DISABLE_ADDITIONAL_FEATURES: "true"
|
|
SECURITY_ENABLELOGIN: "false"
|
|
SYSTEM_DEFAULTLOCALE: en-US
|
|
UI_APPNAME: "Stirling-PDF V2"
|
|
UI_HOMEDESCRIPTION: "V2 Frontend/Backend Split"
|
|
UI_APPNAMENAVBAR: "V2 Deployment"
|
|
SYSTEM_MAXFILESIZE: "100"
|
|
METRICS_ENABLED: "true"
|
|
SYSTEM_GOOGLEVISIBILITY: "false"
|
|
SWAGGER_SERVER_URL: "https://demo.stirlingpdf.cloud"
|
|
baseUrl: "https://demo.stirlingpdf.cloud"
|
|
restart: on-failure:5
|
|
|
|
frontend:
|
|
container_name: stirling-v2-frontend
|
|
image: ${IMAGE_BASE}:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
|
|
ports:
|
|
- "3000:80"
|
|
environment:
|
|
VITE_API_BASE_URL: "http://${NEW_VPS_HOST}:13000"
|
|
depends_on:
|
|
- backend
|
|
restart: on-failure:5
|
|
EOF
|
|
|
|
# Copy to remote with unique name
|
|
scp -i ../private.key -o StrictHostKeyChecking=no $UNIQUE_NAME ${NEW_VPS_USERNAME}@${NEW_VPS_HOST}:/tmp/$UNIQUE_NAME
|
|
|
|
# SSH and rename/move atomically to avoid interference
|
|
ssh -i ../private.key -o StrictHostKeyChecking=no ${NEW_VPS_USERNAME}@${NEW_VPS_HOST} << ENDSSH
|
|
mkdir -p /stirling/V2/{data,config,logs}
|
|
mv /tmp/$UNIQUE_NAME /stirling/V2/docker-compose.yml
|
|
cd /stirling/V2
|
|
docker-compose down || true
|
|
docker-compose pull
|
|
docker-compose up -d
|
|
docker system prune -af --volumes || true
|
|
docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true
|
|
ENDSSH
|
|
|
|
env:
|
|
IMAGE_BASE: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test
|
|
NEW_VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
|
|
NEW_VPS_USERNAME: ${{ secrets.NEW_VPS_USERNAME }}
|
|
- name: Cleanup temporary files
|
|
if: always()
|
|
run: |
|
|
rm -f ../private.key
|