init commit
All checks were successful
build-api-image / build (push) Successful in 51s

This commit is contained in:
Antonio Lopes dos Santos
2026-08-04 16:52:02 -03:00
commit afec302c3e
11594 changed files with 1636728 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
GITEA_INSTANCE_URL=http://192.168.0.41:3001
GITEA_RUNNER_REGISTRATION_TOKEN=
RUNNER_NAME=conectasus-runner-01
RUNNER_LABELS=ubuntu-latest:docker://gitea/runner-images:ubuntu-latest
ACT_RUNNER_IMAGE=gitea/act_runner:0.6.1
TZ=America/Sao_Paulo

106
.gitea/runner/README.md Normal file
View File

@@ -0,0 +1,106 @@
# Runner do Gitea (act_runner)
Runner que executa a esteira [`build-api-image.yml`](../workflows/build-api-image.yml).
Sobe em um servidor **separado** do Gitea — o Gitea fica em `192.168.0.41:3001`
e este compose roda na máquina que vai construir as imagens.
## Instalação
```bash
cp .env.example .env
```
Pegue o token de registro no Gitea e cole em `GITEA_RUNNER_REGISTRATION_TOKEN`:
| Escopo do runner | Onde pegar o token |
| --- | --- |
| Só o repositório conectasus | Repo › Settings › Actions › Runners › Create new runner |
| Toda a organização `rkm` | Org › Settings › Actions › Runners |
| Instância inteira | Site Administration › Actions › Runners |
Depois:
```bash
docker compose up -d
docker compose logs -f
```
O runner aparece como online em Settings › Actions › Runners do Gitea. O token de
registro é usado uma única vez; a credencial permanente fica em `./data/.runner`.
## O que o compose faz
| Item | Motivo |
| --- | --- |
| `/var/run/docker.sock` montado | O `act_runner` usa o daemon do host para criar os containers de job **e** repassa o socket para dentro deles. É isso que faz o `docker build` da esteira funcionar. |
| `./data` | Guarda o registro do runner. Apagar essa pasta força novo registro. |
| Label `ubuntu-latest:docker://gitea/runner-images:ubuntu-latest` | Casa com o `runs-on: ubuntu-latest` do workflow. Essa imagem (0,57 GB) já traz `docker`, `git`, `node`, `bash` e `curl` — tudo que a esteira usa. A variante `-full` tem 17,6 GB e não é necessária. |
Como o Gitea está em outro servidor, tudo que aponta para ele é configuração:
`GITEA_INSTANCE_URL` e o token vêm do `.env`, nada fixo no compose. Se as
variáveis obrigatórias faltarem, o `docker compose up` falha na hora com a
mensagem em vez de subir um runner quebrado.
## Pré-requisitos no servidor do runner
**1. Alcançar o Gitea.** O runner e os containers de job precisam falar com
`192.168.0.41:3001` (API do Actions e registry de imagens):
```bash
curl -s http://192.168.0.41:3001/api/v1/version
```
**2. Confiar no registry via HTTP.** O registry não tem TLS, e o `docker push` da
esteira roda no daemon do host (por causa do socket montado). Então a
configuração é do host, não do compose — em `/etc/docker/daemon.json`:
```json
{ "insecure-registries": ["192.168.0.41:3001"] }
```
Se o arquivo já existir, **acrescente ao array** em vez de sobrescrever. Depois:
```bash
systemctl reload docker
docker info | grep -A5 'Insecure Registries'
```
Use `reload`, não `restart`. `insecure-registries` é recarregável por SIGHUP, e
`restart` derruba todos os containers do host quando `live-restore` está
desligado (confira com `docker info --format '{{.LiveRestoreEnabled}}'`).
Sem isso o push falha com `http: server gave HTTP response to HTTPS client`.
**3. Espaço em disco.** Cada build gera uma imagem da API com o `vendor/`
completo. Convém uma limpeza periódica:
```bash
docker image prune -af --filter "until=168h"
```
## Mudando o runner de máquina
O compose não tem nada preso à máquina: o Gitea é alcançado por
`GITEA_INSTANCE_URL`, que vem do `.env`. Para mover o runner basta subir o
compose no servidor novo e registrar de novo.
Na fase inicial de desenvolvimento é natural rodar na própria máquina de dev.
Duas ressalvas quando esse host também roda outros containers:
- Um `restart` do daemon Docker (necessário se você usar `restart` em vez de
`reload` para o `insecure-registries`) derruba tudo que estiver no host.
- Os builds competem por disco e CPU com os containers já existentes.
Ao migrar para o servidor do Gitea, o registro anterior deve ser removido em
Settings › Actions › Runners, e a pasta `./data` do host antigo descartada.
`GITEA_INSTANCE_URL` pode continuar apontando para o IP — não precisa virar
`localhost`.
## Notas
- Se o Gitea for acessado por hostname em vez de IP e o servidor do runner não
resolver esse nome, acrescente `extra_hosts` ao serviço no compose.
- Por padrão o runner executa **um job por vez**. Para paralelismo, monte um
`config.yaml` com `runner.capacity` e aponte `CONFIG_FILE` para ele.
- O socket do Docker montado dá ao runner controle total sobre o daemon do host.
Use um servidor dedicado a CI, não um que rode containers de produção.

View File

@@ -0,0 +1,22 @@
services:
act-runner:
image: ${ACT_RUNNER_IMAGE:-gitea/act_runner:0.6.1}
container_name: ${RUNNER_NAME:-conectasus-runner-01}
restart: unless-stopped
environment:
GITEA_INSTANCE_URL: ${GITEA_INSTANCE_URL:?informe a URL do Gitea}
GITEA_RUNNER_REGISTRATION_TOKEN: NJtScAqvccbNQJfdFnS8udtLsjSITXJq5bzmi41d
GITEA_RUNNER_NAME: ${RUNNER_NAME:-conectasus-runner-01}
GITEA_RUNNER_LABELS: ${RUNNER_LABELS:-ubuntu-latest:docker://gitea/runner-images:ubuntu-latest}
TZ: ${TZ:-America/Sao_Paulo}
volumes:
- runner-data:/data
- /var/run/docker.sock:/var/run/docker.sock
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
volumes:
runner-data:

21
.gitea/runner/compose.yml Normal file
View File

@@ -0,0 +1,21 @@
name: gitea-runner
services:
act-runner:
image: ${ACT_RUNNER_IMAGE:-gitea/act_runner:0.6.1}
container_name: ${RUNNER_NAME:-conectasus-runner-01}
restart: unless-stopped
environment:
GITEA_INSTANCE_URL: ${GITEA_INSTANCE_URL:?defina GITEA_INSTANCE_URL no .env}
GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_REGISTRATION_TOKEN:?defina GITEA_RUNNER_REGISTRATION_TOKEN no .env}
GITEA_RUNNER_NAME: ${RUNNER_NAME:-conectasus-runner-01}
GITEA_RUNNER_LABELS: ${RUNNER_LABELS:-ubuntu-latest:docker://gitea/runner-images:ubuntu-latest}
TZ: ${TZ:-America/Sao_Paulo}
volumes:
- ./data:/data
- /var/run/docker.sock:/var/run/docker.sock
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"

View File

@@ -0,0 +1,453 @@
name: build-api-image
on:
push:
branches:
# Branch = ambiente. Um merge aceito aqui constroi e implanta no ambiente
# correspondente (mapeamento no step de metadados).
- develop # -> teste
- 'release-*' # -> homologacao
- feat_CI_CD_TEST # -> teste (branch de validacao da propria esteira)
tags:
- '2.*'
jobs:
build:
runs-on: ubuntu-latest
env:
REGISTRY: ${{ vars.REGISTRY_HOST || '192.168.0.41:3001' }}
IMAGE_NAME: ${{ vars.API_IMAGE_NAME || 'rkm/conectasus-api' }}
BASE_IMAGE: ${{ vars.API_BASE_IMAGE || 'tirkm/v2-application:6' }}
# O contexto e a raiz do application: o build precisa enxergar api/ e
# frontend/ ao mesmo tempo, entao Dockerfile e docker/ vivem na raiz.
CONTEXT_DIR: ${{ vars.CONTEXT_DIR || '.' }}
DOCKERFILE: ${{ vars.DOCKERFILE || 'Dockerfile' }}
# Subdiretorio da API, usado nas checagens que leem arquivos do repo
# (composer.json, migrations) — nao confundir com o contexto do build.
API_DIR: ${{ vars.API_DIR || 'api' }}
PUSH_LATEST: ${{ vars.PUSH_LATEST || 'true' }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Definir metadados do build
id: meta
run: |
set -euo pipefail
if [ "${GITHUB_REF}" != "${GITHUB_REF#refs/tags/}" ]; then
IS_TAG=true
else
IS_TAG=false
fi
VERSION=$(echo "${GITHUB_REF_NAME}" | tr '/' '-')
IMAGE="${REGISTRY}/${IMAGE_NAME}"
# Branch -> ambiente. A tag de release constroi a imagem, mas nao
# implanta nem aplica migrations: ambos exigem acao manual.
case "${GITHUB_REF_NAME}" in
develop|feat_CI_CD_TEST) AMBIENTE=teste ;;
release-*) AMBIENTE=homolog ;;
*) AMBIENTE="" ;;
esac
[ "${IS_TAG}" = "true" ] && AMBIENTE=""
# Uma imagem serve todos os ambientes: a config do frontend e resolvida
# em runtime (config.js montado no deploy), nao gravada no bundle.
# Toda imagem tambem recebe uma tag pelo commit, para rastreabilidade
# e retencao. Tags de release sempre constroem uma imagem nova a
# partir do commit marcado: a tag e o marco formal do artefato.
SHA_TAG="sha-$(echo "${GITHUB_SHA}" | cut -c1-8)"
echo "ambiente=${AMBIENTE}" >> "$GITHUB_OUTPUT"
echo "sha_tag=${SHA_TAG}" >> "$GITHUB_OUTPUT"
echo "tag_sha=${IMAGE}:${SHA_TAG}" >> "$GITHUB_OUTPUT"
echo "is_tag=${IS_TAG}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "image=${IMAGE}" >> "$GITHUB_OUTPUT"
echo "tag_version=${IMAGE}:${VERSION}" >> "$GITHUB_OUTPUT"
echo "tag_latest=${IMAGE}:latest" >> "$GITHUB_OUTPUT"
echo "build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
echo "Evento : $([ "${IS_TAG}" = "true" ] && echo tag || echo branch)"
echo "Versao : ${VERSION}"
echo "Imagem : ${IMAGE}:${VERSION}"
echo "Base : ${BASE_IMAGE}"
- name: Conferir versao do composer.json
run: |
set -euo pipefail
if [ "${{ steps.meta.outputs.is_tag }}" != "true" ]; then
echo "Build de branch; comparacao com composer.json nao se aplica."
exit 0
fi
DECLARED=$(grep -m1 '"version"' "${API_DIR}/composer.json" \
| sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/')
TAG="${{ steps.meta.outputs.version }}"
if [ "${DECLARED}" != "${TAG}" ]; then
echo "::warning::composer.json declara versao '${DECLARED}' mas a tag e '${TAG}'. Build segue com a tag."
else
echo "composer.json e tag batem: ${TAG}"
fi
- name: Avaliar reversibilidade das migrations
id: rollback
run: |
set -euo pipefail
PREV=$(git describe --tags --abbrev=0 "${GITHUB_REF_NAME}^" 2>/dev/null || true)
if [ -z "${PREV}" ]; then
echo "Nenhuma tag anterior encontrada; analise de rollback ignorada."
echo "safe=unknown" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Comparando ${PREV} -> ${GITHUB_REF_NAME}"
NOVAS=$(git diff --name-only --diff-filter=A "${PREV}" "${GITHUB_REF_NAME}" \
-- "${API_DIR}/database/migrations" || true)
if [ -z "${NOVAS}" ]; then
echo "Nenhuma migration nova neste release."
echo "safe=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Migrations novas:"
echo "${NOVAS}" | sed 's/^/ /'
DESTRUTIVAS=""
while IFS= read -r f; do
[ -f "${f}" ] || continue
UP=$(sed '/function[[:space:]]*down[[:space:]]*(/,$d' "${f}")
if echo "${UP}" | grep -qE 'dropColumn|renameColumn|dropIfExists|DROP TABLE|DROP COLUMN'; then
DESTRUTIVAS="${DESTRUTIVAS}${f}"$'\n'
fi
done <<< "${NOVAS}"
{
echo "### Reversibilidade"
echo ""
echo "Comparado com \`${PREV}\`."
} >> "$GITHUB_STEP_SUMMARY"
if [ -z "${DESTRUTIVAS}" ]; then
echo "Todas as migrations novas sao aditivas."
echo "safe=true" >> "$GITHUB_OUTPUT"
{
echo ""
echo "Migrations novas sao **aditivas**. Rollback da imagem para \`${PREV}\` e seguro mesmo com o schema ja migrado."
} >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
echo "safe=false" >> "$GITHUB_OUTPUT"
echo "${DESTRUTIVAS}" | while IFS= read -r f; do
[ -n "${f}" ] && echo "::warning file=${f}::Migration destrutiva no up(): rollback para ${PREV} nao sera seguro apos aplicar."
done
{
echo ""
echo "**Atencao: este release contem migrations destrutivas.**"
echo ""
echo "Depois de aplicar a migration, voltar a imagem para \`${PREV}\` NAO restaura o funcionamento:"
echo "o codigo antigo vai referenciar colunas/tabelas que deixaram de existir. Faca backup do banco"
echo "antes de migrar, ou planeje a correcao como hotfix para frente."
echo ""
echo "| Migration |"
echo "| --- |"
echo "${DESTRUTIVAS}" | while IFS= read -r f; do
[ -n "${f}" ] && echo "| \`${f}\` |"
done
} >> "$GITHUB_STEP_SUMMARY"
- name: Login no Docker Hub (imagem base)
env:
DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: |
set -euo pipefail
if [ -z "${DOCKERHUB_TOKEN:-}" ]; then
echo "DOCKERHUB_TOKEN nao definido; assumindo imagem base publica ou ja em cache."
exit 0
fi
echo "${DOCKERHUB_TOKEN}" | docker login -u "${DOCKERHUB_USER}" --password-stdin
- name: Login no registry do Gitea
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
REGISTRY_USER: ${{ vars.REGISTRY_USER }}
ACTOR: ${{ github.actor }}
run: |
set -euo pipefail
USER="${REGISTRY_USER:-$ACTOR}"
TOKEN="${REGISTRY_TOKEN:-}"
if [ -z "${TOKEN}" ]; then
echo "REGISTRY_TOKEN ausente; usando o GITEA_TOKEN do job."
TOKEN="${GITEA_TOKEN}"
USER="${ACTOR}"
fi
echo "${TOKEN}" | docker login "${REGISTRY}" -u "${USER}" --password-stdin
- name: Build da imagem
run: |
set -euo pipefail
docker build \
--file "${DOCKERFILE}" \
--build-arg BASE_IMAGE="${BASE_IMAGE}" \
--build-arg APP_VERSION="${{ steps.meta.outputs.version }}" \
--build-arg VCS_REF="${GITHUB_SHA}" \
--build-arg BUILD_DATE="${{ steps.meta.outputs.build_date }}" \
--build-arg SOURCE_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" \
--tag "${{ steps.meta.outputs.tag_version }}" \
--tag "${{ steps.meta.outputs.tag_sha }}" \
"${CONTEXT_DIR}"
- name: Smoke test da imagem
run: |
set -euo pipefail
IMG="${{ steps.meta.outputs.tag_version }}"
echo "--> aplicacao deve bootar com config presente"
docker run --rm --entrypoint bash "${IMG}" -c '
set -e
cd /var/www/api
cp .env.example .env
cp connections.json.example connections.json
php artisan --version
test -f bootstrap/cache/routes-v7.php || test -f bootstrap/cache/routes.php
test -f bootstrap/cache/packages.php
'
echo "--> imagem nao pode carregar credenciais"
if docker run --rm --entrypoint bash "${IMG}" -c 'test -f /var/www/api/.env || test -f /var/www/api/connections.json'; then
echo "::error::.env ou connections.json foram parar dentro da imagem"
exit 1
fi
echo "--> entrypoint deve recusar subir sem config"
if docker run --rm "${IMG}" true 2>/dev/null; then
echo "::error::entrypoint subiu sem .env/connections.json"
exit 1
fi
echo "--> frontend deve estar publicado no dist"
docker run --rm --entrypoint bash "${IMG}" -c '
set -e
test -f /var/www/frontend/dist/index.html
test -d /var/www/frontend/dist/js
test -d /var/www/frontend/dist/css
'
echo "--> imagem NAO pode ter URL de ambiente gravada no bundle"
# O ponto da config em runtime e a imagem ser neutra. Se alguem voltar
# a ler process.env.VUE_APP_* num componente, o webpack grava o valor
# no bundle e a imagem silenciosamente vira especifica de um ambiente —
# o que quebra a promessa de "o que foi homologado e o que vai a prod".
# O index.html precisa carregar o config.js antes do bundle.
docker run --rm --entrypoint bash "${IMG}" -c '
set -e
test -f /var/www/frontend/dist/config.js
grep -q "__APP_CONFIG__" /var/www/frontend/dist/config.js
grep -q "config.js" /var/www/frontend/dist/index.html
'
echo "--> apache deve servir api na :80 e frontend na :8080"
docker run --rm --entrypoint bash "${IMG}" -c '
set -e
apache2ctl configtest 2>&1 | grep -q "Syntax OK"
apache2ctl -S 2>/dev/null | grep -q "^\*:80 "
apache2ctl -S 2>/dev/null | grep -q "^\*:8080 "
'
# As checagens acima usam --entrypoint bash, que nunca executa o CMD.
# As de baixo sobem o container de verdade: sao as unicas que pegam
# defeito de supervisord/apache que so aparece no boot real.
#
# O job roda com o docker.sock do host, entao bind mount de arquivo
# criado aqui dentro nao chegaria ao daemon (o caminho nao existe no
# host). Por isso a config entra via 'docker cp' num container criado
# e ainda nao iniciado.
TMP=$(mktemp -d)
docker run --rm --entrypoint bash "${IMG}" -c 'cat /var/www/api/.env.example' > "${TMP}/.env"
docker run --rm --entrypoint bash "${IMG}" -c 'cat /var/www/api/connections.json.example' > "${TMP}/connections.json"
sed -i "s|^APP_KEY=.*|APP_KEY=base64:$(head -c 32 /dev/urandom | base64)|" "${TMP}/.env"
printf 'window.__APP_CONFIG__ = { ROOT_API: "http://smoke-test", DNS: {}, ENV: "" }\n' > "${TMP}/config.js"
chmod 644 "${TMP}/.env" "${TMP}/connections.json" "${TMP}/config.js"
echo "--> entrypoint deve recusar subir sem a config de runtime do frontend"
# Sem o mount, o app subiria apontando para lugar nenhum em vez de
# falhar — o silencio e que e perigoso aqui.
CID_SEM=$(docker create "${IMG}")
docker cp "${TMP}/.env" "${CID_SEM}:/var/www/api/.env"
docker cp "${TMP}/connections.json" "${CID_SEM}:/var/www/api/connections.json"
docker start "${CID_SEM}" >/dev/null 2>&1 || true
sleep 3
if [ "$(docker inspect -f '{{.State.Running}}' "${CID_SEM}")" = "true" ]; then
echo "::error::container subiu sem /var/www/frontend/config.js montado"
docker rm -f "${CID_SEM}" >/dev/null 2>&1 || true
exit 1
fi
docker rm -f "${CID_SEM}" >/dev/null 2>&1 || true
echo "--> container deve subir com config e permanecer de pe"
CID=$(docker create "${IMG}")
cleanup() { docker rm -f "${CID}" >/dev/null 2>&1 || true; rm -rf "${TMP}"; }
trap cleanup EXIT
docker cp "${TMP}/.env" "${CID}:/var/www/api/.env"
docker cp "${TMP}/connections.json" "${CID}:/var/www/api/connections.json"
docker cp "${TMP}/config.js" "${CID}:/var/www/frontend/config.js"
docker start "${CID}" >/dev/null
for _ in $(seq 1 15); do
docker exec "${CID}" curl -s -o /dev/null http://localhost:8080/ 2>/dev/null && break
sleep 2
done
if [ "$(docker inspect -f '{{.State.Running}}' "${CID}")" != "true" ]; then
echo "::error::container nao permaneceu de pe (exit=$(docker inspect -f '{{.State.ExitCode}}' "${CID}"))"
docker logs "${CID}" 2>&1 | tail -20
exit 1
fi
CODE_API=$(docker exec "${CID}" curl -s -o /dev/null -w '%{http_code}' http://localhost:80/)
CODE_SPA=$(docker exec "${CID}" curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/)
CODE_ROTA=$(docker exec "${CID}" curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/rota/interna)
echo " api :80 -> ${CODE_API} | spa :8080 -> ${CODE_SPA} | spa /rota -> ${CODE_ROTA}"
# A API nao tem rota "/", entao 404 e resposta valida; o que nao pode e
# 000 (sem resposta). Ja o SPA precisa de 200 na raiz e tambem na rota
# interna — este segundo cobre o FallbackResource do mode: 'history'.
[ "${CODE_API}" != "000" ] || { echo "::error::apache nao respondeu na :80"; exit 1; }
[ "${CODE_SPA}" = "200" ] || { echo "::error::frontend nao respondeu 200 na :8080"; exit 1; }
[ "${CODE_ROTA}" = "200" ] || { echo "::error::FallbackResource do SPA nao esta ativo"; exit 1; }
echo "--> config.js servido deve ser o montado, e sem cache"
# Fecha o ciclo: nao basta o entrypoint copiar, o Apache precisa servir
# o arquivo instalado. E o no-store evita que o browser continue com a
# URL do ambiente anterior depois de uma troca.
SERVIDO=$(docker exec "${CID}" curl -s http://localhost:8080/config.js)
echo "${SERVIDO}" | grep -q 'smoke-test' \
|| { echo "::error::config.js servido nao e o montado: ${SERVIDO}"; exit 1; }
docker exec "${CID}" curl -sI http://localhost:8080/config.js | grep -qi 'cache-control: *no-store' \
|| { echo "::error::config.js servido sem Cache-Control no-store (mod_headers habilitado?)"; exit 1; }
echo "Smoke test OK"
- name: Push da imagem
run: |
set -euo pipefail
docker push "${{ steps.meta.outputs.tag_version }}"
docker push "${{ steps.meta.outputs.tag_sha }}"
if [ "${{ steps.meta.outputs.is_tag }}" != "true" ]; then
echo "Build de branch: tag 'latest' preservada."
exit 0
fi
if [ "${PUSH_LATEST}" = "true" ]; then
docker tag "${{ steps.meta.outputs.tag_version }}" "${{ steps.meta.outputs.tag_latest }}"
docker push "${{ steps.meta.outputs.tag_latest }}"
else
echo "PUSH_LATEST=${PUSH_LATEST}: tag 'latest' nao publicada."
fi
- name: Deploy no ambiente da branch
# Branch = ambiente: develop -> teste e release-* -> homolog.
# Tags e master nao fazem deploy por este workflow.
#
# Fica aqui, e nao num workflow proprio disparado por push, porque dois
# workflows no mesmo evento sao enfileirados em ordem arbitraria — o
# deploy poderia rodar antes de a imagem existir. Aqui a ordem e
# garantida e a imagem acabou de ser publicada.
#
# Precede a Limpeza de proposito: ela faz docker logout, e o deploy
# ainda precisa do login para o pull.
if: ${{ steps.meta.outputs.ambiente != '' && vars.AUTO_DEPLOY != 'false' }}
env:
AMBIENTE: ${{ steps.meta.outputs.ambiente }}
SSH_HOST: ${{ vars.TEST_SSH_HOST || '192.168.0.44' }}
SSH_PORT: ${{ vars.TEST_SSH_PORT || '22' }}
SSH_USER: ${{ vars.SSH_USER_TEST || secrets.SSH_USER_TEST }}
SSH_PRIVATE_KEY: ${{ secrets.SSH_KEY_TEST }}
SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS_TEST }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
REGISTRY_USER: ${{ vars.REGISTRY_USER }}
ACTOR: ${{ github.actor }}
run: |
set -euo pipefail
# O Gitea publica a tag em minusculas; a branch pode ter maiusculas.
TAG=$(echo "${{ steps.meta.outputs.version }}" | tr '[:upper:]' '[:lower:]')
if [ "${AMBIENTE}" = "teste" ]; then
echo "Deploy de teste usa SSH para ${SSH_HOST}:${SSH_PORT}."
chmod +x docker/deploy/deploy-over-ssh.sh
./docker/deploy/deploy-over-ssh.sh "${AMBIENTE}" "${TAG}"
else
chmod +x docker/deploy/deploy.sh
./docker/deploy/deploy.sh "${AMBIENTE}" "${TAG}"
fi
- name: Migrations automaticas do ambiente de teste
# Migrations nunca acompanham homolog/prod automaticamente. Em teste,
# a lista continua explicita para nao atingir conexoes tecnicas do
# connections.json. As duas variables sao opt-in deliberado.
if: ${{ steps.meta.outputs.ambiente == 'teste' && vars.AUTO_DEPLOY != 'false' && vars.AUTO_MIGRATE_TEST == 'true' }}
env:
TAG_VERSION: ${{ steps.meta.outputs.version }}
MUNICIPIOS: ${{ vars.MIGRATION_CONNECTIONS_TEST }}
run: |
set -euo pipefail
if [ -z "${MUNICIPIOS:-}" ]; then
echo "::error::AUTO_MIGRATE_TEST=true exige a variable MIGRATION_CONNECTIONS_TEST com a lista explicita de municipios."
exit 1
fi
# O Gitea normaliza a tag de branch para minusculas no registry.
TAG=$(echo "${TAG_VERSION}" | tr '[:upper:]' '[:lower:]')
chmod +x docker/deploy/migrate.sh
./docker/deploy/migrate.sh teste "${TAG}" "${MUNICIPIOS}"
- name: Resumo
if: always()
run: |
{
echo "### Imagem publicada"
echo ""
echo "| Campo | Valor |"
echo "| --- | --- |"
echo "| Versao | \`${{ steps.meta.outputs.version }}\` |"
echo "| Imagem | \`${{ steps.meta.outputs.tag_version }}\` |"
echo "| Base | \`${BASE_IMAGE}\` |"
echo "| Commit | \`${GITHUB_SHA}\` |"
echo ""
echo "**Uma imagem serve todos os ambientes.** A config do frontend e"
echo "resolvida em runtime: monte o \`config.js\` do ambiente em"
echo "\`/var/www/frontend/config.js\` (modelo em \`docker/config.js.example\`)."
echo "Tags de release constroem a imagem e a publicam no registry."
echo "Deploy e migrations de producao continuam sendo passos manuais."
echo ""
echo "Migrations municipais so executam automaticamente em teste quando"
echo "\`AUTO_MIGRATE_TEST=true\` e \`MIGRATION_CONNECTIONS_TEST\` estao definidos."
} >> "$GITHUB_STEP_SUMMARY"
- name: Limpeza
if: always()
run: |
docker rmi "${{ steps.meta.outputs.tag_version }}" \
"${{ steps.meta.outputs.tag_latest }}" \
"${{ steps.meta.outputs.tag_sha }}" 2>/dev/null || true
docker logout "${REGISTRY}" 2>/dev/null || true

102
.gitea/workflows/deploy.yml Normal file
View File

@@ -0,0 +1,102 @@
name: deploy
# Implanta uma imagem ja publicada. Nao constroi nada: a tag de release e o
# artefato formal, construido quando ela e criada e escolhido explicitamente
# para cada deploy.
#
# Producao nunca sobe sozinha: exige acionamento manual, com a versao escolhida.
on:
workflow_dispatch:
inputs:
ambiente:
description: 'Ambiente de destino'
required: true
type: choice
options:
- teste
- homolog
- prod
versao:
description: 'Tag da imagem (ex.: 2.59.3, feat_ci_cd_test)'
required: true
type: string
jobs:
deploy:
runs-on: ubuntu-latest
env:
REGISTRY: ${{ vars.REGISTRY_HOST || '192.168.0.41:3001' }}
IMAGE_NAME: ${{ vars.API_IMAGE_NAME || 'rkm/conectasus-api' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login no registry do Gitea
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
REGISTRY_USER: ${{ vars.REGISTRY_USER }}
ACTOR: ${{ github.actor }}
run: |
set -euo pipefail
if [ -z "${REGISTRY_TOKEN:-}" ]; then
echo "::error::REGISTRY_TOKEN nao definido. O pull da imagem privada depende dele."
exit 1
fi
echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" -u "${REGISTRY_USER:-$ACTOR}" --password-stdin
- name: Confirmar que a imagem existe antes de mexer no ambiente
run: |
set -euo pipefail
IMG="${REGISTRY}/${IMAGE_NAME}:${{ inputs.versao }}"
if ! docker manifest inspect "${IMG}" >/dev/null 2>&1; then
echo "::error::${IMG} nao existe no registry. Confira a tag (o Gitea publica em minusculas)."
exit 1
fi
echo "imagem encontrada: ${IMG}"
- name: Deploy
env:
AMBIENTE: ${{ inputs.ambiente }}
VERSAO: ${{ inputs.versao }}
SSH_HOST: ${{ vars.TEST_SSH_HOST || '192.168.0.44' }}
SSH_PORT: ${{ vars.TEST_SSH_PORT || '22' }}
SSH_USER: ${{ vars.SSH_USER_TEST || secrets.SSH_USER_TEST }}
SSH_PRIVATE_KEY: ${{ secrets.SSH_KEY_TEST }}
SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS_TEST || secrets.SSH_KNOWN_HOST_TEST }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
REGISTRY_USER: ${{ vars.REGISTRY_USER }}
ACTOR: ${{ github.actor }}
run: |
set -euo pipefail
if [ "${AMBIENTE}" = "teste" ]; then
echo "Deploy de teste usa SSH para ${SSH_HOST}:${SSH_PORT}."
chmod +x docker/deploy/deploy-over-ssh.sh
./docker/deploy/deploy-over-ssh.sh "${AMBIENTE}" "${VERSAO}"
else
chmod +x docker/deploy/deploy.sh
./docker/deploy/deploy.sh "${AMBIENTE}" "${VERSAO}"
fi
- name: Resumo
if: always()
run: |
{
echo "### Deploy"
echo ""
echo "| Campo | Valor |"
echo "| --- | --- |"
echo "| Ambiente | \`${{ inputs.ambiente }}\` |"
echo "| Versao | \`${{ inputs.versao }}\` |"
echo "| Acionado por | \`${{ github.actor }}\` |"
echo "| Resultado | \`${{ job.status }}\` |"
echo ""
echo "O deploy so troca a imagem. Migrations continuam sendo passo"
echo "separado e deliberado — ver \`docker/README.md\`."
} >> "$GITHUB_STEP_SUMMARY"
- name: Limpeza
if: always()
run: docker logout "${REGISTRY}" 2>/dev/null || true

View File

@@ -0,0 +1,99 @@
name: migrate
# Migrations alteram dados persistentes e nao acompanham o deploy automatico.
# Este workflow manual recebe uma imagem ja publicada e uma lista EXPLICITA de
# municipios; ele nunca tenta inferir ou migrar todas as conexoes.
on:
workflow_dispatch:
inputs:
ambiente:
description: 'Ambiente de destino'
required: true
type: choice
options:
- teste
- homolog
- prod
versao:
description: 'Tag da imagem ja publicada (ex.: 2.59.3)'
required: true
type: string
municipios:
description: 'Conexoes municipais separadas por virgula (ex.: anhembi,aparecida)'
required: true
type: string
confirmacao:
description: 'Digite MIGRAR para confirmar a alteracao dos bancos'
required: true
type: string
jobs:
migrate:
runs-on: ubuntu-latest
env:
REGISTRY: ${{ vars.REGISTRY_HOST || '192.168.0.41:3001' }}
IMAGE_NAME: ${{ vars.API_IMAGE_NAME || 'rkm/conectasus-api' }}
steps:
- name: Confirmar operacao destrutiva
env:
CONFIRMACAO: ${{ inputs.confirmacao }}
run: |
set -euo pipefail
if [ "${CONFIRMACAO}" != "MIGRAR" ]; then
echo "::error::Para executar migrations, informe confirmacao=MIGRAR."
exit 1
fi
- name: Checkout
uses: actions/checkout@v4
- name: Login no registry do Gitea
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
REGISTRY_USER: ${{ vars.REGISTRY_USER }}
ACTOR: ${{ github.actor }}
run: |
set -euo pipefail
if [ -z "${REGISTRY_TOKEN:-}" ]; then
echo "::error::REGISTRY_TOKEN nao definido. A imagem privada nao pode ser usada."
exit 1
fi
echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" -u "${REGISTRY_USER:-$ACTOR}" --password-stdin
- name: Aplicar migrations municipais
env:
AMBIENTE: ${{ inputs.ambiente }}
VERSAO: ${{ inputs.versao }}
MUNICIPIOS: ${{ inputs.municipios }}
run: |
set -euo pipefail
chmod +x docker/deploy/migrate.sh
./docker/deploy/migrate.sh \
"${AMBIENTE}" \
"${VERSAO}" \
"${MUNICIPIOS}"
- name: Resumo
if: always()
run: |
{
echo "### Migrations"
echo ""
echo "| Campo | Valor |"
echo "| --- | --- |"
echo "| Ambiente | \`${{ inputs.ambiente }}\` |"
echo "| Versao | \`${{ inputs.versao }}\` |"
echo "| Municipios | \`${{ inputs.municipios }}\` |"
echo "| Acionado por | \`${{ github.actor }}\` |"
echo "| Resultado | \`${{ job.status }}\` |"
echo ""
echo "A execucao para no primeiro municipio com falha. Bancos anteriores"
echo "podem ja ter recebido migrations e exigem verificacao antes de repetir."
} >> "$GITHUB_STEP_SUMMARY"
- name: Limpeza
if: always()
run: docker logout "${REGISTRY}" 2>/dev/null || true