- Gitea Actions workflow now syncs repo to server, builds images, restarts containers, and runs health checks - Removed all hardcoded secrets from scripts/deploy.sh - Added CI/CD documentation and ignored .env.deploy NOTE: Existing secrets previously committed must be rotated.
195 lines
7.1 KiB
YAML
195 lines
7.1 KiB
YAML
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 "=========================================="
|