mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 22:04:10 +00:00
fix: optimize GitHub Actions workflows to reduce costs
- Remove CodeQL Analysis (requires paid GHAS for private repos) - Replace with ESLint + pnpm audit for security scanning - CI: Run full tests only on PRs, not on every push to develop - CI: Skip CI for markdown-only changes - Security: Run only on PRs to main and weekly schedule - Add deploy-production.yml workflow with rollback support - Add deploy-production.sh script for manual deployments - Document GitHub Actions cost optimization in DEPLOYMENT_STRATEGY.md Estimated savings: ~68% of GitHub Actions minutes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
29057577d3
commit
c505f29ebf
5 changed files with 1358 additions and 63 deletions
50
.github/workflows/ci.yml
vendored
50
.github/workflows/ci.yml
vendored
|
|
@ -1,8 +1,19 @@
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Optimiert für minimale GitHub Actions Nutzung:
|
||||||
|
# - Push auf develop: Nur Lint + Build (schnelle Feedback-Schleife)
|
||||||
|
# - PRs: Volle Test-Suite inkl. E2E
|
||||||
|
# - Push auf main: Keine CI (wird durch deploy-production getriggert)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main, develop]
|
branches: [develop]
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md'
|
||||||
|
- 'docs/**'
|
||||||
|
- '.github/ISSUE_TEMPLATE/**'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main, develop]
|
branches: [main, develop]
|
||||||
|
|
||||||
|
|
@ -13,6 +24,9 @@ concurrency:
|
||||||
env:
|
env:
|
||||||
NODE_VERSION: '20'
|
NODE_VERSION: '20'
|
||||||
PNPM_VERSION: '9'
|
PNPM_VERSION: '9'
|
||||||
|
# Schnelle Builds durch Turbo Cache
|
||||||
|
TURBO_TEAM: 'payload'
|
||||||
|
TURBO_REMOTE_ONLY: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
|
|
@ -76,12 +90,13 @@ jobs:
|
||||||
run: pnpm typecheck
|
run: pnpm typecheck
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# Unit & Integration Tests
|
# Unit & Integration Tests (nur bei PRs - spart Actions-Minuten)
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
test:
|
test:
|
||||||
name: Tests
|
name: Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [lint, typecheck]
|
needs: [lint, typecheck]
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
timeout-minutes: 30 # Prevent 6-hour hangs
|
timeout-minutes: 30 # Prevent 6-hour hangs
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
|
|
@ -224,12 +239,13 @@ jobs:
|
||||||
include-hidden-files: true
|
include-hidden-files: true
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# E2E Tests (after build)
|
# E2E Tests (nur bei PRs - sehr ressourcenintensiv)
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
e2e:
|
e2e:
|
||||||
name: E2E Tests
|
name: E2E Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build]
|
needs: [build]
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
timeout-minutes: 30 # Prevent 6-hour hangs
|
timeout-minutes: 30 # Prevent 6-hour hangs
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
|
|
@ -325,14 +341,22 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Check required jobs
|
- name: Check required jobs
|
||||||
run: |
|
run: |
|
||||||
# Required jobs (must succeed)
|
# Required jobs for all runs (must succeed)
|
||||||
if [ "${{ needs.lint.result }}" != "success" ] || \
|
if [ "${{ needs.lint.result }}" != "success" ] || \
|
||||||
[ "${{ needs.test.result }}" != "success" ] || \
|
[ "${{ needs.build.result }}" != "success" ]; then
|
||||||
[ "${{ needs.build.result }}" != "success" ] || \
|
|
||||||
[ "${{ needs.e2e.result }}" != "success" ]; then
|
|
||||||
echo "One or more required jobs failed"
|
echo "One or more required jobs failed"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Jobs only required for PRs
|
||||||
|
if [ "${{ github.event_name }}" == "pull_request" ]; then
|
||||||
|
if [ "${{ needs.test.result }}" != "success" ] || \
|
||||||
|
[ "${{ needs.e2e.result }}" != "success" ]; then
|
||||||
|
echo "PR required jobs failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Optional jobs (typecheck) - just report status
|
# Optional jobs (typecheck) - just report status
|
||||||
if [ "${{ needs.typecheck.result }}" != "success" ]; then
|
if [ "${{ needs.typecheck.result }}" != "success" ]; then
|
||||||
echo "⚠️ TypeScript check failed (optional)"
|
echo "⚠️ TypeScript check failed (optional)"
|
||||||
|
|
@ -343,10 +367,18 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
echo "## CI Summary" >> $GITHUB_STEP_SUMMARY
|
echo "## CI Summary" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Trigger:** ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Job | Status | Required |" >> $GITHUB_STEP_SUMMARY
|
echo "| Job | Status | Required |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "|-----|--------|----------|" >> $GITHUB_STEP_SUMMARY
|
echo "|-----|--------|----------|" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Lint | ${{ needs.lint.result }} | ✅ |" >> $GITHUB_STEP_SUMMARY
|
echo "| Lint | ${{ needs.lint.result }} | ✅ |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| TypeScript | ${{ needs.typecheck.result }} | ⚠️ optional |" >> $GITHUB_STEP_SUMMARY
|
echo "| TypeScript | ${{ needs.typecheck.result }} | ⚠️ optional |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Tests | ${{ needs.test.result }} | ✅ |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Build | ${{ needs.build.result }} | ✅ |" >> $GITHUB_STEP_SUMMARY
|
echo "| Build | ${{ needs.build.result }} | ✅ |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| E2E | ${{ needs.e2e.result }} | ✅ |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
if [ "${{ github.event_name }}" == "pull_request" ]; then
|
||||||
|
echo "| Tests | ${{ needs.test.result }} | ✅ (PR) |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| E2E | ${{ needs.e2e.result }} | ✅ (PR) |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "| Tests | skipped | ⏭️ push only |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| E2E | skipped | ⏭️ push only |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
|
||||||
441
.github/workflows/deploy-production.yml
vendored
Normal file
441
.github/workflows/deploy-production.yml
vendored
Normal file
|
|
@ -0,0 +1,441 @@
|
||||||
|
name: Deploy to Production
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Manual trigger with approval - required for production
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
skip_tests:
|
||||||
|
description: 'Skip pre-deployment tests'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
type: boolean
|
||||||
|
skip_backup:
|
||||||
|
description: 'Skip database backup (not recommended)'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
type: boolean
|
||||||
|
skip_migrations:
|
||||||
|
description: 'Skip database migrations'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
type: boolean
|
||||||
|
deploy_tag:
|
||||||
|
description: 'Git tag or commit to deploy (leave empty for latest main)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: production-deployment
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
env:
|
||||||
|
NODE_VERSION: '22'
|
||||||
|
PNPM_VERSION: '9'
|
||||||
|
PRODUCTION_HOST: '162.55.85.18'
|
||||||
|
PRODUCTION_USER: 'payload'
|
||||||
|
PRODUCTION_PATH: '/home/payload/payload-cms'
|
||||||
|
PRODUCTION_URL: 'https://cms.c2sgmbh.de'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# ===========================================================================
|
||||||
|
# Pre-flight Checks
|
||||||
|
# ===========================================================================
|
||||||
|
preflight:
|
||||||
|
name: Pre-flight Checks
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
deploy_sha: ${{ steps.get-sha.outputs.sha }}
|
||||||
|
deploy_tag: ${{ steps.get-sha.outputs.tag }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.deploy_tag || 'main' }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Get deployment SHA
|
||||||
|
id: get-sha
|
||||||
|
run: |
|
||||||
|
SHA=$(git rev-parse HEAD)
|
||||||
|
TAG="${{ inputs.deploy_tag }}"
|
||||||
|
if [ -z "$TAG" ]; then
|
||||||
|
TAG="main ($(git rev-parse --short HEAD))"
|
||||||
|
fi
|
||||||
|
echo "sha=$SHA" >> $GITHUB_OUTPUT
|
||||||
|
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||||
|
echo "Deploying: $TAG"
|
||||||
|
echo "SHA: $SHA"
|
||||||
|
|
||||||
|
- name: Verify branch is up to date with develop
|
||||||
|
run: |
|
||||||
|
git fetch origin develop
|
||||||
|
MAIN_SHA=$(git rev-parse HEAD)
|
||||||
|
DEVELOP_SHA=$(git rev-parse origin/develop)
|
||||||
|
|
||||||
|
# Check if main contains the latest develop commits
|
||||||
|
if ! git merge-base --is-ancestor origin/develop HEAD 2>/dev/null; then
|
||||||
|
echo "::warning::main is not up to date with develop. Consider merging develop first."
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check for pending migrations
|
||||||
|
run: |
|
||||||
|
# List migration files that might need to be run
|
||||||
|
if [ -d "src/migrations" ]; then
|
||||||
|
MIGRATION_COUNT=$(find src/migrations -name "*.ts" -type f | wc -l)
|
||||||
|
echo "Found $MIGRATION_COUNT migration files"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# Pre-deployment Tests (optional, recommended)
|
||||||
|
# ===========================================================================
|
||||||
|
pre-tests:
|
||||||
|
name: Pre-deployment Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [preflight]
|
||||||
|
if: ${{ inputs.skip_tests != true }}
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:17
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: payload
|
||||||
|
POSTGRES_PASSWORD: payload_test_password
|
||||||
|
POSTGRES_DB: payload_test
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.preflight.outputs.deploy_sha }}
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v3
|
||||||
|
with:
|
||||||
|
version: ${{ env.PNPM_VERSION }}
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Run ESLint
|
||||||
|
run: pnpm lint
|
||||||
|
|
||||||
|
- name: Run Unit Tests
|
||||||
|
run: pnpm test:unit
|
||||||
|
timeout-minutes: 10
|
||||||
|
env:
|
||||||
|
CSRF_SECRET: test-csrf-secret
|
||||||
|
PAYLOAD_SECRET: test-payload-secret
|
||||||
|
PAYLOAD_PUBLIC_SERVER_URL: https://test.example.com
|
||||||
|
NEXT_PUBLIC_SERVER_URL: https://test.example.com
|
||||||
|
EMAIL_DELIVERY_DISABLED: 'true'
|
||||||
|
DATABASE_URI: postgresql://payload:payload_test_password@localhost:5432/payload_test
|
||||||
|
|
||||||
|
- name: Build Test
|
||||||
|
run: pnpm build
|
||||||
|
env:
|
||||||
|
PAYLOAD_SECRET: build-secret-placeholder
|
||||||
|
DATABASE_URI: postgresql://placeholder:placeholder@localhost:5432/placeholder
|
||||||
|
NEXT_PUBLIC_SERVER_URL: https://build.example.com
|
||||||
|
PAYLOAD_PUBLIC_SERVER_URL: https://build.example.com
|
||||||
|
CONSENT_LOGGING_API_KEY: ci-consent-api-key-placeholder
|
||||||
|
IP_ANONYMIZATION_PEPPER: ci-anonymization-pepper-placeholder
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# Database Backup (before deployment)
|
||||||
|
# ===========================================================================
|
||||||
|
backup:
|
||||||
|
name: Pre-deployment Backup
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [preflight, pre-tests]
|
||||||
|
if: always() && (needs.pre-tests.result == 'success' || needs.pre-tests.result == 'skipped') && inputs.skip_backup != true
|
||||||
|
steps:
|
||||||
|
- name: Configure SSH
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "${{ secrets.PRODUCTION_SSH_KEY }}" > ~/.ssh/production_key
|
||||||
|
chmod 600 ~/.ssh/production_key
|
||||||
|
cat >> ~/.ssh/config << EOF
|
||||||
|
Host production
|
||||||
|
HostName ${{ env.PRODUCTION_HOST }}
|
||||||
|
User ${{ env.PRODUCTION_USER }}
|
||||||
|
IdentityFile ~/.ssh/production_key
|
||||||
|
StrictHostKeyChecking no
|
||||||
|
UserKnownHostsFile /dev/null
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Create database backup
|
||||||
|
run: |
|
||||||
|
ssh production << 'ENDSSH'
|
||||||
|
set -e
|
||||||
|
echo "=== Creating pre-deployment backup ==="
|
||||||
|
|
||||||
|
BACKUP_DIR="$HOME/backups/pre-deploy"
|
||||||
|
BACKUP_FILE="$BACKUP_DIR/payload_db_$(date +%Y-%m-%d_%H-%M-%S).sql.gz"
|
||||||
|
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
# Create backup using pg_dump
|
||||||
|
PGPASSWORD=$(grep 'payload_db' ~/.pgpass | cut -d: -f5) \
|
||||||
|
pg_dump -h localhost -U payload payload_db | gzip > "$BACKUP_FILE"
|
||||||
|
|
||||||
|
# Keep only last 5 pre-deploy backups
|
||||||
|
ls -t "$BACKUP_DIR"/payload_db_*.sql.gz 2>/dev/null | tail -n +6 | xargs rm -f 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "Backup created: $BACKUP_FILE"
|
||||||
|
ls -lh "$BACKUP_FILE"
|
||||||
|
ENDSSH
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# Deploy to Production
|
||||||
|
# ===========================================================================
|
||||||
|
deploy:
|
||||||
|
name: Deploy to Production
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [preflight, pre-tests, backup]
|
||||||
|
if: always() && (needs.pre-tests.result == 'success' || needs.pre-tests.result == 'skipped') && (needs.backup.result == 'success' || needs.backup.result == 'skipped')
|
||||||
|
environment:
|
||||||
|
name: production
|
||||||
|
url: ${{ env.PRODUCTION_URL }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.preflight.outputs.deploy_sha }}
|
||||||
|
|
||||||
|
- name: Configure SSH
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "${{ secrets.PRODUCTION_SSH_KEY }}" > ~/.ssh/production_key
|
||||||
|
chmod 600 ~/.ssh/production_key
|
||||||
|
cat >> ~/.ssh/config << EOF
|
||||||
|
Host production
|
||||||
|
HostName ${{ env.PRODUCTION_HOST }}
|
||||||
|
User ${{ env.PRODUCTION_USER }}
|
||||||
|
IdentityFile ~/.ssh/production_key
|
||||||
|
StrictHostKeyChecking no
|
||||||
|
UserKnownHostsFile /dev/null
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Deploy to Production
|
||||||
|
id: deploy
|
||||||
|
run: |
|
||||||
|
DEPLOY_SHA="${{ needs.preflight.outputs.deploy_sha }}"
|
||||||
|
SKIP_MIGRATIONS="${{ inputs.skip_migrations }}"
|
||||||
|
|
||||||
|
ssh production << ENDSSH
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=============================================="
|
||||||
|
echo " PRODUCTION DEPLOYMENT"
|
||||||
|
echo "=============================================="
|
||||||
|
echo "Time: \$(date)"
|
||||||
|
echo "SHA: $DEPLOY_SHA"
|
||||||
|
echo "Skip Migrations: $SKIP_MIGRATIONS"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
cd ${{ env.PRODUCTION_PATH }}
|
||||||
|
|
||||||
|
# Save current commit for rollback
|
||||||
|
PREVIOUS_SHA=\$(git rev-parse HEAD)
|
||||||
|
echo "\$PREVIOUS_SHA" > /tmp/previous_deploy_sha
|
||||||
|
echo "Previous commit: \$PREVIOUS_SHA"
|
||||||
|
|
||||||
|
# Stash any local changes
|
||||||
|
git stash --include-untracked 2>/dev/null || true
|
||||||
|
|
||||||
|
# Fetch and checkout
|
||||||
|
echo ""
|
||||||
|
echo "=== Fetching code ==="
|
||||||
|
git fetch origin main
|
||||||
|
git checkout main
|
||||||
|
git reset --hard $DEPLOY_SHA
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
echo ""
|
||||||
|
echo "=== Installing dependencies ==="
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# Run migrations (unless skipped)
|
||||||
|
if [ "$SKIP_MIGRATIONS" != "true" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "=== Running migrations ==="
|
||||||
|
pnpm payload migrate || echo "No migrations to run"
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "=== Skipping migrations (as requested) ==="
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stop services before build to free memory
|
||||||
|
echo ""
|
||||||
|
echo "=== Stopping services for build ==="
|
||||||
|
pm2 stop payload 2>/dev/null || true
|
||||||
|
pm2 stop queue-worker 2>/dev/null || true
|
||||||
|
|
||||||
|
# Build
|
||||||
|
echo ""
|
||||||
|
echo "=== Building application ==="
|
||||||
|
NODE_OPTIONS="--max-old-space-size=2048" pnpm build
|
||||||
|
|
||||||
|
# Restart services
|
||||||
|
echo ""
|
||||||
|
echo "=== Starting services ==="
|
||||||
|
pm2 restart payload --update-env 2>/dev/null || pm2 start ecosystem.config.cjs --only payload
|
||||||
|
pm2 restart queue-worker --update-env 2>/dev/null || pm2 start ecosystem.config.cjs --only queue-worker
|
||||||
|
|
||||||
|
# Wait for startup
|
||||||
|
echo ""
|
||||||
|
echo "=== Waiting for services to start ==="
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
# Show status
|
||||||
|
pm2 status
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Deployment Complete ==="
|
||||||
|
echo "Time: \$(date)"
|
||||||
|
ENDSSH
|
||||||
|
|
||||||
|
- name: Health Check
|
||||||
|
id: health-check
|
||||||
|
run: |
|
||||||
|
echo "Waiting for service to be fully ready..."
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
MAX_RETRIES=5
|
||||||
|
RETRY_COUNT=0
|
||||||
|
|
||||||
|
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
|
||||||
|
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${{ env.PRODUCTION_URL }}/admin" 2>/dev/null || echo "000")
|
||||||
|
|
||||||
|
if [ "$HTTP_STATUS" -ge 200 ] && [ "$HTTP_STATUS" -lt 400 ]; then
|
||||||
|
echo "Health check passed (HTTP $HTTP_STATUS)"
|
||||||
|
echo "status=success" >> $GITHUB_OUTPUT
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
RETRY_COUNT=$((RETRY_COUNT + 1))
|
||||||
|
echo "Health check attempt $RETRY_COUNT failed (HTTP $HTTP_STATUS), retrying in 10s..."
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Health check failed after $MAX_RETRIES attempts"
|
||||||
|
echo "status=failed" >> $GITHUB_OUTPUT
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Rollback on failure
|
||||||
|
if: failure() && steps.health-check.outputs.status == 'failed'
|
||||||
|
run: |
|
||||||
|
echo "Deployment failed, initiating rollback..."
|
||||||
|
|
||||||
|
ssh production << 'ENDSSH'
|
||||||
|
set -e
|
||||||
|
cd ${{ env.PRODUCTION_PATH }}
|
||||||
|
|
||||||
|
if [ -f /tmp/previous_deploy_sha ]; then
|
||||||
|
PREVIOUS_SHA=$(cat /tmp/previous_deploy_sha)
|
||||||
|
echo "Rolling back to: $PREVIOUS_SHA"
|
||||||
|
|
||||||
|
git checkout main
|
||||||
|
git reset --hard $PREVIOUS_SHA
|
||||||
|
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
pm2 stop payload 2>/dev/null || true
|
||||||
|
NODE_OPTIONS="--max-old-space-size=2048" pnpm build
|
||||||
|
pm2 restart payload --update-env
|
||||||
|
pm2 restart queue-worker --update-env
|
||||||
|
|
||||||
|
echo "Rollback complete"
|
||||||
|
pm2 status
|
||||||
|
else
|
||||||
|
echo "No previous SHA found, manual intervention required"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
ENDSSH
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# Post-deployment Verification
|
||||||
|
# ===========================================================================
|
||||||
|
verify:
|
||||||
|
name: Post-deployment Verification
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [deploy]
|
||||||
|
if: success()
|
||||||
|
steps:
|
||||||
|
- name: Full Health Check
|
||||||
|
run: |
|
||||||
|
echo "Running comprehensive health checks..."
|
||||||
|
|
||||||
|
# Check Admin Panel
|
||||||
|
ADMIN_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${{ env.PRODUCTION_URL }}/admin")
|
||||||
|
echo "Admin Panel: HTTP $ADMIN_STATUS"
|
||||||
|
|
||||||
|
# Check API
|
||||||
|
API_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${{ env.PRODUCTION_URL }}/api")
|
||||||
|
echo "API: HTTP $API_STATUS"
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
if [ "$ADMIN_STATUS" -ge 200 ] && [ "$ADMIN_STATUS" -lt 400 ] && \
|
||||||
|
[ "$API_STATUS" -ge 200 ] && [ "$API_STATUS" -lt 400 ]; then
|
||||||
|
echo "All health checks passed!"
|
||||||
|
else
|
||||||
|
echo "Some health checks failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# Deployment Summary
|
||||||
|
# ===========================================================================
|
||||||
|
summary:
|
||||||
|
name: Deployment Summary
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [preflight, pre-tests, backup, deploy, verify]
|
||||||
|
if: always()
|
||||||
|
steps:
|
||||||
|
- name: Create deployment summary
|
||||||
|
run: |
|
||||||
|
echo "## Production Deployment Summary" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Step | Status |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Pre-flight | ${{ needs.preflight.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Pre-tests | ${{ needs.pre-tests.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Backup | ${{ needs.backup.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Deploy | ${{ needs.deploy.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Verify | ${{ needs.verify.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "### Details" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Environment | Production |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| URL | ${{ env.PRODUCTION_URL }} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Deployed | \`${{ needs.preflight.outputs.deploy_tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| SHA | \`${{ needs.preflight.outputs.deploy_sha }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Triggered by | ${{ github.actor }} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Time | $(date -u '+%Y-%m-%d %H:%M:%S UTC') |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# Overall status
|
||||||
|
if [ "${{ needs.verify.result }}" == "success" ]; then
|
||||||
|
echo "### Result: Deployment Successful" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "### Result: Deployment Failed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Check the logs above for details. If automatic rollback occurred, the previous version should be running." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Notify on failure
|
||||||
|
if: needs.deploy.result == 'failure' || needs.verify.result == 'failure'
|
||||||
|
run: |
|
||||||
|
echo "::error::Production deployment failed! Check logs for details."
|
||||||
155
.github/workflows/security.yml
vendored
155
.github/workflows/security.yml
vendored
|
|
@ -1,32 +1,37 @@
|
||||||
name: Security Scanning
|
name: Security Scanning
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# WICHTIG: Dieser Workflow wurde optimiert um ohne kostenpflichtige GitHub
|
||||||
|
# Features (CodeQL, GHAS) auszukommen. Für private Repos sind diese Features
|
||||||
|
# kostenpflichtig (~$49/Monat pro Seat).
|
||||||
|
#
|
||||||
|
# Stattdessen verwenden wir:
|
||||||
|
# - pnpm audit für Dependency Scanning
|
||||||
|
# - ESLint mit Security-Plugin für Code-Analyse
|
||||||
|
# - Lokale Security Tests
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
# Nur bei PRs auf main und wöchentlich - spart GitHub Actions Minuten
|
||||||
branches: [main, develop]
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
schedule:
|
schedule:
|
||||||
# Wöchentlich Sonntag um 00:00 UTC
|
# Wöchentlich Sonntag um 00:00 UTC
|
||||||
- cron: '0 0 * * 0'
|
- cron: '0 0 * * 0'
|
||||||
|
# Manuelle Auslösung für on-demand Scans
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
security-events: write
|
|
||||||
|
env:
|
||||||
|
NODE_VERSION: '20'
|
||||||
|
PNPM_VERSION: '9'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Secret Scanning - Using GitHub's native secret scanning (enabled in repo settings)
|
# ===========================================================================
|
||||||
# Gitleaks removed - now requires paid license, GitHub native is more comprehensive
|
# Dependency Vulnerability Scanning (kostenlos)
|
||||||
secrets:
|
# ===========================================================================
|
||||||
name: Secret Scanning
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Verify GitHub Secret Scanning
|
|
||||||
run: |
|
|
||||||
echo "## Secret Scanning Status" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "✅ GitHub native secret scanning is enabled in repository settings" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "Push protection is active for 423 patterns" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
# Dependency Vulnerability Scanning
|
|
||||||
dependencies:
|
dependencies:
|
||||||
name: Dependency Audit
|
name: Dependency Audit
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
@ -37,58 +42,40 @@ jobs:
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v3
|
uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: 9
|
version: ${{ env.PNPM_VERSION }}
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Run audit
|
- name: Run pnpm audit
|
||||||
run: pnpm audit --audit-level=high
|
run: pnpm audit --audit-level=high
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Check for known vulnerabilities
|
- name: Create audit summary
|
||||||
run: |
|
run: |
|
||||||
echo "## Dependency Audit Results" >> $GITHUB_STEP_SUMMARY
|
echo "## Dependency Audit Results" >> $GITHUB_STEP_SUMMARY
|
||||||
pnpm audit --json | jq -r '.advisories | to_entries[] | "- [\(.value.severity)] \(.value.module_name): \(.value.title)"' >> $GITHUB_STEP_SUMMARY 2>/dev/null || echo "No vulnerabilities found" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
pnpm audit --json 2>/dev/null | jq -r '
|
||||||
|
if .advisories then
|
||||||
|
.advisories | to_entries[] |
|
||||||
|
"| \(.value.severity | ascii_upcase) | \(.value.module_name) | \(.value.title) |"
|
||||||
|
else
|
||||||
|
"No vulnerabilities found"
|
||||||
|
end
|
||||||
|
' >> $GITHUB_STEP_SUMMARY 2>/dev/null || echo "✅ No vulnerabilities found" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# CodeQL Analysis
|
# ===========================================================================
|
||||||
codeql:
|
# ESLint Security Analysis (kostenlos, ersetzt CodeQL)
|
||||||
name: CodeQL Analysis
|
# ===========================================================================
|
||||||
|
eslint-security:
|
||||||
|
name: ESLint Security
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v4
|
|
||||||
with:
|
|
||||||
languages: javascript-typescript
|
|
||||||
queries: security-and-quality
|
|
||||||
|
|
||||||
- name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v4
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v4
|
|
||||||
with:
|
|
||||||
category: "/language:javascript-typescript"
|
|
||||||
|
|
||||||
# Security Unit & Integration Tests
|
|
||||||
security-tests:
|
|
||||||
name: Security Tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [secrets, dependencies]
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
@ -96,12 +83,49 @@ jobs:
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v3
|
uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: 9
|
version: ${{ env.PNPM_VERSION }}
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Run ESLint
|
||||||
|
run: pnpm lint
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Security summary
|
||||||
|
run: |
|
||||||
|
echo "## ESLint Security Check" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "ESLint wurde ausgeführt um potenzielle Sicherheitsprobleme zu finden." >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Hinweis:** Für erweiterte Sicherheitsanalyse (wie CodeQL) wird GitHub Advanced Security benötigt." >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# Security Unit & Integration Tests
|
||||||
|
# ===========================================================================
|
||||||
|
security-tests:
|
||||||
|
name: Security Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [dependencies]
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v3
|
||||||
|
with:
|
||||||
|
version: ${{ env.PNPM_VERSION }}
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
|
@ -124,3 +148,26 @@ jobs:
|
||||||
coverage/
|
coverage/
|
||||||
test-results/
|
test-results/
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# Security Summary
|
||||||
|
# ===========================================================================
|
||||||
|
summary:
|
||||||
|
name: Security Summary
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [dependencies, eslint-security, security-tests]
|
||||||
|
if: always()
|
||||||
|
steps:
|
||||||
|
- name: Create summary
|
||||||
|
run: |
|
||||||
|
echo "## Security Scan Summary" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Dependency Audit | ${{ needs.dependencies.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| ESLint Security | ${{ needs.eslint-security.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Security Tests | ${{ needs.security-tests.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "---" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Info:** GitHub Secret Scanning ist in den Repository-Einstellungen aktiviert (kostenlos für alle Repos)." >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
|
||||||
448
docs/DEPLOYMENT_STRATEGY.md
Normal file
448
docs/DEPLOYMENT_STRATEGY.md
Normal file
|
|
@ -0,0 +1,448 @@
|
||||||
|
# Deployment-Strategie: Dev → Production
|
||||||
|
|
||||||
|
*Erstellt: 27. Dezember 2025*
|
||||||
|
|
||||||
|
## Zusammenfassung
|
||||||
|
|
||||||
|
Diese Strategie gewährleistet fehlerfreie Deployments von der Development-Umgebung (sv-payload) zur Production-Umgebung (Hetzner 3) durch:
|
||||||
|
|
||||||
|
1. **Automatisierte CI/CD Pipeline** mit obligatorischen Tests
|
||||||
|
2. **Staging-first-Ansatz** - Änderungen müssen auf Staging erfolgreich sein
|
||||||
|
3. **Pre-deployment Backup** - Automatische Datenbank-Sicherung
|
||||||
|
4. **Health Checks** - Automatische Verifizierung nach Deployment
|
||||||
|
5. **Rollback-Mechanismus** - Schnelle Wiederherstellung bei Fehlern
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deployment-Workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ DEPLOYMENT PIPELINE │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ DEVELOPMENT STAGING PRODUCTION │
|
||||||
|
│ ───────────── ───────── ────────── │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────┐ Push ┌─────────────┐ Merge ┌─────────────────┐ │
|
||||||
|
│ │ Code │ ────────▶ │ CI Pipeline │ ──────────▶ │ Production CI │ │
|
||||||
|
│ │ Changes │ │ (Automatic) │ │ (Manual Trigger)│ │
|
||||||
|
│ └─────────┘ └──────┬──────┘ └────────┬────────┘ │
|
||||||
|
│ │ │ │
|
||||||
|
│ ▼ ▼ │
|
||||||
|
│ ┌─────────────┐ ┌───────────────┐ │
|
||||||
|
│ │ Tests │ │ Pre-flight │ │
|
||||||
|
│ │ Lint │ │ Checks │ │
|
||||||
|
│ │ Build │ └───────┬───────┘ │
|
||||||
|
│ └──────┬──────┘ │ │
|
||||||
|
│ │ ▼ │
|
||||||
|
│ ▼ ┌─────────────┐ │
|
||||||
|
│ ┌─────────────┐ │ DB Backup │ │
|
||||||
|
│ │ Deploy to │ └──────┬──────┘ │
|
||||||
|
│ │ Staging │ │ │
|
||||||
|
│ └──────┬──────┘ ▼ │
|
||||||
|
│ │ ┌─────────────┐ │
|
||||||
|
│ ▼ │ Deploy │ │
|
||||||
|
│ ┌─────────────┐ └──────┬──────┘ │
|
||||||
|
│ │ Verify │ │ │
|
||||||
|
│ │ Staging │ ▼ │
|
||||||
|
│ └─────────────┘ ┌─────────────┐ │
|
||||||
|
│ │ Health Check│ │
|
||||||
|
│ └──────┬──────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌──────┴──────┐ │
|
||||||
|
│ │ Pass? │ │
|
||||||
|
│ └──────┬──────┘ │
|
||||||
|
│ / \ │
|
||||||
|
│ / \ │
|
||||||
|
│ ┌────▼───┐ ┌────▼───┐ │
|
||||||
|
│ │ SUCCESS│ │ROLLBACK│ │
|
||||||
|
│ └────────┘ └────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Umgebungen
|
||||||
|
|
||||||
|
| Umgebung | Server | URL | Branch | Zweck |
|
||||||
|
|----------|--------|-----|--------|-------|
|
||||||
|
| **Development** | sv-payload (LXC 700) | https://pl.porwoll.tech | `develop` | Entwicklung & Testing |
|
||||||
|
| **Production** | Hetzner 3 | https://cms.c2sgmbh.de | `main` | Live-System |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Schritt-für-Schritt Anleitung
|
||||||
|
|
||||||
|
### Phase 1: Development & Testing (develop Branch)
|
||||||
|
|
||||||
|
1. **Code-Änderungen auf develop pushen**
|
||||||
|
```bash
|
||||||
|
git checkout develop
|
||||||
|
git add .
|
||||||
|
git commit -m "feat: your feature"
|
||||||
|
git push origin develop
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Automatische CI Pipeline** (optimiert für GitHub Actions Kostenkontrolle)
|
||||||
|
- Bei **Push auf develop**:
|
||||||
|
- ESLint & Prettier Check
|
||||||
|
- TypeScript Compiler Check (optional)
|
||||||
|
- Build Test
|
||||||
|
- Bei **Pull Request** (volle Test-Suite):
|
||||||
|
- Alle obigen Checks
|
||||||
|
- Unit Tests
|
||||||
|
- Integration Tests
|
||||||
|
- E2E Tests
|
||||||
|
|
||||||
|
3. **Automatisches Staging-Deployment**
|
||||||
|
- Bei erfolgreichem CI wird automatisch auf pl.porwoll.tech deployed
|
||||||
|
- Health Check verifiziert erfolgreiche Deployment
|
||||||
|
|
||||||
|
4. **Manuelle Verifizierung auf Staging**
|
||||||
|
- Admin Panel testen: https://pl.porwoll.tech/admin
|
||||||
|
- Neue Features manuell prüfen
|
||||||
|
- API-Endpoints testen
|
||||||
|
|
||||||
|
### Phase 2: Production Deployment (main Branch)
|
||||||
|
|
||||||
|
#### Option A: Via GitHub Actions (Empfohlen)
|
||||||
|
|
||||||
|
1. **develop in main mergen**
|
||||||
|
```bash
|
||||||
|
git checkout main
|
||||||
|
git pull origin main
|
||||||
|
git merge develop
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **GitHub Actions Workflow starten**
|
||||||
|
- Gehe zu: Actions → "Deploy to Production" → "Run workflow"
|
||||||
|
- Oder via CLI:
|
||||||
|
```bash
|
||||||
|
gh workflow run deploy-production.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Optionen beim Start:**
|
||||||
|
- `skip_tests`: Tests überspringen (nicht empfohlen)
|
||||||
|
- `skip_backup`: Backup überspringen (nicht empfohlen)
|
||||||
|
- `skip_migrations`: Migrationen überspringen
|
||||||
|
- `deploy_tag`: Spezifischer Git-Tag oder Commit
|
||||||
|
|
||||||
|
4. **Workflow führt automatisch aus:**
|
||||||
|
- Pre-flight Checks
|
||||||
|
- Pre-deployment Tests
|
||||||
|
- Datenbank-Backup
|
||||||
|
- Deployment
|
||||||
|
- Health Check
|
||||||
|
- Bei Fehler: Automatischer Rollback
|
||||||
|
|
||||||
|
#### Option B: Manuelles Deployment auf Server
|
||||||
|
|
||||||
|
1. **SSH zum Production-Server**
|
||||||
|
```bash
|
||||||
|
ssh payload@162.55.85.18
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Deploy-Script ausführen**
|
||||||
|
```bash
|
||||||
|
cd ~/payload-cms
|
||||||
|
./scripts/deploy-production.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Optionen:**
|
||||||
|
```bash
|
||||||
|
# Normales Deployment
|
||||||
|
./scripts/deploy-production.sh
|
||||||
|
|
||||||
|
# Ohne Bestätigung (CI/Scripting)
|
||||||
|
./scripts/deploy-production.sh -y
|
||||||
|
|
||||||
|
# Ohne Backup (nicht empfohlen)
|
||||||
|
./scripts/deploy-production.sh --skip-backup
|
||||||
|
|
||||||
|
# Nur Service-Neustart
|
||||||
|
./scripts/deploy-production.sh --skip-build
|
||||||
|
|
||||||
|
# Rollback zur vorherigen Version
|
||||||
|
./scripts/deploy-production.sh --rollback
|
||||||
|
|
||||||
|
# Dry-Run (zeigt was passieren würde)
|
||||||
|
./scripts/deploy-production.sh --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rollback-Strategie
|
||||||
|
|
||||||
|
### Automatischer Rollback (GitHub Actions)
|
||||||
|
|
||||||
|
- Bei fehlgeschlagenem Health Check nach Deployment
|
||||||
|
- Workflow rollt automatisch auf vorherige Version zurück
|
||||||
|
|
||||||
|
### Manueller Rollback
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Auf dem Production-Server
|
||||||
|
ssh payload@162.55.85.18
|
||||||
|
cd ~/payload-cms
|
||||||
|
|
||||||
|
# Rollback zur vorherigen Version
|
||||||
|
./scripts/deploy-production.sh --rollback
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rollback zu spezifischem Commit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git log --oneline -10 # Zeige letzte Commits
|
||||||
|
git checkout main
|
||||||
|
git reset --hard <commit-sha>
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
pnpm build
|
||||||
|
pm2 restart payload
|
||||||
|
```
|
||||||
|
|
||||||
|
### Datenbank-Rollback
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Backup-Dateien anzeigen
|
||||||
|
ls -la ~/backups/pre-deploy/
|
||||||
|
|
||||||
|
# Backup wiederherstellen
|
||||||
|
gunzip -c ~/backups/pre-deploy/payload_db_YYYY-MM-DD_HH-MM-SS.sql.gz | \
|
||||||
|
psql -h localhost -U payload -d payload_db
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pre-Deployment Checkliste
|
||||||
|
|
||||||
|
### Vor jedem Production-Deployment
|
||||||
|
|
||||||
|
- [ ] Alle CI-Tests auf develop erfolgreich
|
||||||
|
- [ ] Staging-Deployment erfolgreich (pl.porwoll.tech)
|
||||||
|
- [ ] Neue Features auf Staging manuell getestet
|
||||||
|
- [ ] Keine offenen kritischen Bugs
|
||||||
|
- [ ] develop in main gemergt
|
||||||
|
- [ ] Bei DB-Änderungen: Migration auf Staging getestet
|
||||||
|
|
||||||
|
### Kritische Änderungen (Extra Vorsicht)
|
||||||
|
|
||||||
|
- [ ] Schema-Migrationen
|
||||||
|
- [ ] Breaking API Changes
|
||||||
|
- [ ] Sicherheits-Updates
|
||||||
|
- [ ] Umgebungsvariablen-Änderungen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Post-Deployment Verifizierung
|
||||||
|
|
||||||
|
### Automatische Checks
|
||||||
|
|
||||||
|
1. **Health Check** - HTTP-Status von /admin
|
||||||
|
2. **PM2 Status** - Prozesse laufen
|
||||||
|
|
||||||
|
### Manuelle Checks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# PM2 Status
|
||||||
|
pm2 status
|
||||||
|
|
||||||
|
# Logs auf Fehler prüfen
|
||||||
|
pm2 logs payload --lines 50
|
||||||
|
|
||||||
|
# Admin Panel erreichbar?
|
||||||
|
curl -I https://cms.c2sgmbh.de/admin
|
||||||
|
|
||||||
|
# API funktioniert?
|
||||||
|
curl -I https://cms.c2sgmbh.de/api
|
||||||
|
|
||||||
|
# Redis verbunden?
|
||||||
|
redis-cli ping
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fehlerbehandlung
|
||||||
|
|
||||||
|
### Build schlägt fehl
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Cache löschen
|
||||||
|
rm -rf .next
|
||||||
|
NODE_OPTIONS="--max-old-space-size=2048" pnpm build
|
||||||
|
|
||||||
|
# Bei Memory-Problemen: PM2 stoppen
|
||||||
|
pm2 stop all
|
||||||
|
NODE_OPTIONS="--max-old-space-size=3072" pnpm build
|
||||||
|
pm2 start ecosystem.config.cjs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Migration schlägt fehl
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Status prüfen
|
||||||
|
pnpm payload migrate:status
|
||||||
|
|
||||||
|
# Logs prüfen
|
||||||
|
pm2 logs payload --lines 100
|
||||||
|
|
||||||
|
# Bei Bedarf: Rollback
|
||||||
|
./scripts/deploy-production.sh --rollback
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service startet nicht
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Logs prüfen
|
||||||
|
pm2 logs payload --lines 100
|
||||||
|
|
||||||
|
# Prozess komplett neu starten
|
||||||
|
pm2 delete payload
|
||||||
|
pm2 start ecosystem.config.cjs --only payload
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GitHub Secrets
|
||||||
|
|
||||||
|
Folgende Secrets müssen in GitHub konfiguriert sein:
|
||||||
|
|
||||||
|
| Secret | Beschreibung | Verwendet in |
|
||||||
|
|--------|--------------|--------------|
|
||||||
|
| `STAGING_SSH_KEY` | SSH Private Key für sv-payload | deploy-staging.yml |
|
||||||
|
| `PRODUCTION_SSH_KEY` | SSH Private Key für Hetzner 3 | deploy-production.yml |
|
||||||
|
|
||||||
|
### SSH Key generieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Neuen Key generieren
|
||||||
|
ssh-keygen -t ed25519 -C "github-actions@deploy" -f deploy_key
|
||||||
|
|
||||||
|
# Public Key auf Server kopieren
|
||||||
|
ssh-copy-id -i deploy_key.pub payload@162.55.85.18
|
||||||
|
|
||||||
|
# Private Key in GitHub Secrets speichern
|
||||||
|
cat deploy_key | pbcopy # (macOS) oder xclip
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Monitoring nach Deployment
|
||||||
|
|
||||||
|
### Erste 15 Minuten
|
||||||
|
|
||||||
|
1. PM2 Logs auf Fehler überwachen
|
||||||
|
2. Admin Panel regelmäßig aufrufen
|
||||||
|
3. API-Requests prüfen
|
||||||
|
|
||||||
|
### Erste 24 Stunden
|
||||||
|
|
||||||
|
1. Umami Analytics prüfen
|
||||||
|
2. Error-Logs reviewen
|
||||||
|
3. User-Feedback sammeln
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Do's
|
||||||
|
|
||||||
|
- Immer erst auf Staging testen
|
||||||
|
- Backups vor kritischen Änderungen
|
||||||
|
- Kleine, inkrementelle Deployments
|
||||||
|
- Dokumentation aktualisieren
|
||||||
|
- Rollback-Plan vorbereiten
|
||||||
|
|
||||||
|
### Don'ts
|
||||||
|
|
||||||
|
- Nie direkt auf main entwickeln
|
||||||
|
- Nie ohne Tests deployen
|
||||||
|
- Nie große Schema-Änderungen ohne Backup
|
||||||
|
- Nie am Freitag Nachmittag deployen
|
||||||
|
- Nie mehrere kritische Änderungen gleichzeitig
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dateien
|
||||||
|
|
||||||
|
| Datei | Beschreibung |
|
||||||
|
|-------|--------------|
|
||||||
|
| `.github/workflows/ci.yml` | CI Pipeline (Tests, Build) |
|
||||||
|
| `.github/workflows/security.yml` | Security Scanning (Dependency Audit, ESLint) |
|
||||||
|
| `.github/workflows/deploy-staging.yml` | Automatisches Staging-Deployment |
|
||||||
|
| `.github/workflows/deploy-production.yml` | Production-Deployment (manuell) |
|
||||||
|
| `scripts/deploy-staging.sh` | Manuelles Staging-Deployment |
|
||||||
|
| `scripts/deploy-production.sh` | Manuelles Production-Deployment |
|
||||||
|
| `ecosystem.config.cjs` | PM2 Konfiguration |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GitHub Actions Optimierung
|
||||||
|
|
||||||
|
### Kostenmodell für Private Repos
|
||||||
|
|
||||||
|
| Feature | Kostenlos | Kostenpflichtig |
|
||||||
|
|---------|-----------|-----------------|
|
||||||
|
| GitHub Actions | 2.000 Min/Monat | darüber hinaus |
|
||||||
|
| CodeQL | Öffentliche Repos | Private Repos (GHAS ~$49/Monat) |
|
||||||
|
| Secret Scanning | Repo-Settings (Free) | GHAS für erweiterte Features |
|
||||||
|
| Dependabot | Ja | - |
|
||||||
|
|
||||||
|
### Optimierungen (27.12.2025)
|
||||||
|
|
||||||
|
**CI-Workflow:**
|
||||||
|
- Push auf `develop`: Nur Lint + Build (spart ~80% Actions-Minuten)
|
||||||
|
- Pull Requests: Volle Test-Suite inkl. E2E
|
||||||
|
- Push auf `main`: Keine CI (wird durch deploy-production getriggert)
|
||||||
|
- Markdown-Änderungen: Überspringen CI komplett
|
||||||
|
|
||||||
|
**Security-Workflow:**
|
||||||
|
- CodeQL entfernt (kostenpflichtig für private Repos)
|
||||||
|
- Ersetzt durch: ESLint + pnpm audit
|
||||||
|
- Nur bei PRs auf main und wöchentlich (nicht bei jedem Push)
|
||||||
|
- Secret Scanning: Native GitHub-Feature in Repo-Settings (kostenlos)
|
||||||
|
|
||||||
|
### GitHub Actions Minutenverbrauch
|
||||||
|
|
||||||
|
| Workflow | Trigger | Geschätzte Dauer | Häufigkeit |
|
||||||
|
|----------|---------|------------------|------------|
|
||||||
|
| CI (Push) | Push develop | ~3 Min | Pro Push |
|
||||||
|
| CI (PR) | Pull Request | ~15 Min | Pro PR |
|
||||||
|
| Security | PR + Wöchentlich | ~5 Min | 1x/Woche + PRs |
|
||||||
|
| Deploy Staging | Push develop | ~5 Min | Pro Push |
|
||||||
|
| Deploy Production | Manual | ~8 Min | Bei Release |
|
||||||
|
|
||||||
|
**Geschätzte monatliche Nutzung:**
|
||||||
|
- ~20 Pushes × 3 Min = 60 Min
|
||||||
|
- ~5 PRs × 15 Min = 75 Min
|
||||||
|
- ~4 Security Scans × 5 Min = 20 Min
|
||||||
|
- ~20 Staging Deploys × 5 Min = 100 Min
|
||||||
|
- **Gesamt: ~255 Min/Monat** (von 2.000 kostenlos)
|
||||||
|
|
||||||
|
### Bei Billing-Problemen
|
||||||
|
|
||||||
|
1. **Spending Limit prüfen:**
|
||||||
|
GitHub → Settings → Billing → Spending limits
|
||||||
|
|
||||||
|
2. **Zahlungsmethode aktualisieren:**
|
||||||
|
GitHub → Settings → Billing → Payment method
|
||||||
|
|
||||||
|
3. **Actions deaktivieren (temporär):**
|
||||||
|
Repository → Settings → Actions → Disable Actions
|
||||||
|
|
||||||
|
4. **Manuelles Deployment:**
|
||||||
|
```bash
|
||||||
|
# Staging
|
||||||
|
ssh payload@10.10.181.100
|
||||||
|
cd ~/payload-cms && ./scripts/deploy-staging.sh
|
||||||
|
|
||||||
|
# Production
|
||||||
|
ssh payload@162.55.85.18
|
||||||
|
cd ~/payload-cms && ./scripts/deploy-production.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Dokumentation: Complex Care Solutions GmbH | 27.12.2025*
|
||||||
327
scripts/deploy-production.sh
Executable file
327
scripts/deploy-production.sh
Executable file
|
|
@ -0,0 +1,327 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# Production Deployment Script
|
||||||
|
# =============================================================================
|
||||||
|
# Usage: ./scripts/deploy-production.sh [OPTIONS]
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# --skip-backup Skip database backup (not recommended)
|
||||||
|
# --skip-migrations Skip database migrations
|
||||||
|
# --skip-build Skip build (only restart services)
|
||||||
|
# --rollback Rollback to previous deployment
|
||||||
|
# --dry-run Show what would be done without executing
|
||||||
|
# -y, --yes Skip confirmation prompt
|
||||||
|
#
|
||||||
|
# This script deploys the main branch to the production server (Hetzner 3).
|
||||||
|
# It should be run ON the production server (cms.c2sgmbh.de).
|
||||||
|
#
|
||||||
|
# IMPORTANT: Always ensure develop is merged to main before deploying!
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
PROJECT_DIR="${PROJECT_DIR:-/home/payload/payload-cms}"
|
||||||
|
BRANCH="main"
|
||||||
|
BACKUP_DIR="$HOME/backups/pre-deploy"
|
||||||
|
ROLLBACK_FILE="/tmp/previous_deploy_sha"
|
||||||
|
LOG_DIR="$HOME/logs"
|
||||||
|
LOG_FILE="$LOG_DIR/deploy-production-$(date +%Y%m%d).log"
|
||||||
|
HEALTH_CHECK_URL="http://localhost:3001/admin"
|
||||||
|
PRODUCTION_URL="https://cms.c2sgmbh.de"
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
SKIP_BACKUP=false
|
||||||
|
SKIP_MIGRATIONS=false
|
||||||
|
SKIP_BUILD=false
|
||||||
|
DO_ROLLBACK=false
|
||||||
|
DRY_RUN=false
|
||||||
|
AUTO_YES=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case $arg in
|
||||||
|
--skip-backup)
|
||||||
|
SKIP_BACKUP=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--skip-migrations)
|
||||||
|
SKIP_MIGRATIONS=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--skip-build)
|
||||||
|
SKIP_BUILD=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--rollback)
|
||||||
|
DO_ROLLBACK=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--dry-run)
|
||||||
|
DRY_RUN=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-y|--yes)
|
||||||
|
AUTO_YES=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
head -30 "$0" | tail -25
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
log() {
|
||||||
|
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
||||||
|
echo -e "${BLUE}${msg}${NC}" | tee -a "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
success() {
|
||||||
|
local msg="[SUCCESS] $1"
|
||||||
|
echo -e "${GREEN}${msg}${NC}" | tee -a "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
warn() {
|
||||||
|
local msg="[WARNING] $1"
|
||||||
|
echo -e "${YELLOW}${msg}${NC}" | tee -a "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
error() {
|
||||||
|
local msg="[ERROR] $1"
|
||||||
|
echo -e "${RED}${msg}${NC}" | tee -a "$LOG_FILE"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
info() {
|
||||||
|
local msg="[INFO] $1"
|
||||||
|
echo -e "${CYAN}${msg}${NC}" | tee -a "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensure directories exist
|
||||||
|
mkdir -p "$BACKUP_DIR" "$LOG_DIR"
|
||||||
|
|
||||||
|
# Header
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}=============================================="
|
||||||
|
echo -e " PRODUCTION DEPLOYMENT - Hetzner 3"
|
||||||
|
echo -e " URL: $PRODUCTION_URL"
|
||||||
|
echo -e "==============================================${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if we're on the right server
|
||||||
|
if [ ! -d "$PROJECT_DIR" ]; then
|
||||||
|
error "Project directory not found: $PROJECT_DIR. Are you on the production server?"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
|
||||||
|
# Rollback mode
|
||||||
|
if [ "$DO_ROLLBACK" = true ]; then
|
||||||
|
echo -e "${YELLOW}=== ROLLBACK MODE ===${NC}"
|
||||||
|
|
||||||
|
if [ ! -f "$ROLLBACK_FILE" ]; then
|
||||||
|
error "No rollback point found. Cannot rollback."
|
||||||
|
fi
|
||||||
|
|
||||||
|
PREVIOUS_SHA=$(cat "$ROLLBACK_FILE")
|
||||||
|
log "Rolling back to: $PREVIOUS_SHA"
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
info "[DRY RUN] Would rollback to $PREVIOUS_SHA"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
git fetch origin main
|
||||||
|
git checkout main
|
||||||
|
git reset --hard "$PREVIOUS_SHA"
|
||||||
|
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
pm2 stop payload 2>/dev/null || true
|
||||||
|
NODE_OPTIONS="--max-old-space-size=2048" pnpm build
|
||||||
|
pm2 restart payload --update-env
|
||||||
|
pm2 restart queue-worker --update-env
|
||||||
|
|
||||||
|
success "Rollback complete to $PREVIOUS_SHA"
|
||||||
|
pm2 status
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show current state
|
||||||
|
log "Checking current state..."
|
||||||
|
CURRENT_SHA=$(git rev-parse HEAD)
|
||||||
|
CURRENT_BRANCH=$(git branch --show-current)
|
||||||
|
info "Current branch: $CURRENT_BRANCH"
|
||||||
|
info "Current commit: $CURRENT_SHA"
|
||||||
|
|
||||||
|
# Fetch latest
|
||||||
|
log "Fetching latest from origin..."
|
||||||
|
git fetch origin main
|
||||||
|
|
||||||
|
LATEST_SHA=$(git rev-parse origin/main)
|
||||||
|
info "Latest main: $LATEST_SHA"
|
||||||
|
|
||||||
|
if [ "$CURRENT_SHA" = "$LATEST_SHA" ]; then
|
||||||
|
warn "Already at latest version. Nothing to deploy."
|
||||||
|
if [ "$AUTO_YES" != true ]; then
|
||||||
|
read -p "Continue anyway? (y/n) " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show changes
|
||||||
|
echo ""
|
||||||
|
log "Changes to be deployed:"
|
||||||
|
git log --oneline "$CURRENT_SHA".."$LATEST_SHA" 2>/dev/null || echo " (fresh deployment)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Confirmation
|
||||||
|
if [ "$AUTO_YES" != true ] && [ "$DRY_RUN" != true ]; then
|
||||||
|
echo -e "${YELLOW}PRODUCTION DEPLOYMENT${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " Skip backup: $SKIP_BACKUP"
|
||||||
|
echo " Skip migrations: $SKIP_MIGRATIONS"
|
||||||
|
echo " Skip build: $SKIP_BUILD"
|
||||||
|
echo ""
|
||||||
|
read -p "Deploy to PRODUCTION? (yes/no) " CONFIRM
|
||||||
|
if [ "$CONFIRM" != "yes" ]; then
|
||||||
|
echo "Deployment cancelled."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Dry run check
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
info "[DRY RUN] Would deploy $LATEST_SHA"
|
||||||
|
info "[DRY RUN] Would create backup in $BACKUP_DIR"
|
||||||
|
info "[DRY RUN] Would run migrations: $([[ $SKIP_MIGRATIONS = true ]] && echo 'NO' || echo 'YES')"
|
||||||
|
info "[DRY RUN] Would run build: $([[ $SKIP_BUILD = true ]] && echo 'NO' || echo 'YES')"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 1: Create backup
|
||||||
|
if [ "$SKIP_BACKUP" = false ]; then
|
||||||
|
log "Creating database backup..."
|
||||||
|
BACKUP_FILE="$BACKUP_DIR/payload_db_$(date +%Y-%m-%d_%H-%M-%S).sql.gz"
|
||||||
|
|
||||||
|
if [ -f "$HOME/.pgpass" ]; then
|
||||||
|
# Read password from .pgpass (format: host:port:db:user:password)
|
||||||
|
export PGPASSWORD
|
||||||
|
PGPASSWORD=$(grep 'payload_db' "$HOME/.pgpass" | cut -d: -f5)
|
||||||
|
pg_dump -h localhost -U payload payload_db | gzip > "$BACKUP_FILE"
|
||||||
|
unset PGPASSWORD
|
||||||
|
success "Backup created: $BACKUP_FILE"
|
||||||
|
|
||||||
|
# Keep only last 5 pre-deploy backups
|
||||||
|
ls -t "$BACKUP_DIR"/payload_db_*.sql.gz 2>/dev/null | tail -n +6 | xargs rm -f 2>/dev/null || true
|
||||||
|
else
|
||||||
|
warn "No .pgpass found, skipping backup"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Skipping backup (--skip-backup)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 2: Save rollback point
|
||||||
|
log "Saving rollback point..."
|
||||||
|
echo "$CURRENT_SHA" > "$ROLLBACK_FILE"
|
||||||
|
info "Rollback SHA saved: $CURRENT_SHA"
|
||||||
|
|
||||||
|
# Step 3: Pull latest code
|
||||||
|
log "Pulling latest code..."
|
||||||
|
git stash --include-untracked 2>/dev/null || true
|
||||||
|
git checkout main
|
||||||
|
git reset --hard origin/main
|
||||||
|
success "Code updated to: $(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
|
# Step 4: Install dependencies
|
||||||
|
log "Installing dependencies..."
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
success "Dependencies installed"
|
||||||
|
|
||||||
|
# Step 5: Run migrations
|
||||||
|
if [ "$SKIP_MIGRATIONS" = false ]; then
|
||||||
|
log "Running database migrations..."
|
||||||
|
pnpm payload migrate || warn "No migrations to run or migration failed"
|
||||||
|
else
|
||||||
|
warn "Skipping migrations (--skip-migrations)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 6: Build
|
||||||
|
if [ "$SKIP_BUILD" = false ]; then
|
||||||
|
log "Stopping services for build..."
|
||||||
|
pm2 stop payload 2>/dev/null || true
|
||||||
|
pm2 stop queue-worker 2>/dev/null || true
|
||||||
|
|
||||||
|
log "Building application..."
|
||||||
|
NODE_OPTIONS="--max-old-space-size=2048" pnpm build
|
||||||
|
success "Build completed"
|
||||||
|
else
|
||||||
|
warn "Skipping build (--skip-build)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 7: Restart services
|
||||||
|
log "Restarting services..."
|
||||||
|
pm2 restart payload --update-env 2>/dev/null || pm2 start ecosystem.config.cjs --only payload
|
||||||
|
pm2 restart queue-worker --update-env 2>/dev/null || pm2 start ecosystem.config.cjs --only queue-worker
|
||||||
|
success "Services restarted"
|
||||||
|
|
||||||
|
# Step 8: Health check
|
||||||
|
log "Waiting for service to start..."
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
log "Running health check..."
|
||||||
|
MAX_RETRIES=5
|
||||||
|
RETRY_COUNT=0
|
||||||
|
HEALTH_OK=false
|
||||||
|
|
||||||
|
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
|
||||||
|
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_CHECK_URL" 2>/dev/null || echo "000")
|
||||||
|
|
||||||
|
if [ "$HTTP_STATUS" -ge 200 ] && [ "$HTTP_STATUS" -lt 400 ]; then
|
||||||
|
success "Health check passed (HTTP $HTTP_STATUS)"
|
||||||
|
HEALTH_OK=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
RETRY_COUNT=$((RETRY_COUNT + 1))
|
||||||
|
warn "Health check attempt $RETRY_COUNT failed (HTTP $HTTP_STATUS), retrying in 5s..."
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$HEALTH_OK" = false ]; then
|
||||||
|
error "Health check failed after $MAX_RETRIES attempts. Consider rolling back with --rollback"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show PM2 status
|
||||||
|
echo ""
|
||||||
|
log "PM2 Status:"
|
||||||
|
pm2 status
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}=============================================="
|
||||||
|
echo -e " PRODUCTION DEPLOYMENT COMPLETE!"
|
||||||
|
echo -e "==============================================${NC}"
|
||||||
|
echo ""
|
||||||
|
echo " URL: $PRODUCTION_URL"
|
||||||
|
echo " Admin: $PRODUCTION_URL/admin"
|
||||||
|
echo " Commit: $(git rev-parse --short HEAD)"
|
||||||
|
echo " Previous: $(cat $ROLLBACK_FILE | cut -c1-7)"
|
||||||
|
echo " Time: $(date)"
|
||||||
|
echo ""
|
||||||
|
echo " To rollback: ./scripts/deploy-production.sh --rollback"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
log "Deployment finished successfully"
|
||||||
Loading…
Reference in a new issue