diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..9d10827 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,69 @@ +# Deploy automático do app wedding. +# +# A cada push na branch principal (ou disparo manual), conecta por SSH no host +# de produção e roda `make deploy`, que faz git reset --hard na branch + rebuild +# do app wedding (docker compose up -d --build). +# +# Por que SSH (e não rodar docker direto no runner): +# os arquivos .env (segredos do app) ficam só no host, fora do git. O host já +# tem o repo configurado, então o deploy é só atualizar o código e subir. +# +# Segredos necessários (Gitea: repo > Settings > Actions > Secrets): +# DEPLOY_HOST IP ou hostname do host de produção +# DEPLOY_USER usuário SSH (precisa rodar docker + ter o repo clonado) +# DEPLOY_SSH_KEY chave PRIVADA SSH (conteúdo completo, incl. cabeçalhos) +# DEPLOY_PATH caminho absoluto do repo no host (ex.: /opt/wedding-app) +# Variáveis opcionais (Gitea: ... > Variables): +# DEPLOY_PORT porta SSH (default 22) + +name: Deploy + +on: + push: + branches: + - main + paths: + # Só dispara quando algo que afeta o app/infra muda. Edição de docs + # (CLAUDE.md, README) não redeploya. + - "infra/wedding_photo/**" + - "Makefile" + - ".gitea/workflows/deploy.yml" + workflow_dispatch: {} + +# Cancela um deploy em andamento se um novo push chegar. +concurrency: + group: deploy-wedding + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Configura chave SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + PORT="${{ vars.DEPLOY_PORT }}" + ssh-keyscan -p "${PORT:-22}" -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts 2>/dev/null + + - name: Deploy via SSH + run: | + PORT="${{ vars.DEPLOY_PORT }}" + ssh -i ~/.ssh/deploy_key -p "${PORT:-22}" \ + "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" \ + "set -e; cd '${{ secrets.DEPLOY_PATH }}'; make deploy BRANCH=main" + + - name: Health check + run: | + PORT="${{ vars.DEPLOY_PORT }}" + # Espera o container ficar saudável e confere /health internamente. + ssh -i ~/.ssh/deploy_key -p "${PORT:-22}" \ + "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" \ + "for i in \$(seq 1 15); do \ + if docker exec wedding_app sh -c 'wget -qO- http://localhost:3000/api/health' >/dev/null 2>&1; then \ + echo 'app OK'; exit 0; \ + fi; \ + echo \"aguardando app... (\$i/15)\"; sleep 4; \ + done; \ + echo 'app NAO respondeu'; docker logs --tail 50 wedding_app; exit 1" diff --git a/CLAUDE.md b/CLAUDE.md index af9417d..1ab2a83 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -274,6 +274,31 @@ docker exec -u git -it gitea gitea admin user change-password \ --username lronetto --password "$NEWPW" ``` +### Deploy automático (CI/CD) +Workflow `.gitea/workflows/deploy.yml`. A cada push na branch principal +(`main`) que toque `infra/wedding_photo/**`, `Makefile` ou o +próprio workflow — ou disparo manual (`workflow_dispatch`) — o runner conecta +por **SSH no host** e roda `make deploy` (git reset --hard + `up-wedding` com +build), seguido de um health check em `/api/health`. + +**Por que SSH e não docker direto no runner**: os `.env` (segredos do app) +ficam só no host, fora do git. O host já tem o repo configurado; o deploy só +atualiza o código e sobe. + +Setup (1ª vez): +1. No host, garanta o repo clonado (ex.: `/opt/wedding-app`) e um usuário SSH + que rode docker e tenha acesso ao repo. +2. Gere um par de chaves SSH e adicione a **pública** no `~/.ssh/authorized_keys` + desse usuário. +3. Em Gitea → repo → **Settings → Actions → Secrets**, crie: + - `DEPLOY_HOST` — IP/hostname do host + - `DEPLOY_USER` — usuário SSH + - `DEPLOY_SSH_KEY` — a chave **privada** (conteúdo completo) + - `DEPLOY_PATH` — caminho do repo no host (ex.: `/opt/wedding-app`) +4. (Opcional) **Variables**: `DEPLOY_PORT` se o SSH não for 22. + +`make deploy` aceita `BRANCH=` pra sobrescrever a branch deployada. + --- ## 9. Gotchas conhecidos @@ -352,7 +377,7 @@ O `extra_hosts: host-gateway` pra `media.{DOMAIN_BASE}` faz o container resolver Branches relevantes: - `claude/wedding-qrcode-photos-C0PQt` (Cloudflare original) - `claude/docker-vps-migration` (1ª migração Docker Node) -- `claude/python-backend` (atual; Python + restructure 3 stacks) +- `main` (atual; Python + restructure 3 stacks — renomeada de `claude/python-backend`) --- diff --git a/Makefile b/Makefile index a3e12d3..0533da2 100644 --- a/Makefile +++ b/Makefile @@ -3,9 +3,10 @@ down-main down-gitea down-wedding \ logs-main logs-gitea logs-wedding \ pull-main pull-gitea pull-wedding \ - rebuild-wedding + rebuild-wedding deploy NET := infra-net +BRANCH ?= main DC_MAIN := docker compose -f infra/main/docker-compose.yml --env-file infra/main/.env DC_GITEA := docker compose -f infra/gitea/docker-compose.yml --env-file infra/gitea/.env DC_WEDDING := docker compose -f infra/wedding_photo/docker-compose.yml --env-file infra/wedding_photo/.env @@ -22,6 +23,7 @@ help: @echo " make up-gitea Sobe só o gitea" @echo " make up-wedding Build + up do app wedding" @echo " make rebuild-wedding Force rebuild do app wedding" + @echo " make deploy git reset --hard + up-wedding (usado pelo CI)" @echo " make down-{main,gitea,wedding}" @echo " make logs-{main,gitea,wedding}" @echo " make pull-{main,gitea,wedding}" @@ -54,6 +56,16 @@ rebuild-wedding: network $(DC_WEDDING) build --no-cache $(DC_WEDDING) up -d +# Deploy automático: atualiza o código e sobe só o app wedding com build. +# Chamado pelo workflow .gitea/workflows/deploy.yml via SSH no host. +# BRANCH pode ser sobrescrito: `make deploy BRANCH=main` +deploy: network + git fetch --all --prune + git checkout $(BRANCH) + git reset --hard origin/$(BRANCH) + $(DC_WEDDING) up -d --build + $(DC_WEDDING) ps + down-main: $(DC_MAIN) down