feat: add staging deployment workflow and script

- 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 <noreply@anthropic.com>
This commit is contained in:
Martin Porwoll 2025-12-14 12:37:25 +00:00
parent 05fba7f1d7
commit e3c7d92121
4 changed files with 395 additions and 8 deletions

189
.github/workflows/deploy-staging.yml vendored Normal file
View file

@ -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

View file

@ -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)

View file

@ -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

165
scripts/deploy-staging.sh Executable file
View file

@ -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"