name: Deploy Pounce (Auto) on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install deploy tooling run: | apt-get update apt-get install -y --no-install-recommends openssh-client rsync ca-certificates - name: Setup SSH key run: | mkdir -p ~/.ssh echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key ssh-keyscan -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts 2>/dev/null - name: Sync repository to server run: | rsync -az --delete \ -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes" \ --exclude ".git" \ --exclude "frontend/node_modules" \ --exclude "frontend/.next" \ --exclude "**/__pycache__" \ --exclude "**/*.pyc" \ ./ \ "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_PATH }}/" - name: Deploy (build + restart + health check) env: DEPLOY_SUDO_PASSWORD: ${{ secrets.DEPLOY_SUDO_PASSWORD }} # App secrets (used to generate backend env file on server) 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 "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" << 'DEPLOY_EOF' set -euo pipefail # Use sudo non-interactively (password supplied via env) sudo_cmd() { printf '%s\n' "$DEPLOY_SUDO_PASSWORD" | sudo -S "$@" } # Ensure dirs sudo_cmd mkdir -p /data/pounce/env /data/pounce/zones sudo_cmd chmod -R 755 /data/pounce || true # Generate backend env file from pipeline-provided secrets (never echo values) sudo_cmd python3 - <<'PY' import os from pathlib import Path env = { # Core "ENVIRONMENT": "production", "ENABLE_SCHEDULER": "true", "COOKIE_SECURE": "true", "CORS_ORIGINS": "https://pounce.ch,https://www.pounce.ch", "SITE_URL": "https://pounce.ch", "FRONTEND_URL": "https://pounce.ch", # Data dirs "CZDS_DATA_DIR": "/data/czds", "SWITCH_DATA_DIR": "/data/switch", "ZONE_RETENTION_DAYS": "3", # DB/Redis "DATABASE_URL": os.environ["DATABASE_URL"], "REDIS_URL": "redis://pounce-redis:6379/0", # Auth "SECRET_KEY": os.environ["SECRET_KEY"], "JWT_SECRET": os.environ["SECRET_KEY"], # SMTP "SMTP_HOST": "smtp.zoho.eu", "SMTP_PORT": "465", "SMTP_USER": "hello@pounce.ch", "SMTP_PASSWORD": os.environ["SMTP_PASSWORD"], "SMTP_FROM_EMAIL": "hello@pounce.ch", "SMTP_FROM_NAME": "pounce", "SMTP_USE_TLS": "false", "SMTP_USE_SSL": "true", # Stripe "STRIPE_SECRET_KEY": os.environ["STRIPE_SECRET_KEY"], "STRIPE_PUBLISHABLE_KEY": "pk_live_51ScLbjCtFUamNRpNeFugrlTIYhszbo8GovSGiMnPwHpZX9p3SGtgG8iRHYRIlAtg9M9sl3mvT5r8pwXP3mOsPALG00Wk3j0wH4", "STRIPE_PRICE_TRADER": "price_1ScRlzCtFUamNRpNQdMpMzxV", "STRIPE_PRICE_TYCOON": "price_1SdwhSCtFUamNRpNEXTSuGUc", "STRIPE_WEBHOOK_SECRET": os.environ["STRIPE_WEBHOOK_SECRET"], # OAuth "GOOGLE_CLIENT_ID": "865146315769-vi7vcu91d3i7huv8ikjun52jo9ob7spk.apps.googleusercontent.com", "GOOGLE_CLIENT_SECRET": os.environ["GOOGLE_CLIENT_SECRET"], "GOOGLE_REDIRECT_URI": "https://pounce.ch/api/v1/oauth/google/callback", "GITHUB_CLIENT_ID": "Ov23liBjROk39vYXi3G5", "GITHUB_CLIENT_SECRET": os.environ["GH_OAUTH_SECRET"], "GITHUB_REDIRECT_URI": "https://pounce.ch/api/v1/oauth/github/callback", # CZDS "CZDS_USERNAME": os.environ["CZDS_USERNAME"], "CZDS_PASSWORD": os.environ["CZDS_PASSWORD"], } lines = [] for k, v in env.items(): if v is None: continue lines.append(f"{k}={v}") path = Path("/data/pounce/env/backend.env") path.write_text("\n".join(lines) + "\n") PY # Build images from synced repo cd "${{ secrets.DEPLOY_PATH }}" sudo_cmd docker build -t pounce-backend:latest backend sudo_cmd docker build \ --build-arg NEXT_PUBLIC_API_URL=https://api.pounce.ch \ --build-arg BACKEND_URL=http://pounce-backend:8000 \ -t pounce-frontend:latest \ frontend # Deploy backend sudo_cmd docker stop pounce-backend 2>/dev/null || true sudo_cmd docker rm pounce-backend 2>/dev/null || true sudo_cmd docker run -d \ --name pounce-backend \ --network coolify \ --restart unless-stopped \ --shm-size=8g \ --env-file /data/pounce/env/backend.env \ -v /data/pounce/zones:/data \ -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_cmd docker network connect n0488s44osgoow4wgo04ogg0 pounce-backend 2>/dev/null || true # Deploy frontend sudo_cmd docker stop pounce-frontend 2>/dev/null || true sudo_cmd docker rm pounce-frontend 2>/dev/null || true sudo_cmd 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_cmd docker network connect n0488s44osgoow4wgo04ogg0 pounce-frontend 2>/dev/null || true # Health check sleep 15 curl -sf https://api.pounce.ch/api/v1/health >/dev/null curl -sf https://pounce.ch >/dev/null # Cleanup sudo_cmd docker image prune -f >/dev/null 2>&1 || true echo "✅ Deploy finished" DEPLOY_EOF - name: Summary run: | echo "==========================================" echo "🎉 AUTO DEPLOY COMPLETED" echo "==========================================" echo "Commit: ${{ github.sha }}" echo "Backend: https://api.pounce.ch" echo "Web: https://pounce.ch" echo "=========================================="