ci: replace portainer webhook with ssh deploy (portainer free)
Some checks are pending
CI/CD → Deploy via SSH / Build & Push Docker Images (push) Waiting to run
CI/CD → Deploy via SSH / Deploy via SSH (push) Blocked by required conditions
CI/CD → Deploy via SSH / Validate HTTPS & Endpoints (push) Blocked by required conditions

This commit is contained in:
MatheusAlves96 2026-04-21 00:09:30 -03:00
parent dcd18a07e6
commit b0eb12c17d
2 changed files with 113 additions and 68 deletions

View file

@ -1,4 +1,4 @@
name: CI/CD → Portainer
name: CI/CD → Deploy via SSH
on:
push:
@ -6,7 +6,7 @@ on:
- main
env:
REGISTRY: ${{ vars.REGISTRY }} # ex: git.matheussouza.com.br/gitadmin
REGISTRY: ${{ vars.REGISTRY }}
BACKEND_IMAGE: saas-imobiliaria-backend
FRONTEND_IMAGE: saas-imobiliaria-frontend
@ -36,7 +36,6 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# ── Backend ──────────────────────────────────────────────────────────────
- name: Build & push backend
uses: docker/build-push-action@v5
with:
@ -49,7 +48,6 @@ jobs:
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:
@ -68,10 +66,10 @@ jobs:
image_tag: ${{ steps.tag.outputs.IMAGE_TAG }}
# ────────────────────────────────────────────────────────────────────────────
# 2. DEPLOY VIA PORTAINER WEBHOOK
# 2. DEPLOY VIA SSH
# ────────────────────────────────────────────────────────────────────────────
deploy:
name: Deploy to Portainer
name: Deploy via SSH
runs-on: ubuntu-latest
needs: build
@ -79,27 +77,66 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Trigger Portainer stack deploy
- name: Setup SSH key
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 }}"}
]
}'
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -p ${{ vars.SSH_PORT || '22' }} ${{ vars.SSH_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
- name: Copy docker-compose.prod.yml to server
run: |
scp -i ~/.ssh/deploy_key \
-P ${{ vars.SSH_PORT || '22' }} \
docker-compose.prod.yml \
${{ vars.SSH_USER }}@${{ vars.SSH_HOST }}:/opt/saas-imobiliaria/docker-compose.prod.yml
- name: Deploy on server
run: |
ssh -i ~/.ssh/deploy_key \
-p ${{ vars.SSH_PORT || '22' }} \
${{ vars.SSH_USER }}@${{ vars.SSH_HOST }} \
bash -s << 'ENDSSH'
set -e
DEPLOY_DIR="/opt/saas-imobiliaria"
mkdir -p "$DEPLOY_DIR"
cd "$DEPLOY_DIR"
# Write .env for docker compose
cat > .env << EOF
IMAGE_TAG=${{ needs.build.outputs.image_tag }}
REGISTRY=${{ vars.REGISTRY }}
DOMAIN=${{ vars.DOMAIN }}
POSTGRES_DB=${{ secrets.POSTGRES_DB }}
POSTGRES_USER=${{ secrets.POSTGRES_USER }}
POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }}
SECRET_KEY=${{ secrets.SECRET_KEY }}
JWT_SECRET_KEY=${{ secrets.JWT_SECRET_KEY }}
EOF
# Log in to registry on the server
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
docker login ${{ vars.REGISTRY }} \
-u ${{ secrets.REGISTRY_USER }} \
--password-stdin
# Pull new images
docker compose -f docker-compose.prod.yml pull
# Rolling restart (zero-downtime: db stays up)
docker compose -f docker-compose.prod.yml up -d --remove-orphans
# Clean up old images
docker image prune -f
echo "Deploy concluído: ${{ needs.build.outputs.image_tag }}"
ENDSSH
# ────────────────────────────────────────────────────────────────────────────
# 3. HEALTH CHECK (valida HTTPS + endpoints críticos)
# 3. HEALTH CHECK — valida HTTPS + endpoints críticos
# ────────────────────────────────────────────────────────────────────────────
healthcheck:
name: Validate HTTPS & Endpoints
@ -113,48 +150,45 @@ jobs:
- 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)
--max-time 15 "https://${{ vars.DOMAIN }}")
echo "Frontend: $STATUS"
[ "$STATUS" = "200" ] || (echo "❌ Frontend HTTPS falhou ($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)
--max-time 10 "http://${{ vars.DOMAIN }}")
echo "Redirect: $LOCATION"
echo "$LOCATION" | grep -q "https://" || (echo "❌ Redirect HTTP→HTTPS ausente" && 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)
--max-time 15 "https://${{ vars.DOMAIN }}/api/health")
echo "Backend health: $STATUS"
[ "$STATUS" = "200" ] || (echo "❌ Backend health check falhou ($STATUS)" && exit 1)
- name: Check TLS certificate validity
run: |
EXPIRY=$(echo | openssl s_client -connect ${{ vars.DOMAIN }}:443 -servername ${{ vars.DOMAIN }} 2>/dev/null \
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)
echo "Certificado expira: $EXPIRY"
EXPIRY_EPOCH=$(date -d "$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)
echo "Dias restantes: $DAYS_LEFT"
[ "$DAYS_LEFT" -gt 7 ] || (echo "❌ Certificado expira em $DAYS_LEFT dias!" && 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)
--max-time 15 "https://${{ vars.DOMAIN }}/api/properties?limit=1")
echo "Properties API: $STATUS"
[ "$STATUS" = "200" ] || (echo "❌ Properties API falhou ($STATUS)" && exit 1)
- name: Deployment validated successfully
- name: All checks passed
run: |
echo "✅ All checks passed!"
echo " → https://${{ vars.DOMAIN }} is live and healthy"
echo "✅ Deploy validado com sucesso!"
echo " → https://${{ vars.DOMAIN }} está no ar"