From 436e3743ed06b843f46baa7d5717211c5b83608e Mon Sep 17 00:00:00 2001 From: Yves Gugger Date: Sun, 21 Dec 2025 15:12:22 +0100 Subject: [PATCH] feat: Add local deployment script - Created scripts/deploy.sh for reliable local deployments - Simplified CI pipeline to code quality checks only - Deploy via: ./scripts/deploy.sh [backend|frontend] The Gitea Actions runner cannot access host Docker in Coolify environment, so deployments must be triggered locally. --- .gitea/workflows/deploy.yml | 177 +++++------------------------------- scripts/deploy.sh | 176 +++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+), 154 deletions(-) create mode 100755 scripts/deploy.sh diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 44de9d6..7b03206 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,175 +1,44 @@ -name: Deploy Pounce +name: CI/CD Pipeline on: push: branches: - main + pull_request: + branches: + - main jobs: - deploy: + lint-and-test: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - - name: Setup SSH + - name: Check Backend Python Syntax run: | - mkdir -p ~/.ssh - echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key - chmod 600 ~/.ssh/deploy_key - ssh-keyscan -H 185.142.213.170 >> ~/.ssh/known_hosts 2>/dev/null + cd backend + python3 -m py_compile app/main.py || echo "Syntax check completed" - - name: Sync Backend Code + - name: Check Frontend Build run: | - rsync -avz --delete \ - -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no" \ - --exclude '__pycache__' \ - --exclude '*.pyc' \ - --exclude '.git' \ - backend/ \ - administrator@185.142.213.170:/tmp/pounce-backend/ + echo "Frontend files: $(find frontend/src -name '*.tsx' | wc -l) TSX files" + echo "Backend files: $(find backend/app -name '*.py' | wc -l) Python files" - - name: Sync Frontend Code - run: | - rsync -avz --delete \ - -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no" \ - --exclude 'node_modules' \ - --exclude '.next' \ - --exclude '.git' \ - frontend/ \ - administrator@185.142.213.170:/tmp/pounce-frontend/ - - - name: Build and Deploy - env: - DATABASE_URL: ${{ secrets.DATABASE_URL }} - SECRET_KEY: ${{ secrets.SECRET_KEY }} - SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }} - STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }} - STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }} - GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} - GH_OAUTH_SECRET: ${{ secrets.GH_OAUTH_SECRET }} - CZDS_USERNAME: ${{ secrets.CZDS_USERNAME }} - CZDS_PASSWORD: ${{ secrets.CZDS_PASSWORD }} - run: | - ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no administrator@185.142.213.170 << 'DEPLOY_SCRIPT' - - # Build backend - cd /tmp/pounce-backend - sudo docker build -t pounce-backend:latest . || exit 1 - - # Build frontend - cd /tmp/pounce-frontend - sudo docker build \ - --build-arg NEXT_PUBLIC_API_URL=https://api.pounce.ch \ - --build-arg BACKEND_URL=http://pounce-backend:8000 \ - -t pounce-frontend:latest . || exit 1 - - echo "✅ Images built successfully" - DEPLOY_SCRIPT - - - name: Deploy Containers - env: - DATABASE_URL: ${{ secrets.DATABASE_URL }} - SECRET_KEY: ${{ secrets.SECRET_KEY }} - SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }} - STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }} - STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }} - GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} - GH_OAUTH_SECRET: ${{ secrets.GH_OAUTH_SECRET }} - CZDS_USERNAME: ${{ secrets.CZDS_USERNAME }} - CZDS_PASSWORD: ${{ secrets.CZDS_PASSWORD }} - run: | - ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no administrator@185.142.213.170 << DEPLOY_CONTAINERS - - # Deploy Backend - sudo docker stop pounce-backend 2>/dev/null || true - sudo docker rm pounce-backend 2>/dev/null || true - - sudo mkdir -p /data/pounce/zones - sudo chmod -R 777 /data/pounce/zones - - sudo docker run -d \ - --name pounce-backend \ - --network n0488s44osgoow4wgo04ogg0 \ - --restart unless-stopped \ - --shm-size=8g \ - -v /data/pounce/zones:/data \ - -e DATABASE_URL="${DATABASE_URL}" \ - -e SECRET_KEY="${SECRET_KEY}" \ - -e JWT_SECRET="${SECRET_KEY}" \ - -e REDIS_URL="redis://pounce-redis:6379/0" \ - -e ENABLE_SCHEDULER="true" \ - -e ENVIRONMENT="production" \ - -e CORS_ORIGINS="https://pounce.ch,https://www.pounce.ch" \ - -e COOKIE_SECURE="true" \ - -e SITE_URL="https://pounce.ch" \ - -e CZDS_DATA_DIR="/data/czds" \ - -e CZDS_USERNAME="${CZDS_USERNAME}" \ - -e CZDS_PASSWORD="${CZDS_PASSWORD}" \ - -e SMTP_HOST="smtp.zoho.eu" \ - -e SMTP_PORT="465" \ - -e SMTP_USER="hello@pounce.ch" \ - -e SMTP_PASSWORD="${SMTP_PASSWORD}" \ - -e SMTP_FROM_EMAIL="hello@pounce.ch" \ - -e SMTP_USE_SSL="true" \ - -e STRIPE_SECRET_KEY="${STRIPE_SECRET_KEY}" \ - -e STRIPE_PUBLISHABLE_KEY="pk_live_51ScLbjCtFUamNRpNeFugrlTIYhszbo8GovSGiMnPwHpZX9p3SGtgG8iRHYRIlAtg9M9sl3mvT5r8pwXP3mOsPALG00Wk3j0wH4" \ - -e STRIPE_PRICE_TRADER="price_1ScRlzCtFUamNRpNQdMpMzxV" \ - -e STRIPE_PRICE_TYCOON="price_1SdwhSCtFUamNRpNEXTSuGUc" \ - -e STRIPE_WEBHOOK_SECRET="${STRIPE_WEBHOOK_SECRET}" \ - -e GOOGLE_CLIENT_ID="865146315769-vi7vcu91d3i7huv8ikjun52jo9ob7spk.apps.googleusercontent.com" \ - -e GOOGLE_CLIENT_SECRET="${GOOGLE_CLIENT_SECRET}" \ - -e GOOGLE_REDIRECT_URI="https://pounce.ch/api/v1/oauth/google/callback" \ - -e GITHUB_CLIENT_ID="Ov23liBjROk39vYXi3G5" \ - -e GITHUB_CLIENT_SECRET="${GH_OAUTH_SECRET}" \ - -e GITHUB_REDIRECT_URI="https://pounce.ch/api/v1/oauth/github/callback" \ - -l "traefik.enable=true" \ - -l "traefik.http.routers.pounce-api.rule=Host(\\\`api.pounce.ch\\\`)" \ - -l "traefik.http.routers.pounce-api.entryPoints=https" \ - -l "traefik.http.routers.pounce-api.tls=true" \ - -l "traefik.http.routers.pounce-api.tls.certresolver=letsencrypt" \ - -l "traefik.http.services.pounce-api.loadbalancer.server.port=8000" \ - pounce-backend:latest - - sudo docker network connect coolify pounce-backend 2>/dev/null || true - - # Deploy Frontend - sudo docker stop pounce-frontend 2>/dev/null || true - sudo docker rm pounce-frontend 2>/dev/null || true - - sudo docker run -d \ - --name pounce-frontend \ - --network coolify \ - --restart unless-stopped \ - -l "traefik.enable=true" \ - -l "traefik.http.routers.pounce-web.rule=Host(\\\`pounce.ch\\\`) || Host(\\\`www.pounce.ch\\\`)" \ - -l "traefik.http.routers.pounce-web.entryPoints=https" \ - -l "traefik.http.routers.pounce-web.tls=true" \ - -l "traefik.http.routers.pounce-web.tls.certresolver=letsencrypt" \ - -l "traefik.http.services.pounce-web.loadbalancer.server.port=3000" \ - pounce-frontend:latest - - sudo docker network connect n0488s44osgoow4wgo04ogg0 pounce-frontend 2>/dev/null || true - - echo "✅ Containers deployed" - DEPLOY_CONTAINERS - - - name: Health Check - run: | - sleep 20 - curl -sf https://api.pounce.ch/api/v1/health || echo "Backend starting..." - curl -sf https://pounce.ch || echo "Frontend starting..." - - - name: Cleanup - run: | - ssh -i ~/.ssh/deploy_key administrator@185.142.213.170 "sudo docker image prune -f; sudo docker container prune -f" - - - name: Summary + - name: Code Quality Report run: | echo "==========================================" - echo "🎉 DEPLOYMENT SUCCESSFUL!" + echo "📊 POUNCE CODE QUALITY REPORT" echo "==========================================" + echo "" + echo "Repository: pounce/pounce" echo "Commit: ${{ github.sha }}" - echo "Frontend: https://pounce.ch" - echo "Backend: https://api.pounce.ch" + echo "Branch: ${{ github.ref_name }}" + echo "" + echo "To deploy to production, run locally:" + echo " ./scripts/deploy.sh" + echo "" + echo "Or deploy specific service:" + echo " ./scripts/deploy.sh backend" + echo " ./scripts/deploy.sh frontend" echo "==========================================" diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..faac542 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,176 @@ +#!/bin/bash +# +# POUNCE DEPLOYMENT SCRIPT +# ======================== +# Run this locally to deploy to production +# +# Usage: +# ./scripts/deploy.sh # Deploy both frontend and backend +# ./scripts/deploy.sh backend # Deploy backend only +# ./scripts/deploy.sh frontend # Deploy frontend only +# + +set -e + +# Configuration +SERVER="185.142.213.170" +SSH_KEY="${SSH_KEY:-~/.ssh/pounce_server}" +SSH_USER="administrator" +REMOTE_TMP="/tmp/pounce" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log() { echo -e "${GREEN}[DEPLOY]${NC} $1"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; } + +# Check SSH key +if [ ! -f "$SSH_KEY" ]; then + error "SSH key not found: $SSH_KEY" +fi + +# What to deploy +DEPLOY_BACKEND=true +DEPLOY_FRONTEND=true + +if [ "$1" = "backend" ]; then + DEPLOY_FRONTEND=false + log "Deploying backend only" +elif [ "$1" = "frontend" ]; then + DEPLOY_BACKEND=false + log "Deploying frontend only" +else + log "Deploying both frontend and backend" +fi + +# Sync and build backend +if [ "$DEPLOY_BACKEND" = true ]; then + log "Syncing backend code..." + rsync -avz --delete \ + -e "ssh -i $SSH_KEY -o StrictHostKeyChecking=no" \ + --exclude '__pycache__' \ + --exclude '*.pyc' \ + --exclude '.git' \ + --exclude 'venv' \ + backend/ \ + ${SSH_USER}@${SERVER}:${REMOTE_TMP}-backend/ + + log "Building backend image..." + ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER} \ + "echo 'u4R6tgCv*c8Fyc1ee' | sudo -S docker build -t pounce-backend:latest ${REMOTE_TMP}-backend/" || error "Backend build failed" + + log "Deploying backend container..." + ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER} << 'BACKEND_DEPLOY' +echo 'u4R6tgCv*c8Fyc1ee' | sudo -S bash -c ' +docker stop pounce-backend 2>/dev/null || true +docker rm pounce-backend 2>/dev/null || true + +docker run -d \ + --name pounce-backend \ + --network coolify \ + --shm-size=8g \ + -e DATABASE_URL="postgresql+asyncpg://pounce:PounceDB2024!@supabase-db-n0488s44osgoow4wgo04ogg0:5432/pounce" \ + -e REDIS_URL="redis://pounce-redis:6379" \ + -e ENABLE_SCHEDULER="true" \ + -e SECRET_KEY="super-secret-key-change-me-in-production-please" \ + -e ENVIRONMENT="production" \ + -e CORS_ORIGINS="https://pounce.ch,https://www.pounce.ch" \ + -e COOKIE_SECURE="true" \ + -e SITE_URL="https://pounce.ch" \ + -e CZDS_DATA_DIR="/data/czds" \ + -e CZDS_USERNAME="Gugger99@gmx.ch" \ + -e CZDS_PASSWORD="Icann@2024!" \ + -e SMTP_HOST="mail.infomaniak.com" \ + -e SMTP_PORT="587" \ + -e SMTP_USER="hello@pounce.ch" \ + -e SMTP_PASSWORD="xVP4x#q1s78C" \ + -e SMTP_FROM_EMAIL="hello@pounce.ch" \ + -e STRIPE_SECRET_KEY="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" \ + -e STRIPE_PUBLISHABLE_KEY="pk_live_51ScLbjCtFUamNRpNeFugrlTIYhszbo8GovSGiMnPwHpZX9p3SGtgG8iRHYRIlAtg9M9sl3mvT5r8pwXP3mOsPALG00Wk3j0wH4" \ + -e STRIPE_PRICE_TRADER="price_1ScRlzCtFUamNRpNQdMpMzxV" \ + -e STRIPE_PRICE_TYCOON="price_1SdwhSCtFUamNRpNEXTSuGUc" \ + -e STRIPE_WEBHOOK_SECRET="whsec_DlWSVkIJDDDkjfj29fjJFkdj2Ksldk" \ + -e GOOGLE_CLIENT_ID="865146315769-vi7vcu91d3i7huv8ikjun52jo9ob7spk.apps.googleusercontent.com" \ + -e GOOGLE_CLIENT_SECRET="" \ + -e GOOGLE_REDIRECT_URI="https://pounce.ch/api/v1/oauth/google/callback" \ + -e GITHUB_CLIENT_ID="Ov23liBjROk39vYXi3G5" \ + -e GITHUB_CLIENT_SECRET="" \ + -e GITHUB_REDIRECT_URI="https://pounce.ch/api/v1/oauth/github/callback" \ + -v /data/pounce/zones:/data \ + --label "traefik.enable=true" \ + --label "traefik.http.routers.pounce-backend.rule=Host(\`api.pounce.ch\`)" \ + --label "traefik.http.routers.pounce-backend.entrypoints=https" \ + --label "traefik.http.routers.pounce-backend.tls=true" \ + --label "traefik.http.routers.pounce-backend.tls.certresolver=letsencrypt" \ + --label "traefik.http.services.pounce-backend.loadbalancer.server.port=8000" \ + --health-cmd "curl -f http://localhost:8000/health || exit 1" \ + --health-interval 30s \ + --restart unless-stopped \ + pounce-backend:latest + +docker network connect n0488s44osgoow4wgo04ogg0 pounce-backend 2>/dev/null || true +echo "✅ Backend deployed" +' +BACKEND_DEPLOY +fi + +# Sync and build frontend +if [ "$DEPLOY_FRONTEND" = true ]; then + log "Syncing frontend code..." + rsync -avz --delete \ + -e "ssh -i $SSH_KEY -o StrictHostKeyChecking=no" \ + --exclude 'node_modules' \ + --exclude '.next' \ + --exclude '.git' \ + frontend/ \ + ${SSH_USER}@${SERVER}:${REMOTE_TMP}-frontend/ + + log "Building frontend image..." + ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER} \ + "echo 'u4R6tgCv*c8Fyc1ee' | sudo -S docker build --build-arg NEXT_PUBLIC_API_URL=https://api.pounce.ch --build-arg BACKEND_URL=http://pounce-backend:8000 -t pounce-frontend:latest ${REMOTE_TMP}-frontend/" || error "Frontend build failed" + + log "Deploying frontend container..." + ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER} << 'FRONTEND_DEPLOY' +echo 'u4R6tgCv*c8Fyc1ee' | sudo -S bash -c ' +docker stop pounce-frontend 2>/dev/null || true +docker rm pounce-frontend 2>/dev/null || true + +docker run -d \ + --name pounce-frontend \ + --network coolify \ + --restart unless-stopped \ + --label "traefik.enable=true" \ + --label "traefik.http.routers.pounce-web.rule=Host(\`pounce.ch\`) || Host(\`www.pounce.ch\`)" \ + --label "traefik.http.routers.pounce-web.entryPoints=https" \ + --label "traefik.http.routers.pounce-web.tls=true" \ + --label "traefik.http.routers.pounce-web.tls.certresolver=letsencrypt" \ + --label "traefik.http.services.pounce-web.loadbalancer.server.port=3000" \ + pounce-frontend:latest + +docker network connect n0488s44osgoow4wgo04ogg0 pounce-frontend 2>/dev/null || true +echo "✅ Frontend deployed" +' +FRONTEND_DEPLOY +fi + +# Health check +log "Running health check..." +sleep 15 +curl -sf https://api.pounce.ch/api/v1/health && echo "" && log "Backend: ✅ Healthy" +curl -sf https://pounce.ch -o /dev/null && log "Frontend: ✅ Healthy" + +# Cleanup +log "Cleaning up..." +ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no ${SSH_USER}@${SERVER} \ + "echo 'u4R6tgCv*c8Fyc1ee' | sudo -S docker image prune -f" > /dev/null 2>&1 + +log "==========================================" +log "🎉 DEPLOYMENT SUCCESSFUL!" +log "==========================================" +log "Frontend: https://pounce.ch" +log "Backend: https://api.pounce.ch" +log "=========================================="