chore: retire deploy-prod.yml, document the real PM2 deploy path
deploy-prod.yml targeted a remote SSH deploy server via PROD_SERVER_HOST/PROD_SERVER_USER/PROD_SERVER_PATH/PROD_SSH_PRIVATE_KEY, but none of those secrets were ever configured (repo and production environment secret lists are both empty) and every historical run of the workflow failed as a result. The actual production server is this machine: unified-server.js runs directly out of this working directory via PM2. Removed the dead pipeline and documented the real manual deploy steps (pull, rebuild frontend, sync backend deps, pm2 restart, verify) in CLAUDE.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
537
.github/workflows/deploy-prod.yml
vendored
537
.github/workflows/deploy-prod.yml
vendored
@@ -1,537 +0,0 @@
|
||||
name: Deploy to Production
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version tag to deploy (e.g., v1.2.3)'
|
||||
required: false
|
||||
skip_tests:
|
||||
description: 'Skip E2E tests (emergency deploy)'
|
||||
required: false
|
||||
default: 'false'
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
NODE_VERSION: '22'
|
||||
PROD_SERVER_HOST: ${{ secrets.PROD_SERVER_HOST }}
|
||||
PROD_SERVER_USER: ${{ secrets.PROD_SERVER_USER }}
|
||||
PROD_SERVER_PATH: ${{ secrets.PROD_SERVER_PATH }}
|
||||
PROD_APP_URL: ${{ secrets.PROD_APP_URL }}
|
||||
|
||||
jobs:
|
||||
# ============================================
|
||||
# Pre-Deploy Checks
|
||||
# ============================================
|
||||
pre-deploy-checks:
|
||||
name: Pre-Deploy Checks
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
should_deploy: ${{ steps.check.outputs.should_deploy }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Determine version
|
||||
id: version
|
||||
run: |
|
||||
if [ -n "${{ github.event.inputs.version }}" ]; then
|
||||
VERSION="${{ github.event.inputs.version }}"
|
||||
elif [[ "${{ github.ref }}" == refs/tags/* ]]; then
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
else
|
||||
VERSION="prod-$(date +'%Y%m%d-%H%M%S')-${GITHUB_SHA::7}"
|
||||
fi
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Deploying version: $VERSION"
|
||||
|
||||
- name: Check deployment conditions
|
||||
id: check
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ] || [[ "${{ github.ref }}" == refs/tags/* ]]; then
|
||||
echo "should_deploy=true" >> $GITHUB_OUTPUT
|
||||
echo "Deployment approved: manual trigger or tag push"
|
||||
else
|
||||
echo "should_deploy=true" >> $GITHUB_OUTPUT
|
||||
echo "Deployment approved: version tag push"
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# Unit Tests
|
||||
# ============================================
|
||||
unit-tests:
|
||||
name: Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre-deploy-checks
|
||||
if: needs.pre-deploy-checks.outputs.should_deploy == 'true'
|
||||
services:
|
||||
mariadb:
|
||||
image: mariadb:10.11
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: testpassword
|
||||
MYSQL_DATABASE: tilbudgivern_test
|
||||
MYSQL_USER: testuser
|
||||
MYSQL_PASSWORD: testpassword
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: >-
|
||||
--health-cmd="mysqladmin ping -h localhost"
|
||||
--health-interval=10s
|
||||
--health-timeout=5s
|
||||
--health-retries=5
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install backend dependencies
|
||||
working-directory: backend
|
||||
run: npm ci
|
||||
|
||||
- name: Wait for MariaDB
|
||||
run: |
|
||||
while ! mysqladmin ping -h"127.0.0.1" --silent; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- name: Run backend unit tests
|
||||
working-directory: backend
|
||||
env:
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_PORT: 3306
|
||||
DB_USER: testuser
|
||||
DB_PASSWORD: testpassword
|
||||
DB_NAME: tilbudgivern_test
|
||||
NODE_ENV: test
|
||||
run: npm test -- --coverage
|
||||
|
||||
- name: Upload coverage report
|
||||
uses: actions/upload-artifact@v7
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: backend-coverage
|
||||
path: backend/coverage
|
||||
retention-days: 3
|
||||
|
||||
# ============================================
|
||||
# Security Scan
|
||||
# ============================================
|
||||
security-scan:
|
||||
name: Security Scan
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre-deploy-checks
|
||||
if: needs.pre-deploy-checks.outputs.should_deploy == 'true'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd frontend && npm ci
|
||||
cd ../backend && npm ci
|
||||
|
||||
- name: Run npm audit (frontend)
|
||||
working-directory: frontend
|
||||
run: npm audit --audit-level=critical
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run npm audit (backend)
|
||||
working-directory: backend
|
||||
run: npm audit --audit-level=critical
|
||||
continue-on-error: true
|
||||
|
||||
- name: Check for secrets in code
|
||||
run: |
|
||||
# Check for potential hardcoded secrets
|
||||
if grep -rE "(sk-[a-zA-Z0-9]{20,}|password\s*=\s*['\"][^'\"]+['\"])" --include="*.js" --include="*.ts" backend/src/ frontend/src/ 2>/dev/null | grep -v "process.env" | grep -v ".test.js" | grep -v "example"; then
|
||||
echo "Warning: Potential hardcoded secrets found"
|
||||
else
|
||||
echo "No hardcoded secrets detected"
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# Build for Production
|
||||
# ============================================
|
||||
build:
|
||||
name: Build Production Package
|
||||
runs-on: ubuntu-latest
|
||||
needs: [pre-deploy-checks, unit-tests, security-scan]
|
||||
if: needs.pre-deploy-checks.outputs.should_deploy == 'true'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm ci
|
||||
cd frontend && npm ci
|
||||
cd ../backend && npm ci
|
||||
|
||||
- name: Build frontend for production
|
||||
working-directory: frontend
|
||||
env:
|
||||
CI: false
|
||||
REACT_APP_ENV: production
|
||||
REACT_APP_VERSION: ${{ needs.pre-deploy-checks.outputs.version }}
|
||||
NODE_ENV: production
|
||||
run: npm run build
|
||||
|
||||
- name: Create deployment package
|
||||
run: |
|
||||
mkdir -p deploy-package
|
||||
cp -r frontend/build deploy-package/frontend-build
|
||||
cp -r backend deploy-package/backend
|
||||
cp ecosystem.config.js deploy-package/
|
||||
cp package.json deploy-package/
|
||||
rm -rf deploy-package/backend/node_modules
|
||||
rm -rf deploy-package/backend/__tests__
|
||||
rm -rf deploy-package/backend/src/__tests__
|
||||
rm -rf deploy-package/backend/coverage
|
||||
echo "${{ needs.pre-deploy-checks.outputs.version }}" > deploy-package/VERSION
|
||||
echo "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" > deploy-package/DEPLOY_TIME
|
||||
|
||||
- name: Upload deployment package
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: deploy-package-prod
|
||||
path: deploy-package
|
||||
retention-days: 3
|
||||
|
||||
# ============================================
|
||||
# Deploy to Production
|
||||
# ============================================
|
||||
deploy:
|
||||
name: Deploy to Production Server
|
||||
runs-on: ubuntu-latest
|
||||
needs: [pre-deploy-checks, build]
|
||||
environment:
|
||||
name: production
|
||||
url: ${{ env.PROD_APP_URL }}
|
||||
steps:
|
||||
- name: Download deployment package
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: deploy-package-prod
|
||||
path: deploy-package
|
||||
|
||||
- name: Setup SSH
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.PROD_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Add server to known hosts
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keyscan -H ${{ env.PROD_SERVER_HOST }} >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Create backup on server
|
||||
run: |
|
||||
ssh ${{ env.PROD_SERVER_USER }}@${{ env.PROD_SERVER_HOST }} << 'BACKUP_SCRIPT'
|
||||
set -e
|
||||
DEPLOY_PATH="${{ env.PROD_SERVER_PATH }}"
|
||||
BACKUP_PATH="$DEPLOY_PATH/backups/$(date +'%Y%m%d-%H%M%S')"
|
||||
|
||||
echo "=== Creating backup ==="
|
||||
mkdir -p $BACKUP_PATH
|
||||
|
||||
# Backup current release info
|
||||
if [ -L "$DEPLOY_PATH/current" ]; then
|
||||
CURRENT_VERSION=$(readlink $DEPLOY_PATH/current | xargs basename)
|
||||
echo "$CURRENT_VERSION" > $BACKUP_PATH/previous_version
|
||||
|
||||
# Backup database (if mysqldump available)
|
||||
if command -v mysqldump &> /dev/null; then
|
||||
source $DEPLOY_PATH/shared/.env
|
||||
mysqldump -h${DB_HOST:-localhost} -u${DB_USER} -p${DB_PASSWORD} ${DB_NAME} > $BACKUP_PATH/database.sql 2>/dev/null || echo "Database backup skipped"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Keep only last 5 backups
|
||||
cd $DEPLOY_PATH/backups
|
||||
ls -t | tail -n +6 | xargs -r rm -rf
|
||||
|
||||
echo "=== Backup complete ==="
|
||||
BACKUP_SCRIPT
|
||||
|
||||
- name: Deploy to production server
|
||||
run: |
|
||||
VERSION="${{ needs.pre-deploy-checks.outputs.version }}"
|
||||
|
||||
# Create deployment directory
|
||||
ssh ${{ env.PROD_SERVER_USER }}@${{ env.PROD_SERVER_HOST }} "mkdir -p ${{ env.PROD_SERVER_PATH }}/releases/$VERSION"
|
||||
|
||||
# Upload deployment package
|
||||
scp -r deploy-package/* ${{ env.PROD_SERVER_USER }}@${{ env.PROD_SERVER_HOST }}:${{ env.PROD_SERVER_PATH }}/releases/$VERSION/
|
||||
|
||||
# Run deployment script on server
|
||||
ssh ${{ env.PROD_SERVER_USER }}@${{ env.PROD_SERVER_HOST }} << DEPLOY_SCRIPT
|
||||
set -e
|
||||
|
||||
DEPLOY_PATH="${{ env.PROD_SERVER_PATH }}"
|
||||
VERSION="$VERSION"
|
||||
RELEASE_PATH="\$DEPLOY_PATH/releases/\$VERSION"
|
||||
|
||||
echo "=== Deploying version: \$VERSION ==="
|
||||
|
||||
# Install backend dependencies
|
||||
cd \$RELEASE_PATH/backend
|
||||
npm ci --production --ignore-scripts
|
||||
|
||||
# Copy environment file
|
||||
cp \$DEPLOY_PATH/shared/.env \$RELEASE_PATH/backend/.env
|
||||
|
||||
# Setup frontend directory structure
|
||||
mkdir -p \$RELEASE_PATH/frontend
|
||||
mv \$RELEASE_PATH/frontend-build \$RELEASE_PATH/frontend/build
|
||||
|
||||
# Update symlink atomically
|
||||
ln -sfn \$RELEASE_PATH \$DEPLOY_PATH/current_new
|
||||
mv -Tf \$DEPLOY_PATH/current_new \$DEPLOY_PATH/current
|
||||
|
||||
# Restart PM2 gracefully
|
||||
cd \$DEPLOY_PATH/current
|
||||
pm2 reload ecosystem.config.js --env production || pm2 start ecosystem.config.js --env production
|
||||
|
||||
# Wait for server to be ready
|
||||
sleep 5
|
||||
|
||||
# Cleanup old releases (keep last 10)
|
||||
cd \$DEPLOY_PATH/releases
|
||||
ls -t | tail -n +11 | xargs -r rm -rf
|
||||
|
||||
echo "=== Deployment complete ==="
|
||||
DEPLOY_SCRIPT
|
||||
|
||||
# ============================================
|
||||
# Post-Deploy Verification
|
||||
# ============================================
|
||||
verify:
|
||||
name: Verify Production Deployment
|
||||
runs-on: ubuntu-latest
|
||||
needs: [pre-deploy-checks, deploy]
|
||||
steps:
|
||||
- name: Wait for server startup
|
||||
run: sleep 20
|
||||
|
||||
- name: Health check with retries
|
||||
run: |
|
||||
MAX_RETRIES=10
|
||||
RETRY_COUNT=0
|
||||
WAIT_TIME=10
|
||||
|
||||
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
|
||||
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" ${{ env.PROD_APP_URL }}/api/health || echo "000")
|
||||
|
||||
if [ "$HTTP_STATUS" = "200" ]; then
|
||||
echo "Health check passed!"
|
||||
break
|
||||
fi
|
||||
|
||||
RETRY_COUNT=$((RETRY_COUNT + 1))
|
||||
echo "Health check failed (HTTP $HTTP_STATUS), retry $RETRY_COUNT/$MAX_RETRIES..."
|
||||
sleep $WAIT_TIME
|
||||
done
|
||||
|
||||
if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then
|
||||
echo "Health check failed after $MAX_RETRIES retries"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: API endpoint verification
|
||||
run: |
|
||||
echo "Verifying API endpoints..."
|
||||
|
||||
# Health endpoint
|
||||
echo "Testing /api/health..."
|
||||
curl -sf ${{ env.PROD_APP_URL }}/api/health || exit 1
|
||||
|
||||
# Materials endpoint
|
||||
echo "Testing /api/materials..."
|
||||
curl -sf ${{ env.PROD_APP_URL }}/api/materials > /dev/null || echo "Warning: materials endpoint issue"
|
||||
|
||||
# Smart packages endpoint
|
||||
echo "Testing /api/smart-packages/..."
|
||||
curl -sf "${{ env.PROD_APP_URL }}/api/smart-packages/" > /dev/null || echo "Warning: smart-packages endpoint issue"
|
||||
|
||||
# Quotes endpoint
|
||||
echo "Testing /api/quotes/completed..."
|
||||
curl -sf "${{ env.PROD_APP_URL }}/api/quotes/completed" > /dev/null || echo "Warning: quotes endpoint issue"
|
||||
|
||||
echo "API verification complete!"
|
||||
|
||||
- name: Frontend accessibility check
|
||||
run: |
|
||||
echo "Checking frontend accessibility..."
|
||||
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" ${{ env.PROD_APP_URL }}/)
|
||||
|
||||
if [ "$HTTP_STATUS" = "200" ]; then
|
||||
echo "Frontend accessible!"
|
||||
else
|
||||
echo "Warning: Frontend returned HTTP $HTTP_STATUS"
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# E2E Smoke Tests (Production)
|
||||
# ============================================
|
||||
smoke-tests:
|
||||
name: Production Smoke Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: verify
|
||||
if: github.event.inputs.skip_tests != 'true'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install test dependencies
|
||||
working-directory: tests
|
||||
run: npm ci
|
||||
|
||||
- name: Install Playwright browsers
|
||||
working-directory: tests
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Run smoke tests
|
||||
working-directory: tests
|
||||
env:
|
||||
PLAYWRIGHT_BASE_URL: ${{ env.PROD_APP_URL }}
|
||||
run: |
|
||||
# Run only critical smoke tests, not full suite
|
||||
npx playwright test --project=chromium --grep="@smoke|@critical" --reporter=html || \
|
||||
npx playwright test --project=chromium --reporter=html --max-failures=3
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload test report
|
||||
uses: actions/upload-artifact@v7
|
||||
if: always()
|
||||
# Artifact upload must never fail the job (storage quota can be full)
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: prod-smoke-test-report
|
||||
path: tests/playwright-report
|
||||
retention-days: 3
|
||||
|
||||
- name: Upload test screenshots
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
# Artifact upload must never fail the job (storage quota can be full)
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: prod-test-screenshots
|
||||
path: tests/test-results
|
||||
retention-days: 3
|
||||
|
||||
# ============================================
|
||||
# Rollback Job (On Verification Failure)
|
||||
# ============================================
|
||||
rollback:
|
||||
name: Rollback Production
|
||||
runs-on: ubuntu-latest
|
||||
if: failure() && needs.verify.result == 'failure'
|
||||
needs: [pre-deploy-checks, deploy, verify]
|
||||
steps:
|
||||
- name: Setup SSH
|
||||
uses: webfactory/ssh-agent@v0.9.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.PROD_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Add server to known hosts
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keyscan -H ${{ env.PROD_SERVER_HOST }} >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Rollback to previous version
|
||||
run: |
|
||||
ssh ${{ env.PROD_SERVER_USER }}@${{ env.PROD_SERVER_HOST }} << 'ROLLBACK_SCRIPT'
|
||||
set -e
|
||||
DEPLOY_PATH="${{ env.PROD_SERVER_PATH }}"
|
||||
|
||||
echo "=== Starting rollback ==="
|
||||
|
||||
# Find previous release
|
||||
CURRENT=$(readlink $DEPLOY_PATH/current | xargs basename)
|
||||
PREVIOUS=$(ls -t $DEPLOY_PATH/releases | grep -v "$CURRENT" | head -1)
|
||||
|
||||
if [ -z "$PREVIOUS" ]; then
|
||||
echo "No previous release found for rollback!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Rolling back from $CURRENT to $PREVIOUS"
|
||||
|
||||
# Update symlink
|
||||
ln -sfn $DEPLOY_PATH/releases/$PREVIOUS $DEPLOY_PATH/current_rollback
|
||||
mv -Tf $DEPLOY_PATH/current_rollback $DEPLOY_PATH/current
|
||||
|
||||
# Restart PM2
|
||||
cd $DEPLOY_PATH/current
|
||||
pm2 reload ecosystem.config.js --env production
|
||||
|
||||
echo "=== Rollback complete ==="
|
||||
ROLLBACK_SCRIPT
|
||||
|
||||
- name: Notify rollback
|
||||
run: |
|
||||
echo "::error::ROLLBACK EXECUTED!"
|
||||
echo "Production deployment failed verification and was rolled back."
|
||||
echo "Please investigate the issue before redeploying."
|
||||
|
||||
# ============================================
|
||||
# Deployment Summary
|
||||
# ============================================
|
||||
summary:
|
||||
name: Deployment Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: [pre-deploy-checks, unit-tests, security-scan, build, deploy, verify, smoke-tests]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Create 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-Deploy Checks | ${{ needs.pre-deploy-checks.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Unit Tests | ${{ needs.unit-tests.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Security Scan | ${{ needs.security-scan.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Build | ${{ needs.build.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Deploy | ${{ needs.deploy.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Verify | ${{ needs.verify.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Smoke Tests | ${{ needs.smoke-tests.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Version:** ${{ needs.pre-deploy-checks.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**URL:** ${{ env.PROD_APP_URL }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Artifacts" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Backend coverage report" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Deployment package" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Smoke test report" >> $GITHUB_STEP_SUMMARY
|
||||
19
CLAUDE.md
19
CLAUDE.md
@@ -123,14 +123,29 @@ The application uses a unified server (`backend/unified-server.js`) that:
|
||||
- Admin endpoints: `/api/admin/logs`
|
||||
|
||||
## Deployment
|
||||
|
||||
**This machine is the production server.** There is no separate deploy target: `backend/unified-server.js` runs directly out of this git working directory via PM2, serving the built frontend from `frontend/build/`. There is no CI/CD pipeline that deploys automatically on merge — deploying means running these steps here, on this host, after `main` has the commit you want live.
|
||||
|
||||
```bash
|
||||
git checkout main && git pull # Get the commit you want live
|
||||
cd frontend && npm run build # Rebuild the served static frontend
|
||||
cd ../backend && npm ci --omit=dev # Sync backend deps (skip if unchanged)
|
||||
pm2 restart tilbudgivern-unified # Restart the running process
|
||||
pm2 logs tilbudgivern-unified --lines 30 --nostream # Confirm a clean startup (no errors)
|
||||
curl -sf http://localhost:4032/api/health # Should return {"status":"ok",...}
|
||||
curl -sI https://tilbudsgiveren.alw.dk # Should return HTTP/2 200
|
||||
```
|
||||
|
||||
Other useful commands:
|
||||
```bash
|
||||
pm2 restart tilbudgivern-unified # Restart after code changes
|
||||
pm2 logs tilbudgivern-unified # View live logs
|
||||
pm2 status # Check process status
|
||||
pm2 logs tilbudgivern-unified # Tail live logs
|
||||
```
|
||||
|
||||
Daily database backups configured via cron at 02:00, retention: 7 days.
|
||||
|
||||
Note: `.github/workflows/deploy-prod.yml` (an SSH/rsync-to-a-remote-server pipeline) was removed — it targeted a `PROD_SERVER_HOST`/`PROD_SERVER_USER`/`PROD_SERVER_PATH`/`PROD_SSH_PRIVATE_KEY` setup that was never configured (no repo or `production`-environment secrets ever existed), and every historical run of it failed. If a real separate deploy target is ever set up, that pipeline's steps (versioned releases dir, atomic symlink swap, pre-deploy DB backup, health-check retries, smoke tests) are a reasonable starting point to resurrect from git history.
|
||||
|
||||
## Danish Terminology
|
||||
- Tilbud = Quote/Offer
|
||||
- Tømrer = Carpenter
|
||||
|
||||
Reference in New Issue
Block a user