Continuous Integration / config (pull_request) Successful in 10s
Continuous Integration / changes (pull_request) Successful in 21s
Continuous Integration / backend-build (pull_request) Successful in 5m17s
Continuous Integration / vulnerability-scan (pull_request) Successful in 4m30s
Continuous Integration / frontend-prepare (pull_request) Successful in 1m32s
Continuous Integration / backend-test (pull_request) Successful in 5m31s
Continuous Integration / frontend-build (pull_request) Successful in 2m10s
Continuous Integration / frontend-test (pull_request) Successful in 4m26s
Continuous Integration / frontend-lint (pull_request) Successful in 2m12s
Continuous Integration / publish-test (pull_request) Canceled after 0s
Continuous Integration / publish-production (pull_request) Canceled after 0s
Continuous Integration / deploy-test (pull_request) Canceled after 0s
Continuous Integration / deploy-production (pull_request) Canceled after 0s
Push-to-master keeps auto-deploying to test unchanged. A new deploy_test input (default false) lets a manual workflow_dispatch run opt into a test deploy instead of it firing by default. A new changes job (dorny/paths-filter) gates the backend and frontend build/test/lint jobs on whether their side of the repo actually changed, shortening runs that only touch one side; publish jobs still run whenever either side changed, and manual runs always run everything since there's no commit to diff against. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VhySVFARKAP89WqQ3Kff8M
440 lines
20 KiB
YAML
440 lines
20 KiB
YAML
name: Continuous Integration
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
deploy_test:
|
|
description: 'Deploy to Test'
|
|
type: boolean
|
|
default: false
|
|
deploy_production:
|
|
description: 'Deploy to Production'
|
|
type: boolean
|
|
default: false
|
|
pull_request:
|
|
types: [opened, synchronize, reopened]
|
|
push:
|
|
branches: [master]
|
|
|
|
# Reusable settings for this workflow. Change these in one place if the .NET/Node/pnpm version,
|
|
# artifact names, or deploy destinations change. The env context is NOT available inside a called
|
|
# reusable workflow's `with:` inputs (see the `config` job below, which works around this by passing
|
|
# these values through as job outputs to the `deploy-test` / `deploy-production` jobs).
|
|
env:
|
|
DOTNET_VERSION: '10.0.x'
|
|
NODE_VERSION: '20'
|
|
PNPM_VERSION: '9'
|
|
PUBLISH_RID: linux-arm64
|
|
ARTIFACT_NAME_TEST: app-test
|
|
ARTIFACT_NAME_PRODUCTION: app-production
|
|
DEPLOY_ENVIRONMENT_TEST: test
|
|
DEPLOY_ENVIRONMENT_PRODUCTION: production
|
|
DEPLOY_PATH_TEST: ${{ vars.DEPLOY_PATH_TEST }}
|
|
DEPLOY_PATH_PRODUCTION: ${{ vars.DEPLOY_PATH_PRODUCTION }}
|
|
SERVICE_NAME_TEST: ${{ vars.SERVICE_NAME_TEST }}
|
|
SERVICE_NAME_PRODUCTION: ${{ vars.SERVICE_NAME_PRODUCTION }}
|
|
HEALTH_CHECK_URL_TEST: ${{ vars.HEALTH_CHECK_URL_TEST }}
|
|
HEALTH_CHECK_URL_PRODUCTION: ${{ vars.HEALTH_CHECK_URL_PRODUCTION }}
|
|
|
|
jobs:
|
|
# Passes the env: values above through as job outputs, since the env context is unavailable in
|
|
# the `with:` block of a job that calls a reusable workflow (see deploy-test / deploy-production).
|
|
config:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
artifact_name_test: ${{ steps.set.outputs.artifact_name_test }}
|
|
artifact_name_production: ${{ steps.set.outputs.artifact_name_production }}
|
|
deploy_environment_test: ${{ steps.set.outputs.deploy_environment_test }}
|
|
deploy_environment_production: ${{ steps.set.outputs.deploy_environment_production }}
|
|
deploy_path_test: ${{ steps.set.outputs.deploy_path_test }}
|
|
deploy_path_production: ${{ steps.set.outputs.deploy_path_production }}
|
|
service_name_test: ${{ steps.set.outputs.service_name_test }}
|
|
service_name_production: ${{ steps.set.outputs.service_name_production }}
|
|
health_check_url_test: ${{ steps.set.outputs.health_check_url_test }}
|
|
health_check_url_production: ${{ steps.set.outputs.health_check_url_production }}
|
|
steps:
|
|
- id: set
|
|
run: |
|
|
echo "artifact_name_test=${{ env.ARTIFACT_NAME_TEST }}" >> "$GITHUB_OUTPUT"
|
|
echo "artifact_name_production=${{ env.ARTIFACT_NAME_PRODUCTION }}" >> "$GITHUB_OUTPUT"
|
|
echo "deploy_environment_test=${{ env.DEPLOY_ENVIRONMENT_TEST }}" >> "$GITHUB_OUTPUT"
|
|
echo "deploy_environment_production=${{ env.DEPLOY_ENVIRONMENT_PRODUCTION }}" >> "$GITHUB_OUTPUT"
|
|
echo "deploy_path_test=${{ env.DEPLOY_PATH_TEST }}" >> "$GITHUB_OUTPUT"
|
|
echo "deploy_path_production=${{ env.DEPLOY_PATH_PRODUCTION }}" >> "$GITHUB_OUTPUT"
|
|
echo "service_name_test=${{ env.SERVICE_NAME_TEST }}" >> "$GITHUB_OUTPUT"
|
|
echo "service_name_production=${{ env.SERVICE_NAME_PRODUCTION }}" >> "$GITHUB_OUTPUT"
|
|
echo "health_check_url_test=${{ env.HEALTH_CHECK_URL_TEST }}" >> "$GITHUB_OUTPUT"
|
|
echo "health_check_url_production=${{ env.HEALTH_CHECK_URL_PRODUCTION }}" >> "$GITHUB_OUTPUT"
|
|
|
|
# Path-based skip: keeps the pipeline short when a change only touches one side of the repo.
|
|
# fetch-depth: 0 gives paths-filter full local git history to diff against, rather than relying
|
|
# on its GitHub-API fallback for missing history, which targets GitHub's REST API and cannot be
|
|
# assumed to work against a Gitea remote. Changes under the workflow files themselves count as
|
|
# both backend and frontend, so a workflow edit is never left partially untested. A manual
|
|
# workflow_dispatch run has no `before` commit to diff against and is always an explicit request
|
|
# to run everything, so it force-reports both sides as changed rather than trusting the filter.
|
|
changes:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
backend: ${{ github.event_name == 'workflow_dispatch' || steps.filter.outputs.backend == 'true' }}
|
|
frontend: ${{ github.event_name == 'workflow_dispatch' || steps.filter.outputs.frontend == 'true' }}
|
|
steps:
|
|
# Skipped on workflow_dispatch: there's no `before` commit to diff against, and a manual run
|
|
# always force-reports both sides changed anyway (see the outputs above), so the diff would be
|
|
# wasted work.
|
|
- uses: actions/checkout@v4
|
|
if: github.event_name != 'workflow_dispatch'
|
|
with:
|
|
fetch-depth: 0
|
|
- uses: dorny/paths-filter@v3
|
|
if: github.event_name != 'workflow_dispatch'
|
|
id: filter
|
|
with:
|
|
filters: |
|
|
workflow: &workflow
|
|
- '.gitea/workflows/**'
|
|
backend:
|
|
- *workflow
|
|
- 'src/**'
|
|
- 'tests/**'
|
|
- '*.sln'
|
|
- '**/*.csproj'
|
|
frontend:
|
|
- *workflow
|
|
- 'frontend/**'
|
|
|
|
# --- Gate 1: backend build ---
|
|
backend-build:
|
|
needs: changes
|
|
if: needs.changes.outputs.backend == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-dotnet@v4
|
|
with:
|
|
dotnet-version: ${{ env.DOTNET_VERSION }}
|
|
- name: Restore and build (Release)
|
|
run: dotnet build SlpModularCms.sln -c Release
|
|
|
|
# --- Gate 2: backend tests ---
|
|
backend-test:
|
|
needs: [changes, backend-build]
|
|
if: needs.changes.outputs.backend == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-dotnet@v4
|
|
with:
|
|
dotnet-version: ${{ env.DOTNET_VERSION }}
|
|
- name: Test (Release)
|
|
run: dotnet test SlpModularCms.sln -c Release
|
|
|
|
# --- Gate 3: vulnerability scan ---
|
|
# `dotnet list package --vulnerable` always exits 0, even when it reports vulnerabilities, so the
|
|
# step greps its own output and fails deliberately (FR-22, D-12, OPEN-03 — closed by pinning
|
|
# Microsoft.OpenApi and System.Security.Cryptography.Xml in the relevant .csproj files).
|
|
vulnerability-scan:
|
|
needs: changes
|
|
if: needs.changes.outputs.backend == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-dotnet@v4
|
|
with:
|
|
dotnet-version: ${{ env.DOTNET_VERSION }}
|
|
- name: Check for vulnerable packages
|
|
run: |
|
|
OUTPUT=$(dotnet list SlpModularCms.sln package --vulnerable --include-transitive 2>&1)
|
|
echo "$OUTPUT"
|
|
if echo "$OUTPUT" | grep -q "has the following vulnerable packages"; then
|
|
echo "Vulnerable packages detected — see above."
|
|
exit 1
|
|
fi
|
|
|
|
frontend-prepare:
|
|
needs: changes
|
|
if: needs.changes.outputs.frontend == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v3
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
- uses: pnpm/action-setup@v4
|
|
with:
|
|
version: ${{ env.PNPM_VERSION }}
|
|
- name: Get pnpm store directory
|
|
id: pnpm-store
|
|
working-directory: frontend
|
|
run: echo "path=$(pnpm store path)" >> "$GITHUB_OUTPUT"
|
|
- name: Cache pnpm store
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ${{ steps.pnpm-store.outputs.path }}
|
|
key: pnpm-${{ hashFiles('frontend/pnpm-lock.yaml') }}
|
|
- name: Install dependencies
|
|
working-directory: frontend
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
# --- Gate 4: frontend build ---
|
|
# Validates the frontend compiles on its own, independent of any environment. The actual
|
|
# per-environment deployable artifact is produced later by each publish job's own "Build admin
|
|
# frontend" step (publish-test / publish-production, below), which builds frontend/ again with
|
|
# that environment's Vite variables set and copies the result into wwwroot/admin before
|
|
# `dotnet publish` runs — see that step's comment for why this isn't an MSBuild target instead.
|
|
frontend-build:
|
|
needs: [changes, frontend-prepare]
|
|
if: needs.changes.outputs.frontend == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v3
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
- uses: pnpm/action-setup@v4
|
|
with:
|
|
version: ${{ env.PNPM_VERSION }}
|
|
- name: Get pnpm store directory
|
|
id: pnpm-store
|
|
working-directory: frontend
|
|
run: echo "path=$(pnpm store path)" >> "$GITHUB_OUTPUT"
|
|
- name: Restore pnpm store
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ${{ steps.pnpm-store.outputs.path }}
|
|
key: pnpm-${{ hashFiles('frontend/pnpm-lock.yaml') }}
|
|
- name: Install dependencies
|
|
working-directory: frontend
|
|
run: pnpm install --frozen-lockfile
|
|
- name: Build
|
|
working-directory: frontend
|
|
run: pnpm run build
|
|
|
|
# --- Gate 5: frontend tests ---
|
|
frontend-test:
|
|
needs: [changes, frontend-prepare]
|
|
if: needs.changes.outputs.frontend == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v3
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
- uses: pnpm/action-setup@v4
|
|
with:
|
|
version: ${{ env.PNPM_VERSION }}
|
|
- name: Get pnpm store directory
|
|
id: pnpm-store
|
|
working-directory: frontend
|
|
run: echo "path=$(pnpm store path)" >> "$GITHUB_OUTPUT"
|
|
- name: Restore pnpm store
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ${{ steps.pnpm-store.outputs.path }}
|
|
key: pnpm-${{ hashFiles('frontend/pnpm-lock.yaml') }}
|
|
- name: Install dependencies
|
|
working-directory: frontend
|
|
run: pnpm install --frozen-lockfile
|
|
- name: Unit tests
|
|
working-directory: frontend
|
|
run: pnpm run test
|
|
|
|
# --- Gate 6: frontend lint / format-check ---
|
|
frontend-lint:
|
|
needs: [changes, frontend-prepare]
|
|
if: needs.changes.outputs.frontend == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v3
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
- uses: pnpm/action-setup@v4
|
|
with:
|
|
version: ${{ env.PNPM_VERSION }}
|
|
- name: Get pnpm store directory
|
|
id: pnpm-store
|
|
working-directory: frontend
|
|
run: echo "path=$(pnpm store path)" >> "$GITHUB_OUTPUT"
|
|
- name: Restore pnpm store
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ${{ steps.pnpm-store.outputs.path }}
|
|
key: pnpm-${{ hashFiles('frontend/pnpm-lock.yaml') }}
|
|
- name: Install dependencies
|
|
working-directory: frontend
|
|
run: pnpm install --frozen-lockfile
|
|
- name: Lint
|
|
working-directory: frontend
|
|
run: pnpm run lint
|
|
|
|
# Publishes the Api project for the test environment. The admin SPA is built by this job's own
|
|
# "Build admin frontend" step below, ahead of `dotnet publish` — see that step's comment for why.
|
|
#
|
|
# REF-U5-01: the Umami-origin drift check this gate performs exists because U3's own startup check
|
|
# (BR-U3-22) can't work — the backend can never see VITE_UMAMI_WEBSITE_ID at runtime. It compares
|
|
# two CI-time values instead: the frontend's Umami script origin, and a Gitea variable that MUST be
|
|
# kept in sync with the host's real SecurityHeaders__AllowedScriptOrigins__0 env var (D-16). This
|
|
# gate cannot see the actual runtime CSP configuration; it only catches drift between the frontend
|
|
# build and what this variable claims the CSP allows.
|
|
publish-test:
|
|
needs: [changes, backend-build, backend-test, vulnerability-scan, frontend-build, frontend-test, frontend-lint]
|
|
# always() bypasses the automatic skip-cascade from a gate job that was itself skipped (because
|
|
# its side of the repo didn't change) — !failure() && !cancelled() still blocks a run where a
|
|
# gate that DID run actually failed. The remaining changes.outputs check makes sure there's
|
|
# something to publish at all: a run that touched neither backend nor frontend has nothing new
|
|
# to deploy.
|
|
if: |
|
|
always() && !failure() && !cancelled() &&
|
|
(needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true')
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-dotnet@v4
|
|
with:
|
|
dotnet-version: ${{ env.DOTNET_VERSION }}
|
|
- uses: actions/setup-node@v3
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
- uses: pnpm/action-setup@v4
|
|
with:
|
|
version: ${{ env.PNPM_VERSION }}
|
|
|
|
- name: Verify Umami origin is permitted (REF-U5-01)
|
|
if: ${{ vars.VITE_UMAMI_SCRIPT_URL != '' }}
|
|
run: |
|
|
ORIGIN=$(echo "${{ vars.VITE_UMAMI_SCRIPT_URL }}" | sed -E 's#^(https?://[^/]+).*#\1#')
|
|
if ! echo ",${{ vars.SECURITY_ALLOWED_SCRIPT_ORIGINS_TEST }}," | grep -qF ",$ORIGIN,"; then
|
|
echo "Umami origin '$ORIGIN' is not present in the SECURITY_ALLOWED_SCRIPT_ORIGINS_TEST Gitea variable."
|
|
echo "Fix the variable, or the test environment's SecurityHeaders CSP config it must mirror (D-16)."
|
|
exit 1
|
|
fi
|
|
|
|
# Builds the admin SPA and lands it in wwwroot/admin *before* dotnet publish runs, as its own
|
|
# step rather than an MSBuild target hooked to Build/Publish. That was tried and reliably
|
|
# failed two different ways (see SlpModularCms.Api.csproj's comment on the topic): the SDK's
|
|
# wwwroot static-web-asset item set is fixed at project evaluation time, before any target
|
|
# runs, so files a target creates afterward never make it into the publish output; and forcing
|
|
# them in as an explicit Content item collided with the SDK's own static-web-asset resolution.
|
|
# Physically existing on disk before dotnet publish/build ever runs is the only thing that
|
|
# worked, verified locally against a clean obj/bin and a fresh node_modules.
|
|
- name: Build admin frontend
|
|
working-directory: frontend
|
|
env:
|
|
VITE_APP_ENV: test
|
|
VITE_UMAMI_SCRIPT_URL: ${{ vars.VITE_UMAMI_SCRIPT_URL }}
|
|
VITE_UMAMI_WEBSITE_ID: ${{ vars.VITE_UMAMI_WEBSITE_ID_TEST }}
|
|
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
|
|
run: |
|
|
pnpm install --frozen-lockfile
|
|
pnpm build
|
|
mkdir -p ../src/SlpModularCms.Api/wwwroot/admin
|
|
cp -r dist/. ../src/SlpModularCms.Api/wwwroot/admin/
|
|
|
|
- name: Publish (test)
|
|
working-directory: src/SlpModularCms.Api
|
|
run: >
|
|
dotnet publish -c Release -r ${{ env.PUBLISH_RID }} --self-contained false
|
|
-o ${{ github.workspace }}/${{ env.ARTIFACT_NAME_TEST }}
|
|
|
|
- name: Upload publish artifact
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: ${{ env.ARTIFACT_NAME_TEST }}
|
|
path: ${{ env.ARTIFACT_NAME_TEST }}
|
|
retention-days: 1
|
|
|
|
# Same as publish-test, but only for a manual workflow_dispatch run with deploy_production set —
|
|
# a distinct job rather than a parameterized reuse of publish-test, because VITE_APP_ENV is a
|
|
# Vite build-time value: one dist/ bundle cannot be tagged as both 'test' and 'production' (FR-05).
|
|
publish-production:
|
|
needs: [changes, backend-build, backend-test, vulnerability-scan, frontend-build, frontend-test, frontend-lint]
|
|
# See publish-test's comment on the always()/!failure()/!cancelled() combination above.
|
|
if: |
|
|
always() && !failure() && !cancelled() &&
|
|
github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_production == 'true' &&
|
|
(needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true')
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-dotnet@v4
|
|
with:
|
|
dotnet-version: ${{ env.DOTNET_VERSION }}
|
|
- uses: actions/setup-node@v3
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
- uses: pnpm/action-setup@v4
|
|
with:
|
|
version: ${{ env.PNPM_VERSION }}
|
|
|
|
- name: Verify Umami origin is permitted (REF-U5-01)
|
|
if: ${{ vars.VITE_UMAMI_SCRIPT_URL != '' }}
|
|
run: |
|
|
ORIGIN=$(echo "${{ vars.VITE_UMAMI_SCRIPT_URL }}" | sed -E 's#^(https?://[^/]+).*#\1#')
|
|
if ! echo ",${{ vars.SECURITY_ALLOWED_SCRIPT_ORIGINS_PRODUCTION }}," | grep -qF ",$ORIGIN,"; then
|
|
echo "Umami origin '$ORIGIN' is not present in the SECURITY_ALLOWED_SCRIPT_ORIGINS_PRODUCTION Gitea variable."
|
|
echo "Fix the variable, or the production environment's SecurityHeaders CSP config it must mirror (D-16)."
|
|
exit 1
|
|
fi
|
|
|
|
# See publish-test's "Build admin frontend" step for why this runs as its own step ahead of
|
|
# dotnet publish rather than an MSBuild target.
|
|
- name: Build admin frontend
|
|
working-directory: frontend
|
|
env:
|
|
VITE_APP_ENV: production
|
|
VITE_UMAMI_SCRIPT_URL: ${{ vars.VITE_UMAMI_SCRIPT_URL }}
|
|
VITE_UMAMI_WEBSITE_ID: ${{ vars.VITE_UMAMI_WEBSITE_ID_PRODUCTION }}
|
|
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
|
|
run: |
|
|
pnpm install --frozen-lockfile
|
|
pnpm build
|
|
mkdir -p ../src/SlpModularCms.Api/wwwroot/admin
|
|
cp -r dist/. ../src/SlpModularCms.Api/wwwroot/admin/
|
|
|
|
- name: Publish (production)
|
|
working-directory: src/SlpModularCms.Api
|
|
run: >
|
|
dotnet publish -c Release -r ${{ env.PUBLISH_RID }} --self-contained false
|
|
-o ${{ github.workspace }}/${{ env.ARTIFACT_NAME_PRODUCTION }}
|
|
|
|
- name: Upload publish artifact
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: ${{ env.ARTIFACT_NAME_PRODUCTION }}
|
|
path: ${{ env.ARTIFACT_NAME_PRODUCTION }}
|
|
retention-days: 1
|
|
|
|
# Automatic test deploy on every push to master (unchanged — a merge should reach test without
|
|
# extra steps). A manual workflow_dispatch run only deploys to test when deploy_test is checked,
|
|
# since a dispatch run is often just re-running CI/production, not a new test build (FR-03, D-01,
|
|
# D-09).
|
|
deploy-test:
|
|
needs: [publish-test, config]
|
|
if: (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_test == 'true') || (github.event_name == 'push' && github.ref == 'refs/heads/master')
|
|
uses: ./.gitea/workflows/deploy-scp.yaml
|
|
secrets: inherit
|
|
with:
|
|
artifact_name: ${{ needs.config.outputs.artifact_name_test }}
|
|
environment: ${{ needs.config.outputs.deploy_environment_test }}
|
|
deploy_path: ${{ needs.config.outputs.deploy_path_test }}
|
|
service_name: ${{ needs.config.outputs.service_name_test }}
|
|
health_check_url: ${{ needs.config.outputs.health_check_url_test }}
|
|
run_db_backup: false
|
|
|
|
# Production deploy ONLY on an explicit workflow_dispatch run with deploy_production checked
|
|
# (FR-04, D-09) — never reachable from a plain push to master, so pushing cannot deploy production
|
|
# under any circumstance.
|
|
deploy-production:
|
|
needs: [publish-production, config]
|
|
if: github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_production == 'true'
|
|
uses: ./.gitea/workflows/deploy-scp.yaml
|
|
secrets: inherit
|
|
with:
|
|
artifact_name: ${{ needs.config.outputs.artifact_name_production }}
|
|
environment: ${{ needs.config.outputs.deploy_environment_production }}
|
|
deploy_path: ${{ needs.config.outputs.deploy_path_production }}
|
|
service_name: ${{ needs.config.outputs.service_name_production }}
|
|
health_check_url: ${{ needs.config.outputs.health_check_url_production }}
|
|
run_db_backup: true
|