160 lines
7.4 KiB
YAML
160 lines
7.4 KiB
YAML
name: CI/CD → Portainer
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
|
|
env:
|
|
REGISTRY: ${{ vars.REGISTRY }} # ex: git.matheussouza.com.br/gitadmin
|
|
BACKEND_IMAGE: saas-imobiliaria-backend
|
|
FRONTEND_IMAGE: saas-imobiliaria-frontend
|
|
|
|
jobs:
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# 1. BUILD & PUSH IMAGES
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
build:
|
|
name: Build & Push Docker Images
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set image tag
|
|
id: tag
|
|
run: echo "IMAGE_TAG=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
|
|
|
|
- name: Log in to Forgejo Container Registry
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ${{ vars.REGISTRY }}
|
|
username: ${{ secrets.REGISTRY_USER }}
|
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
# ── Backend ──────────────────────────────────────────────────────────────
|
|
- name: Build & push backend
|
|
uses: docker/build-push-action@v5
|
|
with:
|
|
context: ./backend
|
|
file: ./backend/Dockerfile.prod
|
|
push: true
|
|
tags: |
|
|
${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:${{ steps.tag.outputs.IMAGE_TAG }}
|
|
${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:latest
|
|
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:cache
|
|
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE }}:cache,mode=max
|
|
|
|
# ── Frontend ─────────────────────────────────────────────────────────────
|
|
- name: Build & push frontend
|
|
uses: docker/build-push-action@v5
|
|
with:
|
|
context: ./frontend
|
|
file: ./frontend/Dockerfile.prod
|
|
push: true
|
|
build-args: |
|
|
VITE_API_URL=https://${{ vars.DOMAIN }}/api
|
|
tags: |
|
|
${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:${{ steps.tag.outputs.IMAGE_TAG }}
|
|
${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:latest
|
|
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:cache
|
|
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}:cache,mode=max
|
|
|
|
outputs:
|
|
image_tag: ${{ steps.tag.outputs.IMAGE_TAG }}
|
|
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# 2. DEPLOY VIA PORTAINER WEBHOOK
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
deploy:
|
|
name: Deploy to Portainer
|
|
runs-on: ubuntu-latest
|
|
needs: build
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Trigger Portainer stack deploy
|
|
run: |
|
|
curl -s -o /dev/null -w "%{http_code}" \
|
|
--fail \
|
|
-X POST "${{ secrets.PORTAINER_WEBHOOK_URL }}" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"env": [
|
|
{"name": "IMAGE_TAG", "value": "${{ needs.build.outputs.image_tag }}"},
|
|
{"name": "REGISTRY", "value": "${{ vars.REGISTRY }}"},
|
|
{"name": "DOMAIN", "value": "${{ vars.DOMAIN }}"},
|
|
{"name": "POSTGRES_DB", "value": "${{ secrets.POSTGRES_DB }}"},
|
|
{"name": "POSTGRES_USER", "value": "${{ secrets.POSTGRES_USER }}"},
|
|
{"name": "POSTGRES_PASSWORD", "value": "${{ secrets.POSTGRES_PASSWORD }}"},
|
|
{"name": "SECRET_KEY", "value": "${{ secrets.SECRET_KEY }}"},
|
|
{"name": "JWT_SECRET_KEY", "value": "${{ secrets.JWT_SECRET_KEY }}"}
|
|
]
|
|
}'
|
|
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# 3. HEALTH CHECK (valida HTTPS + endpoints críticos)
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
healthcheck:
|
|
name: Validate HTTPS & Endpoints
|
|
runs-on: ubuntu-latest
|
|
needs: deploy
|
|
|
|
steps:
|
|
- name: Wait for containers to stabilize
|
|
run: sleep 30
|
|
|
|
- name: Check frontend HTTPS
|
|
run: |
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
--max-time 15 \
|
|
"https://${{ vars.DOMAIN }}")
|
|
echo "Frontend status: $STATUS"
|
|
[ "$STATUS" = "200" ] || (echo "Frontend HTTPS failed ($STATUS)" && exit 1)
|
|
|
|
- name: Check HTTP → HTTPS redirect
|
|
run: |
|
|
LOCATION=$(curl -s -o /dev/null -w "%{redirect_url}" \
|
|
--max-time 10 \
|
|
"http://${{ vars.DOMAIN }}")
|
|
echo "Redirect to: $LOCATION"
|
|
echo "$LOCATION" | grep -q "https://" || (echo "HTTP redirect missing" && exit 1)
|
|
|
|
- name: Check backend /health
|
|
run: |
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
--max-time 15 \
|
|
"https://${{ vars.DOMAIN }}/api/health")
|
|
echo "Backend health status: $STATUS"
|
|
[ "$STATUS" = "200" ] || (echo "Backend health check failed ($STATUS)" && exit 1)
|
|
|
|
- name: Check TLS certificate validity
|
|
run: |
|
|
EXPIRY=$(echo | openssl s_client -connect ${{ vars.DOMAIN }}:443 -servername ${{ vars.DOMAIN }} 2>/dev/null \
|
|
| openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
|
|
echo "Certificate expires: $EXPIRY"
|
|
# Fail if cert expires in less than 7 days
|
|
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s 2>/dev/null || date -j -f "%b %d %T %Y %Z" "$EXPIRY" +%s)
|
|
NOW_EPOCH=$(date +%s)
|
|
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
|
|
echo "Days until expiry: $DAYS_LEFT"
|
|
[ "$DAYS_LEFT" -gt 7 ] || (echo "Certificate expires in $DAYS_LEFT days!" && exit 1)
|
|
|
|
- name: Check API properties endpoint
|
|
run: |
|
|
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
--max-time 15 \
|
|
"https://${{ vars.DOMAIN }}/api/properties?limit=1")
|
|
echo "Properties API status: $STATUS"
|
|
[ "$STATUS" = "200" ] || (echo "Properties API failed ($STATUS)" && exit 1)
|
|
|
|
- name: Deployment validated successfully
|
|
run: |
|
|
echo "✅ All checks passed!"
|
|
echo " → https://${{ vars.DOMAIN }} is live and healthy"
|