Correção de horario e implantação da geração de Romaneio

This commit is contained in:
2026-08-27 16:21:56 -03:00
parent a0aafe62fc
commit ad1a071e4b
51 changed files with 3450 additions and 46 deletions

119
README.md
View File

@@ -3068,3 +3068,122 @@ caminho do servidor e o trace inteiro — para **qualquer pessoa** que provocass
celular, onde não há faixa lateral livre.
</details>
---
<details>
<summary><strong>🕐 Relógio, restart automático e migrations no boot</strong></summary>
## O problema relatado
> "toda vez que faço o deploy ou reinicio o Docker a data e hora ficam erradas."
São **dois problemas diferentes** que se confundem porque aparecem no mesmo lugar:
| Sintoma | Causa | Onde se resolve |
|---|---|---|
| Hora **sempre 3h adiantada**, todo restart | O container subia em **UTC** | `Dockerfile` (fuso) |
| Hora **derivando / errada depois de reboot** | Relógio do **host** sem sincronia | `deploy/ntp-seguro.sh` (NTS) |
### 1. Fuso do container (o "sempre 3h à frente")
A imagem `ruby:3.2.2-slim` sobe em UTC. O Rails até mostrava a hora certa
(`config.time_zone = "America/Sao_Paulo"`), mas **tudo que era do sistema
operacional continuava adiantado** — e o pior caso não era visual:
> **O cron rodava no horário errado.** `every "*/30 8-18"` no `config/schedule.rb`
> executava das **05h às 15h** de Brasília, não das 08h às 18h.
Agora o `Dockerfile` fixa `TZ=America/Sao_Paulo` **e** o symlink de
`/etc/localtime` — os dois, porque o cron do Debian lê o arquivo, não a
variável. O mesmo foi feito no `whatsapp/Dockerfile` (Alpine precisa do pacote
`tzdata`, que não vem na imagem). Dá para ajustar sem rebuild pelo `TZ` do `.env`.
### 2. Hora do host — NTP autenticado (NTS)
**Não dá para sincronizar o relógio de dentro do container**: ele lê o clock do
kernel do host. Rodar NTP lá dentro exigiria `CAP_SYS_TIME` e mudaria a hora do
servidor inteiro a partir de um processo da aplicação.
No servidor, uma vez:
```bash
sudo bash deploy/ntp-seguro.sh
```
O script instala e configura o **chrony com NTS** (RFC 8915): a troca de chaves
é por TLS (TCP 4460) e os pacotes NTP vêm assinados, então uma resposta forjada
no caminho é descartada. NTP comum é UDP sem autenticação — e hora errada aqui
não é detalhe: ela muda **o dia da consolidação, o recorte do período financeiro
e a validade da sessão**.
O script ainda: usa **3 fontes independentes** (Cloudflare, Netnod, PTB),
**desliga as fontes não autenticadas** (deixar o `pool` padrão anularia o ganho),
desativa o `systemd-timesyncd` (não fala NTS e brigaria pelo relógio), liga o
`rtcsync` (é o relógio de hardware que dá a hora no boot) e mostra a
conferência no fim. É idempotente e guarda `.bak` da config.
Conferir depois, a qualquer momento:
```bash
chronyc tracking # 'System time' deve ficar na casa dos milissegundos
chronyc -N authdata # NTS ativo em cada fonte (Cook > 0)
```
> **Firewall:** precisa de saída em **UDP 123** e **TCP 4460**. Sem a 4460 o NTS
> não fecha e o chrony fica sem fonte.
## Restart automático + migrations
`docker-compose.yml`, serviço `app`: **`restart: unless-stopped`**. Sobe sozinho
depois de queda do processo e de reboot do servidor, e só fica parado se alguém
der `docker compose stop/down`. (`always` foi descartado: ele reergue o container
até depois de um `stop` deliberado, tirando do operador a chance de deixar o
sistema fora do ar de propósito.)
O boot saiu do `command:` de uma linha só e virou **`bin/docker-boot`**, com o
motivo de cada passo comentado:
1. **relógio** — imprime a hora e o fuso no log (se aparecer UTC, o rebuild não pegou);
2. **pid órfão** — sem limpar, o Puma se recusa a subir depois de uma queda;
3. **gems** — `bundle check || bundle install` (o volume `bundle_cache` sombreia os gems da imagem);
4. **migrations** — `db:prepare`, **com espera pelo banco**;
5. **cron** — `whenever --update-crontab` + daemon, *best-effort*;
6. **Puma**.
Ou seja: **a migration é automática a todo restart** — `db:prepare` cria o banco
se não existir, aplica as migrations pendentes e só faz seed em banco novo
(idempotente). Não há passo manual depois do deploy.
> **Por que a espera no passo 4:** o PostgreSQL é externo ao compose. Num reboot
> do servidor o Rails pode subir antes de o banco aceitar conexão — e agora, com
> `restart: unless-stopped`, isso viraria um **loop de reinício** parecendo erro
> de migration. São 10 tentativas × 6s. Se falhar depois disso (migration
> quebrada, credencial errada), o container sai com erro **de propósito**: melhor
> do que servir a aplicação contra um schema desatualizado.
## Deploy
```bash
# 1. No servidor, uma única vez — relógio com NTP autenticado:
sudo bash deploy/ntp-seguro.sh
# 2. Rebuild obrigatório (o fuso entra na imagem):
docker compose up -d --build
# 3. Conferir no log que o container está em -03 e não em UTC:
docker compose logs app | grep '\[boot\] relógio'
```
## 📂 Arquivos
```
Dockerfile # TZ=America/Sao_Paulo + tzdata + /etc/localtime; CMD -> bin/docker-boot
whatsapp/Dockerfile # tzdata (Alpine) + mesmo fuso
docker-compose.yml # restart: unless-stopped, TZ, command: bin/docker-boot
bin/docker-boot # boot documentado: migrations com espera, cron, Puma — NOVO
deploy/ntp-seguro.sh # chrony + NTS no host (rodar 1x, como root) — NOVO
.env.example # TZ=America/Sao_Paulo + aviso "fuso ≠ hora"
```
</details>