From e3c7d92121629e43f1041f231632d7728d1e0dbd Mon Sep 17 00:00:00 2001 From: Martin Porwoll Date: Sun, 14 Dec 2025 12:37:25 +0000 Subject: [PATCH] feat: add staging deployment workflow and script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add GitHub Actions workflow for automatic staging deployment on develop branch - Add manual deploy script with --skip-build and --skip-migrations options - Update CLAUDE.md with deployment documentation - Mark staging-deployment TODO as complete Deployment target: pl.c2sgmbh.de (37.24.237.181) Triggers: push to develop, manual workflow_dispatch 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/deploy-staging.yml | 189 +++++++++++++++++++++++++++ CLAUDE.md | 33 +++++ docs/anleitungen/TODO.md | 16 +-- scripts/deploy-staging.sh | 165 +++++++++++++++++++++++ 4 files changed, 395 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/deploy-staging.yml create mode 100755 scripts/deploy-staging.sh diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml new file mode 100644 index 0000000..ee9e75b --- /dev/null +++ b/.github/workflows/deploy-staging.yml @@ -0,0 +1,189 @@ +name: Deploy to Staging + +on: + push: + branches: [develop] + workflow_dispatch: + inputs: + skip_tests: + description: 'Skip tests before deployment' + required: false + default: 'false' + type: boolean + +concurrency: + group: staging-deployment + cancel-in-progress: false + +env: + NODE_VERSION: '20' + PNPM_VERSION: '9' + STAGING_HOST: '37.24.237.181' + STAGING_USER: 'payload' + STAGING_PATH: '/home/payload/payload-cms' + +jobs: + # =========================================================================== + # Pre-deployment checks (optional, can be skipped via workflow_dispatch) + # =========================================================================== + pre-checks: + name: Pre-deployment Checks + runs-on: ubuntu-latest + if: ${{ github.event.inputs.skip_tests != 'true' }} + 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' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run ESLint + run: pnpm lint + + - name: Run Unit Tests + run: pnpm test:unit + 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' + + # =========================================================================== + # Deploy to Staging Server + # =========================================================================== + deploy: + name: Deploy to Staging + runs-on: ubuntu-latest + needs: [pre-checks] + if: always() && (needs.pre-checks.result == 'success' || needs.pre-checks.result == 'skipped') + environment: + name: staging + url: https://pl.c2sgmbh.de + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Configure SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.STAGING_SSH_KEY }}" > ~/.ssh/staging_key + chmod 600 ~/.ssh/staging_key + cat >> ~/.ssh/config << EOF + Host staging + HostName ${{ env.STAGING_HOST }} + User ${{ env.STAGING_USER }} + IdentityFile ~/.ssh/staging_key + StrictHostKeyChecking no + UserKnownHostsFile /dev/null + EOF + + - name: Deploy to Staging + run: | + ssh staging << 'ENDSSH' + set -e + + echo "=== Staging Deployment Started ===" + echo "Time: $(date)" + echo "Branch: ${{ github.ref_name }}" + echo "Commit: ${{ github.sha }}" + + cd ${{ env.STAGING_PATH }} + + # Stash any local changes + git stash --include-untracked || true + + # Fetch and checkout + echo "=== Fetching latest code ===" + git fetch origin develop + git checkout develop + git reset --hard origin/develop + + # Install dependencies + echo "=== Installing dependencies ===" + pnpm install --frozen-lockfile + + # Run migrations if any + echo "=== Running migrations ===" + pnpm payload migrate || echo "No migrations to run" + + # Build application + echo "=== Building application ===" + NODE_OPTIONS="--max-old-space-size=2048" pnpm build + + # Restart services + echo "=== Restarting services ===" + pm2 restart payload --update-env || pm2 start ecosystem.config.cjs --only payload + pm2 restart queue-worker --update-env || pm2 start ecosystem.config.cjs --only queue-worker + + # Wait for service to be healthy + echo "=== Waiting for service to start ===" + sleep 10 + + # Health check + echo "=== Running health check ===" + curl -sf http://localhost:3000/api/health || curl -sf http://localhost:3000/admin || echo "Health check endpoint not available" + + # Show status + echo "=== Deployment Complete ===" + pm2 status + echo "Time: $(date)" + ENDSSH + + - name: Verify Deployment + run: | + echo "Verifying staging deployment..." + sleep 5 + + # Check if staging is responding + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://pl.c2sgmbh.de/admin || echo "000") + + if [ "$HTTP_STATUS" -ge 200 ] && [ "$HTTP_STATUS" -lt 400 ]; then + echo "Staging is responding with HTTP $HTTP_STATUS" + else + echo "Warning: Staging returned HTTP $HTTP_STATUS" + fi + + - name: Create deployment summary + if: always() + run: | + echo "## Staging Deployment Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY + echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Environment | Staging |" >> $GITHUB_STEP_SUMMARY + echo "| URL | https://pl.c2sgmbh.de |" >> $GITHUB_STEP_SUMMARY + echo "| Branch | ${{ github.ref_name }} |" >> $GITHUB_STEP_SUMMARY + echo "| Commit | \`${{ github.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 + + # =========================================================================== + # Notify on failure + # =========================================================================== + notify-failure: + name: Notify on Failure + runs-on: ubuntu-latest + needs: [deploy] + if: failure() + steps: + - name: Create failure summary + run: | + echo "## Staging Deployment Failed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "The deployment to staging failed. Please check the logs above for details." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY + echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY + echo "**Actor:** ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY diff --git a/CLAUDE.md b/CLAUDE.md index 24ec9bd..c2a65a8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -831,6 +831,39 @@ pnpm build # Production Build - **CodeQL**: Static Analysis (SAST) - **Security Tests**: Unit & Integration Tests für Security-Module +### deploy-staging.yml (Staging Deployment) +Automatisches Deployment auf Staging-Server bei Push auf `develop`: + +| Trigger | Aktion | +|---------|--------| +| Push auf `develop` | Automatisches Deployment | +| `workflow_dispatch` | Manuelles Deployment (optional: skip_tests) | + +**Deployment-Ziel:** +- **URL:** https://pl.c2sgmbh.de +- **Server:** 37.24.237.181 (sv-payload) + +**Ablauf:** +1. Pre-deployment Checks (Lint, Tests) +2. SSH-Verbindung zum Staging-Server +3. Git Pull + Dependencies installieren +4. Migrations ausführen +5. Build + PM2 Restart +6. Health Check + +**Manuelles Staging-Deployment:** +```bash +# Auf dem Staging-Server (pl.c2sgmbh.de) +./scripts/deploy-staging.sh + +# Mit Optionen +./scripts/deploy-staging.sh --skip-build # Nur Code-Update +./scripts/deploy-staging.sh --skip-migrations # Ohne Migrationen +``` + +**GitHub Secret erforderlich:** +- `STAGING_SSH_KEY` - SSH Private Key für `payload@37.24.237.181` + ## Dokumentation - `CLAUDE.md` - Diese Datei (Projekt-Übersicht) diff --git a/docs/anleitungen/TODO.md b/docs/anleitungen/TODO.md index 751a4d3..a99d7cc 100644 --- a/docs/anleitungen/TODO.md +++ b/docs/anleitungen/TODO.md @@ -17,8 +17,8 @@ |--------|------|---------| | [ ] | Media-Backup zu S3/MinIO | Backup | | [ ] | CDN-Integration (Cloudflare) | Caching | -| [ ] | CI/CD Pipeline erweitern (Lint/Test/Build) | DevOps | -| [ ] | Staging-Deployment | DevOps | +| [x] | CI/CD Pipeline erweitern (Lint/Test/Build) | DevOps | +| [x] | Staging-Deployment | DevOps | | [ ] | Memory-Problem lösen (Swap) | Infrastruktur | | [ ] | PM2 Cluster Mode testen | Infrastruktur | @@ -30,7 +30,7 @@ | [ ] | Email-Log Cleanup Cron | Data Retention | | [ ] | Dashboard-Widget für Email-Status | Admin UX | | [ ] | TypeScript Strict Mode | Tech Debt | -| [ ] | E2E Tests für kritische Flows | Testing | +| [x] | E2E Tests für kritische Flows | Testing | ### Dokumentation | Status | Task | @@ -117,11 +117,11 @@ ## Testing & CI/CD -- [ ] **CI/CD Pipeline** - - [ ] Automatisches Lint/Test/Build Workflow - - [ ] Staging-Deployment +- [x] **CI/CD Pipeline** *(erledigt: `.github/workflows/ci.yml`)* + - [x] Automatisches Lint/Test/Build Workflow + - [x] Staging-Deployment *(erledigt: `.github/workflows/deploy-staging.yml`)* -- [ ] **E2E Tests für kritische Flows** +- [x] **E2E Tests für kritische Flows** *(erledigt: `tests/e2e/`)* --- @@ -161,7 +161,7 @@ ## Technische Schulden - [ ] TypeScript Strict Mode aktivieren -- [ ] E2E Tests für kritische Flows +- [x] E2E Tests für kritische Flows - [ ] Code-Review für Security-relevante Bereiche - [ ] Performance-Audit der Datenbank-Queries diff --git a/scripts/deploy-staging.sh b/scripts/deploy-staging.sh new file mode 100755 index 0000000..3a51f45 --- /dev/null +++ b/scripts/deploy-staging.sh @@ -0,0 +1,165 @@ +#!/bin/bash +# ============================================================================= +# Staging Deployment Script +# ============================================================================= +# Usage: ./scripts/deploy-staging.sh [--skip-build] [--skip-migrations] +# +# This script deploys the current develop branch to the staging environment. +# Run this on the staging server (pl.c2sgmbh.de) or via SSH. +# ============================================================================= + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration +PROJECT_DIR="/home/payload/payload-cms" +BRANCH="${DEPLOY_BRANCH:-develop}" # Use env var or default to develop +LOG_FILE="/home/payload/logs/deploy-staging.log" + +# Parse arguments +SKIP_BUILD=false +SKIP_MIGRATIONS=false +for arg in "$@"; do + case $arg in + --skip-build) + SKIP_BUILD=true + shift + ;; + --skip-migrations) + SKIP_MIGRATIONS=true + shift + ;; + esac +done + +# Functions +log() { + echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE" +} + +success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" | tee -a "$LOG_FILE" +} + +warn() { + echo -e "${YELLOW}[WARNING]${NC} $1" | tee -a "$LOG_FILE" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE" + exit 1 +} + +# Ensure log directory exists +mkdir -p "$(dirname "$LOG_FILE")" + +echo "==============================================" +echo " Staging Deployment Script" +echo " Environment: pl.c2sgmbh.de" +echo "==============================================" +echo "" + +log "Starting staging deployment..." +log "Branch: $BRANCH" +log "Skip build: $SKIP_BUILD" +log "Skip migrations: $SKIP_MIGRATIONS" + +# Change to project directory +cd "$PROJECT_DIR" || error "Could not change to project directory: $PROJECT_DIR" +log "Working directory: $(pwd)" + +# Check current branch and stash changes +log "Checking git status..." +CURRENT_BRANCH=$(git branch --show-current) +if [ "$CURRENT_BRANCH" != "$BRANCH" ]; then + warn "Currently on branch '$CURRENT_BRANCH', switching to '$BRANCH'" +fi + +# Stash any local changes +if [ -n "$(git status --porcelain)" ]; then + warn "Stashing local changes..." + git stash --include-untracked +fi + +# Fetch and reset to origin +log "Fetching latest code from origin/$BRANCH..." +git fetch origin "$BRANCH" +git checkout "$BRANCH" +git reset --hard "origin/$BRANCH" +success "Code updated to latest origin/$BRANCH" + +# Show current commit +COMMIT_SHA=$(git rev-parse --short HEAD) +COMMIT_MSG=$(git log -1 --pretty=%s) +log "Current commit: $COMMIT_SHA - $COMMIT_MSG" + +# Install dependencies +log "Installing dependencies..." +pnpm install --frozen-lockfile +success "Dependencies installed" + +# Run migrations (unless skipped) +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 flag)" +fi + +# Build (unless skipped) +if [ "$SKIP_BUILD" = false ]; then + log "Building application..." + + # Stop PM2 to free memory for build + pm2 stop payload 2>/dev/null || true + pm2 stop queue-worker 2>/dev/null || true + + # Build with memory limit + NODE_OPTIONS="--max-old-space-size=2048" pnpm build + success "Build completed" +else + warn "Skipping build (--skip-build flag)" +fi + +# Restart services +log "Restarting PM2 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" + +# Wait for service to start +log "Waiting for service to start..." +sleep 5 + +# Health check +log "Running health check..." +HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/admin 2>/dev/null || echo "000") +if [ "$HTTP_STATUS" -ge 200 ] && [ "$HTTP_STATUS" -lt 400 ]; then + success "Health check passed (HTTP $HTTP_STATUS)" +else + warn "Health check returned HTTP $HTTP_STATUS" +fi + +# Show PM2 status +echo "" +log "PM2 Status:" +pm2 status + +# Summary +echo "" +echo "==============================================" +echo -e "${GREEN} Staging Deployment Complete!${NC}" +echo "==============================================" +echo " URL: https://pl.c2sgmbh.de" +echo " Admin: https://pl.c2sgmbh.de/admin" +echo " Commit: $COMMIT_SHA" +echo " Time: $(date)" +echo "==============================================" + +log "Deployment finished successfully"