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-; 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 -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