Files
Reem-Notas/bin/docker-boot

149 lines
8.7 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
#
# Boot do container do Rails (deploy e todo restart do Docker).
#
# POR QUE ISSO VIROU UM SCRIPT: antes era um `command:` de uma linha só no
# docker-compose.yml. Com `restart: unless-stopped` o boot passou a rodar
# sozinho — depois de queda, de reboot do servidor e de cada deploy — e um
# passo que falha em silêncio vira loop de reinício sem ninguém entender por
# quê. Aqui cada etapa diz o que está fazendo no log do container
# (`docker compose logs -f app`).
#
# ORDEM DOS PASSOS (não troque sem ler o motivo):
# 1. relógio — só imprime, para diagnóstico (ver comentário abaixo).
# 2. pid órfão — se o container morreu sem limpar, o Puma se recusa a subir.
# 3. gems — o volume bundle_cache SOMBREIA os gems da imagem, então
# gem nova no Gemfile não chega no container só com --build.
# 4. migrations — db:prepare: cria o banco se não existir, roda as migrations
# pendentes e só faz seed em banco recém-criado (idempotente).
# 5. assets — em production o Propshaft NÃO compila sob demanda: o
# arquivo digerido tem que estar em public/assets.
# 6. cron — grava o crontab do config/schedule.rb e sobe o daemon.
# 7. Puma — processo em primeiro plano (é ele que segura o container).
set -uo pipefail
log() { echo "[boot] $*"; }
# ── 1. Relógio ───────────────────────────────────────────────────────────────
# O container NÃO tem relógio próprio: ele lê o clock do kernel do HOST. Não
# adianta rodar NTP aqui dentro (exigiria CAP_SYS_TIME e mexeria na hora do
# servidor inteiro) — quem sincroniza é o host, via chrony/NTS; ver
# deploy/ntp-seguro.sh. O que é do container é só o FUSO (TZ), fixado no
# Dockerfile. Se a linha abaixo aparecer em UTC, o rebuild da imagem não pegou.
log "relógio do container: $(date '+%Y-%m-%d %H:%M:%S %Z (%z)') | UTC: $(date -u '+%H:%M:%S')"
# ── 2. PID órfão do Puma ─────────────────────────────────────────────────────
rm -f tmp/pids/server.pid
# ── 3. Gems ──────────────────────────────────────────────────────────────────
if ! bundle check >/dev/null 2>&1; then
log "gem faltando no volume bundle_cache — rodando bundle install"
bundle install || { log "ERRO: bundle install falhou"; exit 1; }
fi
# ── 4. Migrations ────────────────────────────────────────────────────────────
# ANTES de tentar, confere o DATABASE_URL. Este projeto NÃO tem
# config/database.yml: a conexão inteira sai dessa única variável (as DB_HOST,
# DB_USER etc. do .env não são lidas por ninguém — são só documentação).
#
# POR QUE ESTA CHECAGEM EXISTE: em 27/08/2026 o .env foi recriado a partir do
# .env.example e o DATABASE_URL ficou com o valor de exemplo. O resultado foram
# 10 tentativas cuspindo 40 linhas de backtrace até uma
# `URI::InvalidURIError: ... SEU_IP` no fim — ilegível no log do Container
# Manager. Placeholder e URL vazia não são "banco fora do ar": nenhuma espera
# resolve, então falha na hora e diz o que fazer.
if [ -z "${DATABASE_URL:-}" ]; then
log "ERRO: DATABASE_URL vazia. Sem config/database.yml, é ela que define o banco."
log " Formato: postgresql://USUARIO:SENHA@HOST:5432/NOME_DO_BANCO"
exit 1
fi
case "$DATABASE_URL" in
*SEU_IP*|*senha_segura*|*SEU_HOST*)
log "ERRO: DATABASE_URL ainda está com o valor de EXEMPLO do .env.example:"
log " $(printf '%s' "$DATABASE_URL" | sed 's|://[^@]*@|://***:***@|')"
log " Preencha com o IP, o usuário e a senha reais do PostgreSQL."
exit 1
;;
esac
# O PostgreSQL é EXTERNO ao compose: num reboot do servidor o Rails pode subir
# antes do banco aceitar conexão. Sem a espera abaixo o container sairia com
# erro e o `restart: unless-stopped` ficaria reiniciando em loop — parecendo
# problema de migration quando é só ordem de boot. 10 tentativas × 6s = 1 min.
tentativas=10
for i in $(seq 1 $tentativas); do
log "migrations (db:prepare) — tentativa $i/$tentativas"
if bundle exec rails db:prepare; then
log "banco pronto e migrations aplicadas"
break
fi
if [ "$i" -eq "$tentativas" ]; then
# Aqui é falha DE VERDADE (migration quebrada ou credencial errada): sair
# com erro é melhor do que subir a aplicação contra um schema desatualizado.
log "ERRO: db:prepare falhou após $tentativas tentativas — veja o erro acima"
exit 1
fi
log "banco indisponível ou migration falhou; nova tentativa em 6s"
sleep 6
done
# ── 5. Assets ────────────────────────────────────────────────────────────────
# POR QUE ISTO EXISTE: com RAILS_ENV=production — que é como o teste e o servidor
# rodam hoje — o Propshaft não serve asset dinamicamente. Ele resolve o caminho
# pelo public/assets/.manifest.json e quem entrega o arquivo é o servidor de
# estáticos; sem o arquivo em disco a requisição cai no roteador do Rails e volta
# 404 (a página public/404.html, não um erro de asset — por isso é difícil de
# reconhecer). Nada no deploy gerava esses arquivos: a linha do Dockerfile está
# comentada de propósito (o bind-mount `.:/app` do compose cobriria o que ela
# gerasse na imagem) e o boot não tocava no assunto. O que existia em
# public/assets no servidor era o resto de algum precompile manual antigo.
#
# CASO REAL (28/08/2026): o HTML pedia
# /assets/controllers/carrossel_controller-913b47f7.js e esse arquivo não existia
# mais -> 404 -> "Failed to fetch dynamically imported module" no console, com o
# carrossel (a paginação por tamanho de tela) e o editor de romaneio MORTOS no
# ambiente de teste. Só esses dois quebraram porque são os dois mais recentes:
# os outros dez controllers ainda vinham do cache do Cloudflare (max-age de 1
# ano), o que escondeu o problema por dias e fez parecer defeito de dois arquivos.
#
# A LIMPEZA ANTES do precompile é de propósito: sem ela um manifesto antigo
# convive com arquivos novos e a divergência volta na primeira alteração de JS.
# Assim manifesto e arquivos saem sempre da MESMA execução. É `rm -rf` e não
# `rails assets:clobber` porque faz exatamente o mesmo (essas duas pastas), sem
# pagar um boot inteiro do Rails só para apagar diretório.
if [ "${RAILS_ENV:-development}" = "production" ]; then
log "assets: limpando restos e pré-compilando (public/assets)"
rm -rf public/assets tmp/cache/assets
if bundle exec rails assets:precompile; then
log "assets prontos"
else
# NÃO sai com erro: sem JS o sistema fica ruim, mas ainda dá para entrar e
# ler os dados — e um `exit 1` aqui viraria loop de reinício com o
# `restart: unless-stopped`, deixando TODO MUNDO de fora. O aviso é gritado
# no log para não repetir a história de descobrir dias depois.
log "ERRO: assets:precompile falhou — o sistema vai subir SEM JS/CSS novos."
log " Rode 'docker compose exec app bundle exec rails assets:precompile'"
log " e veja o erro completo."
fi
else
# Em development o Propshaft calcula o digest do arquivo atual a cada
# requisição. Sobra de precompile faz ele voltar a resolver pelo manifesto, e
# aí toda alteração de JS/CSS só aparece depois de recompilar — parece que o
# navegador está com cache quando não está.
rm -rf public/assets
fi
# ── 6. Cron (best-effort) ────────────────────────────────────────────────────
# Best-effort de propósito: agendamento quebrado atrasa um relatório, mas NUNCA
# pode impedir o sistema de subir. As variáveis do banco chegam ao job pelo
# dotenv quando o rake carrega o Rails — o cron não precisa herdar o ENV.
if bundle exec whenever --update-crontab && cron; then
log "cron no ar (jobs do config/schedule.rb, no fuso ${TZ:-?})"
else
log "AVISO: cron/crontab indisponível (rode com --build) — seguindo sem agendamento"
fi
# ── 7. Puma ──────────────────────────────────────────────────────────────────
log "subindo o Rails"
exec bundle exec rails s -b 0.0.0.0