From b20299dd6b380239b59771a655e700ad358db4c3fdc02b7848ea68c90c2b74d9 Mon Sep 17 00:00:00 2001 From: victor Date: Fri, 28 Aug 2026 02:19:08 -0300 Subject: [PATCH] =?UTF-8?q?Atualiza=C3=A7=C3=A3o=20de=20Read.me=20e=20\cor?= =?UTF-8?q?re=C3=A7=C3=A3o=20da=20autoriza=C3=A7=C3=A3o=20no=20primeiro=20?= =?UTF-8?q?login?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 6 +- README.md | 153 +++++++++++++++++++ app/controllers/users/sessions_controller.rb | 13 ++ bin/docker-boot | 56 ++++++- 4 files changed, 222 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index b9b4c01..5b1bd5b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,8 +41,10 @@ RUN bundle install --jobs 4 --retry 3 # Copia o restante do código COPY . . -# Pré-compila assets (em produção) -# RUN bundle exec rails assets:precompile +# Assets NÃO são pré-compilados aqui, e a linha comentada foi removida para +# ninguém "descomentar para resolver": o compose monta o projeto por cima +# (`volumes: - ".:/app"`), então o public/assets gerado na imagem some no boot. +# Quem pré-compila é bin/docker-boot, com o código que está de fato rodando. EXPOSE 3000 diff --git a/README.md b/README.md index b006dd7..de9f8cf 100644 --- a/README.md +++ b/README.md @@ -3360,3 +3360,156 @@ config/routes.rb # POST atualizar_logo ``` + +--- + +
+🩺 28/08/2026 — "A primeira vez que entra dá tela de erro" + os assets 404 que o Cloudflare escondia + +Dois problemas achados a partir de **um** relato ("a primeira vez que entra dá a +tela de erro, em `/auth/login`"). O primeiro era o relatado; o segundo apareceu +com o navegador aberto na mesma tela e era o mais grave dos dois. + +| Sintoma na tela | Causa real | Onde se resolveu | +|---|---|---| +| Primeira entrada do dia parece falha do sistema | `devise.failure.unauthenticated` sai como `flash[:alert]` — vermelho, e **duas vezes** | `Users::SessionsController#new` | +| Carrossel e editor de romaneio mortos no teste | `RAILS_ENV=production` + **nada no deploy pré-compilava assets** | `bin/docker-boot` (passo 5) | + +## 1. O "erro" que era só o Devise pedindo login + +Quem abre `teste.reemtransportes.com.br` deslogado — ou seja, **toda** primeira +entrada — passa por `authenticate_user!`, que redireciona para `/auth/login` +gravando `flash[:alert] = "Para continuar, faça login."`. Como `alert`, a +mensagem saía **em vermelho e em dois lugares ao mesmo tempo**: o toast com +triângulo de alerta no canto superior direito (`layouts/application.html.erb`) e +a caixa vermelha acima do formulário (`devise/sessions/new.html.erb`). + +Entrar no sistema pelo caminho normal ficava com a cara de sistema quebrado. E +como na segunda visita a pessoa já está em `/auth/login` sem flash nenhum, a tela +aparecia limpa — daí o "só na primeira vez", que fazia parecer bug intermitente. + +A mensagem agora é **descartada**: a tela já é o formulário de login, o aviso não +acrescenta nada. + +```ruby +# app/controllers/users/sessions_controller.rb +flash.delete(:alert) if flash[:alert] == I18n.t('devise.failure.unauthenticated', default: nil) +``` + +⚠️ **O filtro é pela mensagem exata, e não `flash.delete(:alert)` seco.** Senha +errada, conta desativada e `timeout` ("Sua sessão expirou") chegam ao mesmo +`#new` pelo mesmo caminho — essas **precisam** continuar vermelhas. Apagar o +alerta inteiro no `new` deixaria a pessoa digitando a senha errada para sempre +sem entender por quê. + +## 2. Os assets 404 — e por que ninguém tinha visto + +O console da tela de login acusava dois módulos: + +``` +Failed to fetch dynamically imported module: + /assets/controllers/carrossel_controller-913b47f7.js -> 404 + /assets/controllers/romaneio_controller-0105d794.js -> 404 +``` + +Não é pouco: `carrossel_controller` é a **paginação por tamanho de tela** (a +regra 3 do `CLAUDE.md`) e `romaneio_controller` é o editor de romaneio inteiro. +Os dois estavam mortos no ambiente de teste. + +### O disfarce + +À primeira vista pareciam só dois arquivos com problema — os outros dez +controllers respondiam 200. **Não respondiam.** Os 200 vinham do **cache do +Cloudflare**: + +``` +$ curl -sI .../validacao_controller-ca4de84f.js +cache-control: public, max-age=31536000, immutable +age: 275844 # ~3,2 dias em cache +cf-cache-status: HIT # ← nunca chegou no servidor +``` + +Os assets saem com `max-age` de **um ano**. Os dez que "funcionavam" eram cópias +guardadas na borda dias antes; os dois quebrados eram os dois arquivos alterados +**depois** dessa fotografia. Batendo direto na origem (com querystring, para +furar o cache), **todos** davam 404. + +> **Lição de diagnóstico:** ao investigar asset em produção, olhe +> `cf-cache-status` antes de concluir qualquer coisa. `HIT` não é prova de que o +> servidor está servindo — é prova de que ele serviu **algum dia**. + +### A causa + +O corpo do 404 era `public/404.html`, ou seja, a requisição chegou no **roteador +do Rails**: o servidor de assets do Propshaft nem estava montado. E a resposta do +HTML não trazia o cabeçalho `Server-Timing`, que só existe em development +(`config.server_timing`). Somando os dois: **o servidor roda +`RAILS_ENV=production`**. + +Em production o Propshaft **não compila sob demanda** — ele resolve o caminho +pelo `public/assets/.manifest.json` e o arquivo digerido tem que existir em +disco. E nada no deploy gerava isso: + +- a linha `RUN bundle exec rails assets:precompile` do `Dockerfile` estava + **comentada** (e não adiantaria descomentar: o compose monta `.:/app` por cima + e apagaria o `public/assets` da imagem); +- `bin/docker-boot` não tocava no assunto; +- `/public/assets` é **gitignorado**. + +Resultado: o que existia no servidor era o resto de algum `precompile` manual +antigo. Todo JS alterado depois dele virava 404 silencioso — silencioso porque a +tela **continua abrindo** (o Tailwind vem de CDN e os ícones são `public/` +direto), só sem comportamento. + +### A correção + +Novo **passo 5** no `bin/docker-boot`, que roda em todo start de container — logo, +em todo deploy: + +```bash +if [ "${RAILS_ENV:-development}" = "production" ]; then + rm -rf public/assets tmp/cache/assets + bundle exec rails assets:precompile +else + rm -rf public/assets # em dev o Propshaft calcula o digest ao vivo +fi +``` + +- **Limpa antes de pré-compilar**: sem isso 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` em vez de + `rails assets:clobber`: faz o mesmo sem pagar outro boot do Rails.) +- **Falha não derruba o boot**: `exit 1` aqui viraria loop de reinício com o + `restart: unless-stopped` e deixaria todo mundo de fora. O erro é gritado no + log — o que não podia continuar é a falha **muda**. +- **Em development apaga `public/assets`**: sobra de precompile faz o Propshaft + voltar a resolver pelo manifesto, e aí alteração de JS só aparece depois de + recompilar — parece cache do navegador quando não é. + +### Como conferir depois do deploy + +```bash +# 1. os assets do HTML respondem na ORIGEM (querystring fura o cache do CDN) +curl -s https://teste.reemtransportes.com.br/auth/login \ + | grep -o '/assets/controllers/[a-z_]*-[a-f0-9]*\.js' | sort -u \ + | while read a; do + printf "%s -> " "$a" + curl -s -o /dev/null -w "%{http_code}\n" "https://teste.reemtransportes.com.br$a?cb=$RANDOM" + done + +# 2. o passo apareceu no log do boot +docker compose logs app | grep '\[boot\] assets' +``` + +Todos têm que responder **200**. Se algum der 404, o `precompile` não rodou ou +falhou — o log do passo 5 diz qual dos dois. + +## 📂 Arquivos + +``` +app/controllers/users/sessions_controller.rb # descarta o "faça login" vermelho +bin/docker-boot # passo 5: clobber + precompile +Dockerfile # tira a linha de precompile morta e explica por quê +``` + +
diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index a79afb5..d5a727a 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -5,6 +5,19 @@ class Users::SessionsController < Devise::SessionsController # GET /users/sign_in def new @pin_login = params[:pin].present? + + # "Para continuar, faça login." NÃO é erro: é o que o Devise sempre grava + # quando alguém abre o sistema deslogado (raiz -> authenticate_user! -> aqui). + # Como `flash[:alert]` ele saía em vermelho DUAS vezes — o toast do layout e + # a caixa desta tela — e a primeira entrada do dia parecia falha do sistema + # (foi relatado como bug: "a primeira vez que entra dá a tela de erro"). + # + # A mensagem é descartada: a tela já é o formulário de login, ela não + # acrescenta nada. NÃO troque isso por apagar todo `flash[:alert]` no `new`: + # senha errada, conta inativa e `timeout` ("Sua sessão expirou") chegam por + # aqui pelo mesmo caminho, e essas a pessoa PRECISA ver em vermelho. + flash.delete(:alert) if flash[:alert] == I18n.t('devise.failure.unauthenticated', default: nil) + super end diff --git a/bin/docker-boot b/bin/docker-boot index 4a723d0..00b2e5d 100755 --- a/bin/docker-boot +++ b/bin/docker-boot @@ -16,8 +16,10 @@ # 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. cron — grava o crontab do config/schedule.rb e sobe o daemon. -# 6. Puma — processo em primeiro plano (é ele que segura o container). +# 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] $*"; } @@ -85,7 +87,53 @@ for i in $(seq 1 $tentativas); do sleep 6 done -# ── 5. Cron (best-effort) ──────────────────────────────────────────────────── +# ── 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. @@ -95,6 +143,6 @@ else log "AVISO: cron/crontab indisponível (rode com --build) — seguindo sem agendamento" fi -# ── 6. Puma ────────────────────────────────────────────────────────────────── +# ── 7. Puma ────────────────────────────────────────────────────────────────── log "subindo o Rails" exec bundle exec rails s -b 0.0.0.0