This commit is contained in:
8
.gitea/runner/.env.example
Normal file
8
.gitea/runner/.env.example
Normal 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
106
.gitea/runner/README.md
Normal 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.
|
||||
22
.gitea/runner/compose.portainer.yml
Normal file
22
.gitea/runner/compose.portainer.yml
Normal 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
21
.gitea/runner/compose.yml
Normal 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"
|
||||
453
.gitea/workflows/build-api-image.yml
Normal file
453
.gitea/workflows/build-api-image.yml
Normal 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
102
.gitea/workflows/deploy.yml
Normal 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
|
||||
99
.gitea/workflows/migrate.yml
Normal file
99
.gitea/workflows/migrate.yml
Normal 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
|
||||
Reference in New Issue
Block a user