From 471c2c26034d5905251b7e195c56f12a137368c3 Mon Sep 17 00:00:00 2001 From: VPS 4 Builder Date: Wed, 17 Jun 2026 12:34:33 +0200 Subject: [PATCH] =?UTF-8?q?feat(ci):=20Passo=205=20=E2=80=94=20Build=20Onc?= =?UTF-8?q?e=20/=20Promote=20/=20Deploy=20no=20build.yml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - build.yml dividido em 2 jobs por ref: • build (só push na main): builda 1x e publica por COMMIT (: + :latest), sem APP_VERSION. • promote (só tag vX.Y.Z): re-tagueia : -> :vX.Y.Z via 'docker buildx imagetools create' (SEM rebuild), valida integridade (imagem do commit existe; digest :versao == :commit; front+back do mesmo commit) e deploya na VPS1. Falha se o commit não foi pushado. - Versão semântica nasce da TAG e é injetada no deploy em runtime (compose.prod). - Removidos frontend/constants.ts e update-version.js (fim do APP_VERSION manual). Co-Authored-By: Claude Opus 4.8 --- .gitea/workflows/build.yml | 99 +++++++++++++++++++++++++------------- frontend/constants.ts | 5 -- update-version.js | 24 --------- 3 files changed, 65 insertions(+), 63 deletions(-) delete mode 100644 frontend/constants.ts delete mode 100644 update-version.js diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 4683db1..f6c6f47 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -1,24 +1,28 @@ -name: build-and-push +name: build-and-promote -# Etapa 5: a cada push na main (ou tag vX.Y.Z), builda as imagens com o carimbo -# de versão e envia ao Container Registry do Gitea. NÃO faz deploy (Etapa 6 separada). +# Passo 5 — Build Once → Promote → Deploy: +# • push na main → job BUILD: builda 1x e publica por COMMIT (: + :latest). NÃO deploya. +# • tag vX.Y.Z → job PROMOTE: re-tagueia a imagem do commit como :vX.Y.Z (SEM rebuild), +# valida integridade e deploya na VPS1. +# A versão semântica NÃO entra no build: nasce da tag e é injetada no deploy em runtime +# (docker-compose.prod.yml). Não há mais APP_VERSION/constants.ts. on: push: branches: [main] tags: ['v*'] jobs: + # ───────────────────────── BUILD (só em push de branch) ───────────────────────── build: - # Roda direto no host VPS4 (runner registrado com label de host + acesso ao Docker). + if: startsWith(github.ref, 'refs/heads/') runs-on: self-hosted steps: - name: Checkout uses: actions/checkout@v4 - - name: Calcular versão/commit/data + - name: Vars (commit + data) id: vars run: | - echo "version=$(grep -oE 'V[0-9]+\.[0-9]+\.[0-9]+' frontend/constants.ts | head -1 | tr 'V' 'v')" >> "$GITHUB_OUTPUT" echo "commit=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" echo "build_time=$(date -u +'%Y-%m-%d %H:%M UTC')" >> "$GITHUB_OUTPUT" @@ -27,65 +31,92 @@ jobs: if [ -n "${{ secrets.REGISTRY_TOKEN }}" ]; then echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.clube67.com -u ruicesar --password-stdin else - echo "REGISTRY_TOKEN não definido — usando a credencial já presente no host (~/.docker/config.json)." + echo "REGISTRY_TOKEN ausente — usando a credencial já presente no host." fi - - name: Build (compose, com carimbo de versão) + - name: Build (carimba commit/data; versão vem da tag no deploy, não aqui) env: - # Fixa o nome do projeto p/ a imagem buildada bater com o passo de tag - # (em modo host o diretório do checkout muda o project name por padrão). COMPOSE_PROJECT_NAME: scoreodontocom - APP_VERSION: ${{ steps.vars.outputs.version }} GIT_COMMIT: ${{ steps.vars.outputs.commit }} BUILD_TIME: ${{ steps.vars.outputs.build_time }} APP_ENV: PROD run: docker compose build scoreodonto-backend scoreodonto-frontend - - name: Tag + push (com retry p/ 499 transitório) + - name: Publicar por COMMIT (: + :latest), com retry p/ 499 run: | set -e - V="${{ steps.vars.outputs.version }}" + REG=git.clube67.com/ruicesar C="${{ steps.vars.outputs.commit }}" for app in backend frontend; do SRC="scoreodontocom-scoreodonto-${app}:latest" - DST="git.clube67.com/ruicesar/scoreodonto-${app}" - docker tag "$SRC" "$DST:$V" + DST="${REG}/scoreodonto-${app}" docker tag "$SRC" "$DST:$C" docker tag "$SRC" "$DST:latest" - for tag in "$V" "$C" "latest"; do + for tag in "$C" "latest"; do n=0 until docker push "$DST:$tag"; do - n=$((n+1)); [ $n -ge 4 ] && { echo "push falhou após 4 tentativas: $DST:$tag"; exit 1; } + n=$((n+1)); [ $n -ge 4 ] && { echo "push falhou: $DST:$tag"; exit 1; } echo "retry $n (499 transitório) em $DST:$tag"; sleep 3 done done done + echo "Publicado: scoreodonto-{backend,frontend}:$C (+ :latest). Push NÃO deploya." - - name: Resumo - run: | - echo "Imagens publicadas:" - echo " git.clube67.com/ruicesar/scoreodonto-backend:${{ steps.vars.outputs.version }}" - echo " git.clube67.com/ruicesar/scoreodonto-frontend:${{ steps.vars.outputs.version }}" - echo "Deploy só acontece em TAG (job deploy)." - - # Deploy automático SÓ em tags vX.Y.Z (a tag = 'aprovado para produção'). - # Push na main NÃO deploya — só builda. Etapa 6. - deploy: - needs: build + # ─────────────────────── PROMOTE (só em tag vX.Y.Z) ─────────────────────── + promote: if: startsWith(github.ref, 'refs/tags/v') runs-on: self-hosted steps: - - name: Checkout + - name: Checkout (na tag) uses: actions/checkout@v4 - - name: Promover a tag para produção (VPS1) + - name: Vars (tag + commit) + id: vars + run: | + echo "version=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" + echo "commit=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" + + - name: Login no registry + run: | + if [ -n "${{ secrets.REGISTRY_TOKEN }}" ]; then + echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.clube67.com -u ruicesar --password-stdin + fi + + - name: Integridade — a imagem do commit existe? (nunca buildar na tag) run: | set -e - TAG="${GITHUB_REF_NAME}" - echo "Promovendo $TAG para a produção (VPS1)..." - # Garante que o mecanismo de deploy está atualizado na VPS1 + REG=git.clube67.com/ruicesar + C="${{ steps.vars.outputs.commit }}" + for app in backend frontend; do + if ! docker buildx imagetools inspect "${REG}/scoreodonto-${app}:$C" >/dev/null 2>&1; then + echo "ERRO: ${REG}/scoreodonto-${app}:$C não existe no registry." + echo " Faça 'git push origin main' (que builda o commit $C) ANTES de criar a tag." + exit 1 + fi + done + echo "OK: backend:$C e frontend:$C presentes — mesmo commit $C (front+back atômicos)." + + - name: Promover — re-tag commit → versão (SEM rebuild) + conferir digest + run: | + set -e + REG=git.clube67.com/ruicesar + C="${{ steps.vars.outputs.commit }}"; V="${{ steps.vars.outputs.version }}" + for app in backend frontend; do + DST="${REG}/scoreodonto-${app}" + docker buildx imagetools create -t "$DST:$V" "$DST:$C" + dC=$(docker buildx imagetools inspect "$DST:$C" --format '{{.Manifest.Digest}}') + dV=$(docker buildx imagetools inspect "$DST:$V" --format '{{.Manifest.Digest}}') + [ "$dC" = "$dV" ] || { echo "ERRO: digest divergiu em $app ($V=$dV != $C=$dC)"; exit 1; } + echo "$app: $V -> $C (digest $dV)" + done + echo "Promovido sem rebuild: a imagem :$V E a imagem :$C (mesmo artefato)." + + - name: Deploy na VPS1 (pull :versao + up --no-build; versao injetada em runtime) + run: | + set -e + V="${{ steps.vars.outputs.version }}" scp -o StrictHostKeyChecking=accept-new \ docker-compose.prod.yml deploy-prod.sh \ deploy@10.99.0.1:/home/deploy/stack/scoreodonto.com/ ssh -o StrictHostKeyChecking=accept-new deploy@10.99.0.1 \ - "/home/deploy/stack/scoreodonto.com/deploy-prod.sh $TAG" + "/home/deploy/stack/scoreodonto.com/deploy-prod.sh $V" diff --git a/frontend/constants.ts b/frontend/constants.ts deleted file mode 100644 index d5ff483..0000000 --- a/frontend/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -// LEGADO (Passo 4): a APLICAÇÃO não usa mais este valor — a versão exibida vem do backend -// (/api/version) em runtime via useAppVersion(). Permanece apenas porque o PIPELINE ainda o lê -// (update-version.js + .gitea/workflows/build.yml). Será eliminado no Passo 5, quando a versão -// passar a nascer da TAG git. Não referenciar no código do frontend. -export const APP_VERSION = 'V1.0.15'; diff --git a/update-version.js b/update-version.js deleted file mode 100644 index f16e1e2..0000000 --- a/update-version.js +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env node -// Increments the patch number in frontend/constants.ts before every build. -// Called via: node ../update-version.js (from inside frontend/) -const fs = require('fs'); -const path = require('path'); - -const constantsPath = path.join(__dirname, 'frontend', 'constants.ts'); -const content = fs.readFileSync(constantsPath, 'utf8'); - -const match = content.match(/APP_VERSION\s*=\s*['"]V(\d+)\.(\d+)\.(\d+)['"]/); -if (!match) { - console.error('[update-version] Could not parse APP_VERSION in constants.ts'); - process.exit(1); -} - -const [, major, minor, patch] = match; -const next = `V${major}.${minor}.${parseInt(patch, 10) + 1}`; -const updated = content.replace( - /APP_VERSION\s*=\s*['"]V\d+\.\d+\.\d+['"]/, - `APP_VERSION = '${next}'` -); - -fs.writeFileSync(constantsPath, updated, 'utf8'); -console.log(`[update-version] ${match[0].match(/V\d+\.\d+\.\d+/)[0]} → ${next}`);