Corrige acesso em produção: hosts permitidos, i18n do Devise e config do ambiente no compose
- production.rb (hosts): a linha que parecia só liberar o APP_HOST na verdade
LIGAVA a verificação de Host e recusava todo o resto — o acesso pelo IP
interno caía em 'Blocked hosts: 100.75.222.23:3001'. Agora entram também
localhost/127.0.0.1 e a lista HOSTS_PERMITIDOS.
- production.rb (i18n): 'fallbacks = true' significa cair no default_locale,
que aqui é o próprio pt-BR — o fallback apontava para si mesmo e chave
ausente virava 'Translation missing' na tela do usuário. Agora [:en], igual
ao application.rb.
- pt-BR.yml: traduções do Devise (failure/sessions/passwords). O projeto não
usa devise-i18n; em development o texto vinha em inglês pelo fallback e o
buraco passava despercebido.
- docker-compose.yml / .env.example: porta do host parametrizada
(${PORTA_APP:-3001}) e APP_HOST/APP_NAME/WHATSAPP_URL/HOSTS_PERMITIDOS com
padrão — essa configuração vivia só no .env do servidor e sumiu junto com ele.
This commit is contained in:
22
.env.example
22
.env.example
@@ -18,11 +18,11 @@ TZ=America/Sao_Paulo
|
|||||||
|
|
||||||
# ── Porta publicada no host ───────────────────────────────
|
# ── Porta publicada no host ───────────────────────────────
|
||||||
# A porta DENTRO do container é sempre 3000; esta é a do servidor.
|
# A porta DENTRO do container é sempre 3000; esta é a do servidor.
|
||||||
# Só precisa mudar quando outro stack já ocupa a porta no mesmo host —
|
# Neste branch (teste) o padrão do docker-compose.yml JÁ É 3001, porque a 3000
|
||||||
# é o caso do ambiente de TESTE, que usa 3001 (a 3000 está ocupada).
|
# está ocupada por outro stack no mesmo NAS. Esta linha só é necessária para
|
||||||
# Sem esta variável o compose assume 3000.
|
# usar uma porta diferente da padrão.
|
||||||
# ⚠️ Tem que casar com o destino do proxy reverso (DSM / Cloudflare).
|
# ⚠️ Tem que casar com o destino do proxy reverso (DSM / Cloudflare).
|
||||||
PORTA_APP=3000
|
PORTA_APP=3001
|
||||||
|
|
||||||
# ── Banco de dados (seu PostgreSQL já existente) ─────────────
|
# ── Banco de dados (seu PostgreSQL já existente) ─────────────
|
||||||
# Aponte DB_HOST para o IP ou hostname do seu servidor PostgreSQL
|
# Aponte DB_HOST para o IP ou hostname do seu servidor PostgreSQL
|
||||||
@@ -91,9 +91,21 @@ SMTP_DOMAIN=gade.com.br
|
|||||||
NOTIFICACAO_SECRET=
|
NOTIFICACAO_SECRET=
|
||||||
|
|
||||||
# ── App ───────────────────────────────────────────────────────
|
# ── App ───────────────────────────────────────────────────────
|
||||||
APP_HOST=localhost:3000
|
# ⚠️ SÓ O HOST, sem "https://". O código monta os links como
|
||||||
|
# "https://#{APP_HOST}/motorista" (consolidacao_mailer.rb,
|
||||||
|
# notificacao_service.rb, gatilhos.rb): com o esquema aqui sai
|
||||||
|
# "https://https://..." e todo link de e-mail e de WhatsApp quebra.
|
||||||
|
# O docker-compose.yml já traz o host do ambiente como padrão — esta linha só
|
||||||
|
# é necessária para apontar para outro endereço.
|
||||||
|
APP_HOST=teste.reemtransportes.com.br
|
||||||
APP_NAME=Reem Logística
|
APP_NAME=Reem Logística
|
||||||
|
|
||||||
|
# Hosts extras aceitos além do APP_HOST, separados por vírgula (só vale com
|
||||||
|
# RAILS_ENV=production, onde o Rails checa o cabeçalho Host).
|
||||||
|
# Use quando o sistema também for acessado por IP:porta na rede interna —
|
||||||
|
# sem isso o Rails responde "Blocked hosts: <ip>:<porta>".
|
||||||
|
HOSTS_PERMITIDOS=100.75.222.23
|
||||||
|
|
||||||
# ── WhatsApp (ponte própria — sessão pareada por QR code) ──────────────────
|
# ── WhatsApp (ponte própria — sessão pareada por QR code) ──────────────────
|
||||||
# Token compartilhado entre o Rails e o container `whatsapp`. Qualquer string
|
# Token compartilhado entre o Rails e o container `whatsapp`. Qualquer string
|
||||||
# longa e aleatória: `openssl rand -hex 32`. Sem ele a ponte recusa TUDO.
|
# longa e aleatória: `openssl rand -hex 32`. Sem ele a ponte recusa TUDO.
|
||||||
|
|||||||
@@ -21,11 +21,42 @@ Rails.application.configure do
|
|||||||
config.action_mailer.perform_caching = false
|
config.action_mailer.perform_caching = false
|
||||||
config.action_mailer.default_url_options = { host: ENV.fetch("APP_HOST", "localhost:3000") }
|
config.action_mailer.default_url_options = { host: ENV.fetch("APP_HOST", "localhost:3000") }
|
||||||
|
|
||||||
config.i18n.fallbacks = true
|
# `= true` significa "caia no default_locale" — e o default AQUI é o próprio
|
||||||
|
# pt-BR, então o fallback apontava para si mesmo e qualquer chave ausente
|
||||||
|
# virava "Translation missing" NA TELA DO USUÁRIO (aconteceu em 27/08/2026 na
|
||||||
|
# tela de login, com devise.failure.unauthenticated). Em development o
|
||||||
|
# config/application.rb já usa [:en] e por isso o problema não aparecia lá.
|
||||||
|
# Mantido igual ao application.rb: chave faltando mostra o texto em inglês,
|
||||||
|
# que é feio mas legível — nunca a mensagem de erro do i18n.
|
||||||
|
config.i18n.fallbacks = [:en]
|
||||||
config.active_support.report_deprecations = false
|
config.active_support.report_deprecations = false
|
||||||
config.active_record.dump_schema_after_migration = false
|
config.active_record.dump_schema_after_migration = false
|
||||||
|
|
||||||
# Aceita o host configurado no .env
|
# ── Hosts aceitos (proteção contra DNS rebinding) ─────────────────────────
|
||||||
|
# Regra do Rails: lista VAZIA = aceita qualquer Host; lista com QUALQUER item
|
||||||
|
# = só o que estiver nela entra. Por isso a linha abaixo, que parece só
|
||||||
|
# "liberar o APP_HOST", na verdade LIGA a verificação e bloqueia todo o resto
|
||||||
|
# com "ActionDispatch::HostAuthorization ... Blocked hosts: <host>".
|
||||||
|
#
|
||||||
|
# ⚠️ APP_HOST tem que vir SEM "https://": com o esquema, este split libera um
|
||||||
|
# host chamado "https" e nada funciona.
|
||||||
config.hosts << ENV["APP_HOST"]&.split(":")&.first if ENV["APP_HOST"].present?
|
config.hosts << ENV["APP_HOST"]&.split(":")&.first if ENV["APP_HOST"].present?
|
||||||
|
|
||||||
|
# O próprio servidor. Sem isto, um `curl http://127.0.0.1:3001/` de dentro do
|
||||||
|
# NAS — que é o teste que separa "app caiu" de "proxy errado" — volta
|
||||||
|
# bloqueado e faz parecer que a aplicação está quebrada.
|
||||||
|
config.hosts += ["localhost", "127.0.0.1", "[::1]"]
|
||||||
|
|
||||||
|
# Endereços extras, separados por vírgula. Caso real (27/08/2026): o acesso
|
||||||
|
# pela rede interna chega com `Host: 100.75.222.23:3001` (o IP do NAS), que
|
||||||
|
# não bate com o domínio público e caía em "Blocked hosts". A porta é
|
||||||
|
# ignorada pelo Rails na comparação — basta o IP/nome.
|
||||||
|
# HOSTS_PERMITIDOS=100.75.222.23,10.0.0.20
|
||||||
|
ENV.fetch("HOSTS_PERMITIDOS", "").split(",").map(&:strip).reject(&:empty?).each do |permitido|
|
||||||
|
config.hosts << permitido
|
||||||
|
end
|
||||||
|
|
||||||
|
# Escotilha de emergência: desliga a verificação por completo. Último recurso —
|
||||||
|
# com ela ligada, qualquer domínio apontado para este servidor é aceito.
|
||||||
config.hosts.clear if ENV["RAILS_ALLOW_ALL_HOSTS"] == "true"
|
config.hosts.clear if ENV["RAILS_ALLOW_ALL_HOSTS"] == "true"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,8 +8,48 @@ pt-BR:
|
|||||||
default: "%d/%m/%Y %H:%M"
|
default: "%d/%m/%Y %H:%M"
|
||||||
short: "%d/%m %H:%M"
|
short: "%d/%m %H:%M"
|
||||||
|
|
||||||
# Mensagem de login recusado para conta desativada (User#inactive_message).
|
# ── Devise ────────────────────────────────────────────────────────────────
|
||||||
# Sem esta chave, o Devise mostraria o símbolo cru "conta_desativada".
|
# POR QUE ESTÁ TUDO AQUI E NÃO SÓ A CHAVE DA CONTA DESATIVADA:
|
||||||
|
# o projeto não usa a gem `devise-i18n`, então as únicas traduções do Devise
|
||||||
|
# são estas. Em development a ausência passava despercebida porque o
|
||||||
|
# config/application.rb faz `i18n.fallbacks = [:en]` e o texto vinha em inglês
|
||||||
|
# do próprio gem. Em produção o `fallbacks` apontava para o pt-BR (ele mesmo)
|
||||||
|
# e a tela de login exibiu, para o usuário final:
|
||||||
|
# "Translation missing. Options considered were: pt-BR.devise.failure.unauthenticated"
|
||||||
|
# Traduzir de fato é melhor do que cair no inglês: quem usa o sistema é
|
||||||
|
# operador e motorista brasileiros.
|
||||||
devise:
|
devise:
|
||||||
failure:
|
failure:
|
||||||
|
already_authenticated: "Você já está autenticado."
|
||||||
|
inactive: "Sua conta ainda não foi ativada."
|
||||||
|
invalid: "%{authentication_keys} ou senha inválidos."
|
||||||
|
not_found_in_database: "%{authentication_keys} ou senha inválidos."
|
||||||
|
timeout: "Sua sessão expirou. Faça login novamente para continuar."
|
||||||
|
unauthenticated: "Para continuar, faça login."
|
||||||
|
# Conta desativada pelo administrador (User#inactive_message).
|
||||||
|
# Sem esta chave, o Devise mostraria o símbolo cru "conta_desativada".
|
||||||
conta_desativada: "Sua conta está desativada. Fale com um administrador."
|
conta_desativada: "Sua conta está desativada. Fale com um administrador."
|
||||||
|
sessions:
|
||||||
|
signed_in: "Login efetuado com sucesso."
|
||||||
|
signed_out: "Logout efetuado com sucesso."
|
||||||
|
already_signed_out: "Logout efetuado com sucesso."
|
||||||
|
passwords:
|
||||||
|
send_instructions: "Em alguns minutos você receberá um e-mail com instruções para redefinir sua senha."
|
||||||
|
send_paranoid_instructions: "Se o seu e-mail existir em nosso cadastro, você receberá um link para redefinir a senha."
|
||||||
|
updated: "Sua senha foi alterada com sucesso. Você já está autenticado."
|
||||||
|
updated_not_active: "Sua senha foi alterada com sucesso."
|
||||||
|
no_token: "Esta página só pode ser acessada pelo link do e-mail de redefinição de senha. Peça um novo link."
|
||||||
|
mailer:
|
||||||
|
reset_password_instructions:
|
||||||
|
subject: "Instruções para redefinir sua senha"
|
||||||
|
|
||||||
|
# Chaves que o Devise procura na RAIZ do locale (não dentro de `devise:`).
|
||||||
|
errors:
|
||||||
|
messages:
|
||||||
|
not_found: "não encontrado"
|
||||||
|
already_confirmed: "já foi confirmado, tente fazer login"
|
||||||
|
not_locked: "não está bloqueado"
|
||||||
|
expired: "expirou, solicite um novo"
|
||||||
|
not_saved:
|
||||||
|
one: "1 erro impediu que este %{resource} fosse salvo:"
|
||||||
|
other: "%{count} erros impediram que este %{resource} fosse salvo:"
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ services:
|
|||||||
# POR QUE NÃO É FIXA: o mesmo NAS roda mais de um stack, e dois containers
|
# POR QUE NÃO É FIXA: o mesmo NAS roda mais de um stack, e dois containers
|
||||||
# não publicam a mesma porta ("driver failed programming external
|
# não publicam a mesma porta ("driver failed programming external
|
||||||
# connectivity ... port is already allocated"). O ambiente de TESTE usa a
|
# connectivity ... port is already allocated"). O ambiente de TESTE usa a
|
||||||
# 3001; a 3000 já está ocupada.
|
# 3001; a 3000 já está ocupada. Por isso o PADRÃO aqui é 3001 e não 3000:
|
||||||
|
# é o valor que mantém ESTE ambiente de pé se o .env se perder — foi
|
||||||
|
# exatamente o que aconteceu, e o padrão 3000 fez o container morrer com
|
||||||
|
# "port is already allocated". Na produção (branch main) o padrão é 3000.
|
||||||
#
|
#
|
||||||
# Isto vivia como alteração LOCAL no docker-compose.yml do servidor — e em
|
# Isto vivia como alteração LOCAL no docker-compose.yml do servidor — e em
|
||||||
# 27/08/2026 um `git reset --hard` levou junto, derrubando o ambiente com o
|
# 27/08/2026 um `git reset --hard` levou junto, derrubando o ambiente com o
|
||||||
@@ -21,7 +24,7 @@ services:
|
|||||||
# ⚠️ Ao mudar, ajuste JUNTO o proxy reverso (DSM/Cloudflare) — senão o 502
|
# ⚠️ Ao mudar, ajuste JUNTO o proxy reverso (DSM/Cloudflare) — senão o 502
|
||||||
# só muda de lugar.
|
# só muda de lugar.
|
||||||
ports:
|
ports:
|
||||||
- "${PORTA_APP:-3000}:3000"
|
- "${PORTA_APP:-3001}:3000"
|
||||||
env_file: .env
|
env_file: .env
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: ${DATABASE_URL}
|
DATABASE_URL: ${DATABASE_URL}
|
||||||
@@ -37,6 +40,24 @@ services:
|
|||||||
# ajuste sem rebuild — basta TZ=... no .env. Ver deploy/ntp-seguro.sh:
|
# ajuste sem rebuild — basta TZ=... no .env. Ver deploy/ntp-seguro.sh:
|
||||||
# a HORA em si é do host, isto aqui só define o fuso de leitura.
|
# a HORA em si é do host, isto aqui só define o fuso de leitura.
|
||||||
TZ: ${TZ:-America/Sao_Paulo}
|
TZ: ${TZ:-America/Sao_Paulo}
|
||||||
|
|
||||||
|
# Endereço público e nome do sistema. Ficam aqui com PADRÃO porque não são
|
||||||
|
# segredo — assim um .env perdido não leva junto a configuração do
|
||||||
|
# ambiente. O .env continua mandando: se a variável existir lá, ela vence.
|
||||||
|
#
|
||||||
|
# ⚠️ APP_HOST é SÓ O HOST, sem "https://". O código monta os links como
|
||||||
|
# "https://#{APP_HOST}/motorista" (consolidacao_mailer.rb,
|
||||||
|
# notificacao_service.rb, gatilhos.rb) — com o esquema no valor sai
|
||||||
|
# "https://https://..." e todo link de e-mail e WhatsApp quebra.
|
||||||
|
APP_HOST: ${APP_HOST:-teste.reemtransportes.com.br}
|
||||||
|
APP_NAME: "${APP_NAME:-Reem Logística}"
|
||||||
|
# Hosts extras aceitos além do APP_HOST (vírgula separa vários). Com
|
||||||
|
# RAILS_ENV=production o Rails só aceita os hosts listados; o acesso pela
|
||||||
|
# rede interna chega com o IP do NAS e era recusado com
|
||||||
|
# "Blocked hosts: 100.75.222.23:3001". Ver config/environments/production.rb.
|
||||||
|
HOSTS_PERMITIDOS: ${HOSTS_PERMITIDOS:-100.75.222.23}
|
||||||
|
# A ponte do WhatsApp é alcançada pelo nome do serviço na rede do compose.
|
||||||
|
WHATSAPP_URL: ${WHATSAPP_URL:-http://whatsapp:3001}
|
||||||
volumes:
|
volumes:
|
||||||
- ".:/app"
|
- ".:/app"
|
||||||
- "bundle_cache:/usr/local/bundle"
|
- "bundle_cache:/usr/local/bundle"
|
||||||
|
|||||||
Reference in New Issue
Block a user