#!/usr/bin/env bash
#
# Relógio do SERVIDOR sincronizado com NTP autenticado (NTS).
# Rodar UMA VEZ no servidor, como root:  sudo bash deploy/ntp-seguro.sh
#
# ─── POR QUE NO HOST E NÃO NO CONTAINER ──────────────────────────────────────
# Container não tem relógio próprio: ele lê o clock do kernel do host. Rodar
# chrony/ntpd dentro do container exigiria CAP_SYS_TIME e mudaria a hora do
# SERVIDOR INTEIRO a partir de dentro de um processo da aplicação — por isso
# não se faz. Dentro do container a gente fixa só o FUSO (TZ, no Dockerfile);
# a HORA vem daqui.
#
# ─── POR QUE NTS E NÃO NTP COMUM ─────────────────────────────────────────────
# NTP puro é UDP sem autenticação: qualquer um no caminho pode responder no
# lugar do servidor e empurrar a hora da máquina. Isso não é teórico para este
# sistema — hora errada muda o dia da consolidação, o recorte do período
# financeiro e a validade de sessão/cookie. NTS (RFC 8915) faz uma troca de
# chaves por TLS (porta TCP 4460) e assina os pacotes NTP, então uma resposta
# forjada é descartada. O `chronyc authdata` no fim mostra se está valendo.
#
# ─── REDE ────────────────────────────────────────────────────────────────────
# Precisa de saída para:  UDP 123 (NTP)  e  TCP 4460 (NTS-KE).
# Se o firewall bloquear a 4460, o NTS não fecha e o chrony fica sem fonte.
set -euo pipefail

[ "$(id -u)" -eq 0 ] || { echo "ERRO: rode com sudo."; exit 1; }
command -v apt-get >/dev/null || { echo "ERRO: script escrito para Debian/Ubuntu."; exit 1; }

echo "==> Hora ANTES:"; date; echo

# ── 1. chrony ────────────────────────────────────────────────────────────────
# NTS só existe no chrony >= 4.0 (Debian 11+ / Ubuntu 22.04+ já têm).
if ! command -v chronyd >/dev/null; then
  echo "==> Instalando chrony"
  apt-get update -qq
  DEBIAN_FRONTEND=noninteractive apt-get install -y chrony
fi

versao="$(chronyd --version 2>&1 | grep -oE '[0-9]+\.[0-9]+' | head -1)"
if [ "${versao%%.*}" -lt 4 ]; then
  echo "ERRO: chrony $versao não fala NTS (precisa >= 4.0). Atualize o sistema."
  exit 1
fi
echo "==> chrony $versao (NTS disponível)"

# ── 2. Fontes NTS ────────────────────────────────────────────────────────────
# Três fontes independentes de propósito: com uma só, ela cai e o relógio fica
# à deriva sem ninguém notar; com três, o chrony ainda descarta a que divergir.
CONF_NTS="$(cat <<'CONF'
# Gerado por deploy/ntp-seguro.sh — fontes de tempo AUTENTICADAS (NTS).
# `iburst`: acerta rápido no boot, em vez de levar minutos.
server time.cloudflare.com iburst nts
server nts.netnod.se       iburst nts
server ptbtime1.ptb.de     iburst nts

# Cache das chaves NTS: sem isto, todo restart refaz a troca TLS e o relógio
# demora a fechar quando a internet ainda não subiu.
ntsdumpdir /var/lib/chrony

# Sincroniza também o relógio de hardware (RTC), que é de onde a máquina lê a
# hora ao ligar — é o que evita voltar errado depois de um reboot/queda de luz.
rtcsync

# makestep: corrige de uma vez (em vez de "escorregar" devagar) quando o erro
# é grande, mas só nas 3 primeiras medições. É a linha certa para servidor
# físico. SE ESTE SERVIDOR FOR VM QUE HIBERNA/PAUSA e a hora só aparece errada
# depois de uma pausa longa, troque pela linha comentada abaixo — ela permite
# o salto a qualquer momento (inclusive para trás, então saiba o que ganha):
makestep 1.0 3
# makestep 1.0 -1
CONF
)"

CHRONY_CONF=/etc/chrony/chrony.conf
[ -f "$CHRONY_CONF" ] || CHRONY_CONF=/etc/chrony.conf

cp -n "$CHRONY_CONF" "$CHRONY_CONF.bak-ntp-seguro" 2>/dev/null || true

# Onde escrever: o Debian/Ubuntu moderno lê o diretório conf.d. Se essa linha
# não existir na config, escrever lá seria IGNORADO EM SILÊNCIO — nesse caso a
# gente anexa no próprio chrony.conf, entre marcadores, para continuar sendo
# possível rodar o script de novo sem duplicar.
if grep -qE '^\s*confdir\s+.*conf\.d' "$CHRONY_CONF" 2>/dev/null; then
  mkdir -p /etc/chrony/conf.d
  printf '%s\n' "$CONF_NTS" > /etc/chrony/conf.d/nts-reem.conf
  echo "==> Fontes NTS em /etc/chrony/conf.d/nts-reem.conf"
else
  sed -i '/# >>> nts-reem >>>/,/# <<< nts-reem <<</d' "$CHRONY_CONF"
  { echo "# >>> nts-reem >>>"; printf '%s\n' "$CONF_NTS"; echo "# <<< nts-reem <<<"; } >> "$CHRONY_CONF"
  echo "==> Fontes NTS anexadas em $CHRONY_CONF"
fi

# ── 3. Desligar as fontes NÃO autenticadas ───────────────────────────────────
# Deixar o `pool` padrão junto anularia o ganho: o chrony poderia se decidir
# por uma fonte sem assinatura. Só comentamos (o .bak fica ao lado).
sed -i -E 's/^([[:space:]]*(pool|server)[[:space:]]+.*)$/# [nts-reem] \1/' "$CHRONY_CONF"
sed -i -E '/# >>> nts-reem >>>/,/# <<< nts-reem <<</ s/^# \[nts-reem\] (.*)$/\1/' "$CHRONY_CONF"
for f in /etc/chrony/sources.d/*.sources; do
  [ -e "$f" ] || continue
  mv "$f" "$f.desativado-ntp-seguro"
done

# ── 4. Um serviço de hora só ─────────────────────────────────────────────────
# systemd-timesyncd e chrony brigam pelo relógio; o timesyncd não fala NTS.
systemctl disable --now systemd-timesyncd 2>/dev/null || true
systemctl enable  --now chrony 2>/dev/null || systemctl enable --now chronyd

echo "==> Reiniciando chrony"
systemctl restart chrony 2>/dev/null || systemctl restart chronyd
sleep 5
chronyc makestep >/dev/null 2>&1 || true

# ── 5. Conferência ───────────────────────────────────────────────────────────
echo
echo "==> Fontes (o '^*' marca a que está valendo):"
chronyc -N sources -v || true
echo
echo "==> NTS (queremos 'KeyID/Type/KLen' preenchido e Cook > 0 nas 3 linhas):"
chronyc -N authdata || true
echo
echo "==> Estado:"; chronyc tracking || true
timedatectl || true
echo
echo "==> Hora DEPOIS:"; date
cat <<'FIM'

Pronto. O relógio agora se mantém sozinho, inclusive depois de reboot.

Se a hora tinha saltado, reinicie os containers para os processos longos
pegarem o clock corrigido:   docker compose restart

Para conferir depois, a qualquer momento:
  chronyc tracking      # 'System time' deve ficar em milissegundos
  chronyc -N authdata   # NTS ativo em cada fonte
FIM
