Files
ESPDocs/.gitea/workflows/release.yml
T
Lucas RayanandClaude Opus 5.5 5fba78308c
Release / Plano (reaproveitar a imagem?) (push) Successful in 7s
Release / ci (push) Skipped
Release / Publica a imagem (linux/arm64) (push) Successful in 21s
Release / Cria a release no Gitea (push) Skipped
Release / Deploy no Coolify (push) Skipped
fix(release): chama o deploy do Coolify com POST
O Coolify 4.5 trocou /api/v1/deploy para POST e responde 405 ao GET, o que
derrubou o primeiro deploy automático.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:26:51 -03:00

185 lines
7.9 KiB
YAML

name: Release
# Publica a imagem no registro de containers do Gitea depois de um merge em main,
# e de novo com tag semver quando uma tag v* é enviada. Quando o latest passa a
# apontar para outra imagem, o job deploy pede ao Coolify para implantá-la.
# Precisa também dos secrets COOLIFY_URL e COOLIFY_TOKEN no repositório.
# Precisa do secret de usuário REGISTRY_TOKEN (token do Gitea com write:package).
on:
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
concurrency:
group: release
cancel-in-progress: false
env:
REGISTRY: git.lucasrguerra.dev.br
jobs:
# A imagem só depende dos arquivos que entram no build, não da documentação nem
# da configuração de ferramentas. O push da branch já a construiu, testou e
# publicou como inputs-<hash>; quando essa tag existe (o caso normal de um
# merge, uma tag num commit validado, um merge só de docs), não há o que
# construir: as tags novas apontam para o manifesto existente.
plano:
name: Plano (reaproveitar a imagem?)
runs-on: ubuntu-latest
outputs:
inputs: ${{ steps.plano.outputs.inputs }}
reuse: ${{ steps.plano.outputs.reuse }}
image: ${{ steps.plano.outputs.image }}
steps:
- uses: actions/checkout@v4
- id: plano
run: scripts/plano-imagem.sh "$REGISTRY" "${{ gitea.repository }}" >> "$GITHUB_OUTPUT"
# Nada é publicado sem a suíte completa passar antes. Pulada quando a imagem é
# reaproveitada: o mesmo conteúdo já passou por ela.
ci:
needs: plano
if: ${{ needs.plano.outputs.reuse != 'true' }}
uses: ./.gitea/workflows/ci.yml
# Um workflow reutilizável não herda os secrets sozinho: sem isto o login no
# registro recebe senha vazia ("Password required").
secrets: inherit
publicar:
name: Publica a imagem (linux/arm64)
needs: [plano, ci]
# ci é pulado, e não bem-sucedido, quando a imagem é reaproveitada.
if: ${{ !cancelled() && needs.plano.result == 'success' && (needs.ci.result == 'success' || needs.plano.outputs.reuse == 'true') }}
# O único runner é arm64 e o servidor de destino (Oracle) também, então a
# imagem é construída nativamente, sem emulação.
runs-on: ubuntu-24.04-arm
outputs:
mudou: ${{ steps.depois.outputs.mudou }}
steps:
- uses: actions/checkout@v4
# Se o ci chamado acima acabou de publicar a imagem (push direto na main),
# o plano lá de cima está desatualizado: confere de novo antes de construir.
- id: replano
run: scripts/plano-imagem.sh "$REGISTRY" "${{ gitea.repository }}" >> "$GITHUB_OUTPUT"
- uses: docker/setup-buildx-action@v4
- name: Autentica no registro
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ gitea.actor }}
password: ${{ secrets.REGISTRY_TOKEN }}
# O deploy só acontece se o latest passar a apontar para outra imagem.
# Um merge só de docs ou uma tag reaponta o latest para o mesmo manifesto,
# e reiniciar o container nesse caso seria trabalho repetido.
- name: Digest do latest antes
id: antes
run: echo "digest=$(.gitea/scripts/digest-latest.sh "$REGISTRY" "${{ gitea.repository }}")" >> "$GITHUB_OUTPUT"
- name: Metadados
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ needs.plano.outputs.image }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest
type=sha,format=long
- name: Build e push
if: ${{ steps.replano.outputs.reuse != 'true' }}
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/arm64
push: true
tags: |
${{ steps.meta.outputs.tags }}
${{ needs.plano.outputs.image }}:inputs-${{ needs.plano.outputs.inputs }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ needs.plano.outputs.image }}:buildcache
cache-to: type=registry,ref=${{ needs.plano.outputs.image }}:buildcache,mode=max
# Mesmo manifesto, nomes novos: nenhuma camada é lida nem escrita.
- name: Aplica as tags à imagem existente
if: ${{ steps.replano.outputs.reuse == 'true' }}
run: |
tags=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< '${{ steps.meta.outputs.json }}')
docker buildx imagetools create $tags "${{ needs.plano.outputs.image }}:inputs-${{ needs.plano.outputs.inputs }}"
- name: Digest do latest depois
id: depois
run: |
antes="${{ steps.antes.outputs.digest }}"
depois=$(.gitea/scripts/digest-latest.sh "$REGISTRY" "${{ gitea.repository }}")
echo "latest: ${antes:-nenhum} -> $depois"
[ -n "$depois" ] && [ "$depois" != "$antes" ] && mudou=true || mudou=false
echo "mudou=$mudou" >> "$GITHUB_OUTPUT"
# Cria a entrada em "Releases" quando a execução é de uma tag v*. Roda depois
# de publicar a imagem, com as notas da seção do CHANGELOG.md.
release:
name: Cria a release no Gitea
needs: [plano, publicar]
# Explícito, como o publicar: o ci acima é pulado quando a imagem é
# reaproveitada, e o success() implícito pularia este job também.
if: ${{ !cancelled() && needs.publicar.result == 'success' && startsWith(gitea.ref, 'refs/tags/v') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Release
env:
TAG: ${{ gitea.ref_name }}
TOKEN: ${{ gitea.token }}
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
IMAGE: ${{ needs.plano.outputs.image }}
run: |
scripts/release-notes.sh "$TAG" "$IMAGE" > notes.md
pre=false; case "$TAG" in *-*) pre=true ;; esac
jq -n --arg tag "$TAG" --arg name "ESPDocs ${TAG#v}" --rawfile body notes.md --argjson pre "$pre" \
'{tag_name:$tag, name:$name, body:$body, prerelease:$pre}' > release.json
curl -fsS -X POST "$API/releases" -H "Authorization: token $TOKEN" \
-H 'Content-Type: application/json' -d @release.json
# Avisa o Coolify para puxar o latest novo e espera o deploy terminar, para
# que uma falha apareça aqui, e não só no painel do Coolify. Roda só quando o
# latest mudou, ou num disparo manual (que força o deploy).
# Precisa dos secrets COOLIFY_URL e COOLIFY_TOKEN no repositório.
deploy:
name: Deploy no Coolify
needs: publicar
if: ${{ !cancelled() && needs.publicar.result == 'success' && (needs.publicar.outputs.mudou == 'true' || gitea.event_name == 'workflow_dispatch') }}
runs-on: ubuntu-latest
steps:
- name: Deploy de ESPDocs
env:
COOLIFY_URL: ${{ secrets.COOLIFY_URL }}
COOLIFY_TOKEN: ${{ secrets.COOLIFY_TOKEN }}
APP: 62wmq51cgbcz9nyfwizmytku
run: |
if [ -z "$COOLIFY_URL" ] || [ -z "$COOLIFY_TOKEN" ]; then
echo "faltam os secrets COOLIFY_URL e COOLIFY_TOKEN no repositório"; exit 1
fi
auth="Authorization: Bearer $COOLIFY_TOKEN"
dep=$(curl -fsS -X POST -H "$auth" "$COOLIFY_URL/api/v1/deploy?uuid=$APP&force=false" | jq -r '.deployments[0].deployment_uuid')
[ -n "$dep" ] && [ "$dep" != null ] || { echo "o Coolify não devolveu um deployment"; exit 1; }
echo "deployment $dep"
# Até 15 minutos: o pull da imagem arm64 e o healthcheck cabem com folga.
for _ in $(seq 1 90); do
st=$(curl -fsS -H "$auth" "$COOLIFY_URL/api/v1/deployments/$dep" | jq -r '.status')
echo "status: $st"
case "$st" in
finished) exit 0 ;;
failed|cancelled*) exit 1 ;;
esac
sleep 10
done
echo "o deploy não terminou em 15 minutos"; exit 1