166 lines
9.9 KiB
Bash
Executable File
166 lines
9.9 KiB
Bash
Executable File
#!/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
|
||
# CONFERÊNCIA À PARTE, e não confiança no código de saída: sem o manifesto o
|
||
# Propshaft NÃO quebra a página — ele cai no resolver dinâmico e continua
|
||
# montando URLs com o digest certo do arquivo atual. O resultado é a pior
|
||
# combinação possível: a tela abre com aparência normal e CADA /assets/*
|
||
# devolve 404, então some o JS inteiro (prévia do PDF, carrossel, edição
|
||
# inline) sem nenhum erro no log. Foi assim em 28/08/2026: o HTML pedia
|
||
# romaneio_controller-a123ef51.js, o arquivo não existia, e como o Cloudflare
|
||
# ainda servia os controllers antigos do cache parecia defeito de dois
|
||
# arquivos — na origem, TODOS os assets estavam 404.
|
||
total=$(find public/assets -type f 2>/dev/null | wc -l)
|
||
if [ ! -f public/assets/.manifest.json ] || [ "$total" -lt 2 ]; then
|
||
log "ALERTA: precompile terminou sem erro mas public/assets tem $total arquivo(s)."
|
||
log " O site vai subir SEM JS: cada /assets/* responde 404."
|
||
log " Rode 'docker compose exec app bundle exec rails assets:precompile'"
|
||
log " e confira com: curl -sI SEU_DOMINIO/assets/controllers/index-<digest>.js"
|
||
else
|
||
log "assets prontos ($total arquivo(s) em public/assets)"
|
||
fi
|
||
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
|