Files
bot-pontos/docs/DEPLOY.md
Bruno Santos 61fde19133 docs: revisa a documentação com base no que a aplicação faz hoje
- README: o que a aplicação faz, migrations, BP_WEB_PORT e testes com
  go.sum/package-lock.json (sem go mod tidy)
- ARQUITETURA: esquema real (00001 a 00003), rotas que faltavam, Compose
  e imagens atuais, cadeia de proxies, retenção, situação dos testes,
  decisões 30 a 33 e estado da implementação
- DEPLOY: cópia por tar, BP_WEB_PORT em todas as etapas e atualização
  para a versão com exclusão de contas
- MANUAL: papéis do owner e navegação por ano nos feriados
- openapi.yaml: DELETE /accounts/{id} e regras da conta restrita
- Dockerfiles: comentários sobre go.sum e package-lock.json

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:20:25 -03:00

21 KiB

Guia de deploy — Bot de Pontos v2

Passo a passo para colocar o Bot de Pontos no ar no servidor de produção:

  • Servidor: máquina física com Debian 12, fora do Proxmox, rodando Docker.
  • Acesso ao painel: pelo Nginx Proxy Manager (NPM) que já existe em outra máquina da rede, com certificado interno.
  • Endereço do painel: https://botpontos.lan (troque pelo nome que você usar).

O backend compila e passa nos testes, e o painel passa na checagem de tipos. A etapa 6 repete essa validação no próprio servidor antes de subir tudo. Se algo falhar, guarde a saída do comando para correção.

A porta do painel no servidor é 3000 por padrão. Se ela já estiver em uso, defina BP_WEB_PORT no .env (etapa 5.1) e use essa porta onde este guia diz 3000 (etapas 7, 8 e 9).

Visão geral

Navegador ──HTTPS──► NPM (outra máquina) ──HTTP :3000──► Servidor Debian (Docker)
                                                          ├── web       (Next.js, porta 3000)
                                                          ├── api       (Go, só rede interna)
                                                          ├── worker    (Go: bot + lembretes)
                                                          ├── postgres  (só rede interna)
                                                          └── backup    (pg_dump diário)
Etapa O quê Onde
1 Reunir informações e credenciais —
2 Preparar o Debian (IP fixo, NTP, atualizações) servidor
3 Instalar o Docker servidor
4 Copiar o projeto Windows → servidor
5 Criar .env e segredos servidor
6 Validar o código (testes) servidor
7 Subir os containers servidor
8 Firewall da porta do painel servidor
9 DNS, certificado e Proxy Host rede / NPM
10 Primeiro acesso e primeiro admin navegador / Telegram
11 Backup fora da máquina servidor
12 Conferência final —

Os comandos no servidor são executados como root (ou com sudo).


1. Reunir informações e credenciais

Tenha em mãos antes de começar:

Item Exemplo Como obter
IP fixo do servidor 192.168.18.50 reserva DHCP no roteador ou IP estático (etapa 2)
IP do NPM 192.168.18.10 máquina onde o NPM roda
Nome do painel botpontos.lan escolha um nome interno
Token do bot 123456:ABC... no Telegram, fale com @BotFather → /newbot
Chave da feriadosapi.com — conta em feriadosapi.com (opcional: sem ela vale segunda a sexta)
Certificado interno .crt + .key emitido pela CA interna da rede para botpontos.lan (com o nome no SAN)
Credenciais PontoMais e-mail e senha de cada usuário cada usuário

No @BotFather, depois de criar o bot, é recomendado:

  • /setjoingroups → Disable (o bot só atende conversas privadas);
  • /setcommands com a lista abaixo, para aparecer o menu de comandos:
status - situação do cadastro e dos lembretes
entrada2 - retorno previsto do almoço
saida2 - saída prevista
ativarponto - ligar os lembretes
desativarponto - pausar os lembretes
cadastrar - solicitar acesso
ajuda - lista de comandos

2. Preparar o Debian

2.1 Atualizar o sistema

apt update && apt full-upgrade -y

2.2 IP fixo

O jeito mais simples é uma reserva DHCP no roteador para o MAC do servidor. Se preferir IP estático, edite /etc/network/interfaces (ajuste interface, IP e gateway):

auto enp1s0
iface enp1s0 inet static
    address 192.168.18.50/24
    gateway 192.168.18.1
    dns-nameservers 192.168.18.1

Confira:

ip -4 addr show

2.3 Relógio (NTP) — obrigatório

Os lembretes dependem da hora certa.

apt install -y systemd-timesyncd
timedatectl set-ntp true
timedatectl

A saída precisa mostrar System clock synchronized: yes. O fuso do sistema não importa (a aplicação grava em UTC e calcula no fuso de cada usuário), mas America/Sao_Paulo facilita a leitura dos logs:

timedatectl set-timezone America/Sao_Paulo

2.4 Ferramentas

apt install -y ca-certificates curl openssl iptables

3. Instalar o Docker

Use o repositório oficial da Docker (o pacote docker.io do Debian costuma estar defasado).

install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" > /etc/apt/sources.list.d/docker.list
apt update && apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Ligue o Docker na inicialização e confira:

systemctl enable --now docker
docker compose version

4. Copiar o projeto

O projeto vai para /opt/botpontos.

No servidor:

mkdir -p /opt/botpontos

No Windows, no PowerShell, dentro da pasta que contém bot-pontos-v2 (ex.: E:\Bruno\GIT), gere um pacote sem os arquivos locais. O tar já vem no Windows 10/11:

tar -czf bot-pontos-v2.tgz --exclude=node_modules --exclude=.next --exclude=.git --exclude=.env --exclude=backups --exclude=tsconfig.tsbuildinfo bot-pontos-v2

Envie para o servidor (troque usuário e IP):

scp .\bot-pontos-v2.tgz usuario@192.168.18.50:/tmp/

O pacote não leva .env, backups/, node_modules, .next nem o histórico do Git. A pasta secrets/ deve ter só o README.md: os segredos são criados direto no servidor (etapa 5).

De volta ao servidor, extraia no lugar final:

tar -xzf /tmp/bot-pontos-v2.tgz -C /opt/botpontos --strip-components=1 && rm /tmp/bot-pontos-v2.tgz
cd /opt/botpontos && ls

Deve aparecer backend, web, docs, docker-compose.yml, .env.example etc.

Os scripts precisam de quebra de linha Linux (LF). Confira e, se aparecer CRLF, converta:

file postgres/init/01-create-app-role.sh scripts/backup-loop.sh
sed -i 's/\r$//' postgres/init/01-create-app-role.sh scripts/backup-loop.sh

5. Criar .env e segredos

Todos os comandos desta etapa rodam em /opt/botpontos.

5.1 .env

cp .env.example .env && nano .env

Ajuste:

BP_HOST_LAN_IP=192.168.18.50          # IP fixo do servidor
BP_WEB_PORT=3000                      # porta do painel no servidor; troque se a 3000 estiver em uso
BP_PUBLIC_URL=https://botpontos.lan   # exatamente o endereço que o navegador usa
BP_NPM_IP=192.168.18.10               # IP do Nginx Proxy Manager
BP_COOKIE_SECURE=true
BP_LOG_LEVEL=info
BP_DEFAULT_TIMEZONE=America/Sao_Paulo
BP_HOLIDAY_LOCATION=3106200           # código IBGE da cidade dos feriados (3106200 = Belo Horizonte)
BP_ENCRYPTION_KEY_VERSION=1

Para saber se a 3000 já está ocupada no servidor:

ss -ltn 'sport = :3000'

Se aparecer alguma linha além do cabeçalho, escolha outra porta (ex.: BP_WEB_PORT=3010).

5.2 Segredos

mkdir -p secrets
openssl rand -base64 32 | tr -d '\n' > secrets/pg_password
openssl rand -base64 32 | tr -d '\n' > secrets/pg_app_password
openssl rand -base64 48 | tr -d '\n' > secrets/encryption_key

Token do bot (cole o seu entre as aspas simples):

printf '%s' 'COLE_O_TOKEN_DO_BOTFATHER' > secrets/telegram_token

Chave da feriadosapi.com (enviada como Authorization: Bearer), ou arquivo vazio se não tiver. A cidade dos feriados é o código IBGE em BP_HOLIDAY_LOCATION no .env (padrão 3106200, Belo Horizonte):

printf '%s' 'COLE_A_CHAVE' > secrets/holiday_api_key
: > secrets/holiday_api_key

Permissões (a pasta protege; os arquivos precisam ser legíveis pelos containers):

chmod 700 secrets && chmod 644 secrets/*

5.3 Guardar a chave de criptografia fora do servidor

Sem a encryption_key, as senhas da PontoMais gravadas não podem ser recuperadas. Copie o conteúdo para o seu cofre de senhas agora:

cat secrets/encryption_key

6. Validar o código (testes)

Rode a validação no servidor antes de subir, para confirmar que o código copiado compila nesta máquina. Esses comandos usam containers temporários e não instalam Go nem Node na máquina.

Backend — roda go vet e os testes com as versões fixadas no go.sum:

docker run --rm -v "$PWD/backend:/src" -w /src golang:1.25-alpine sh -c "go vet ./... && go test ./..."

Painel — instala as versões do package-lock.json e checa os tipos:

docker run --rm -v "$PWD/web:/app" -w /app node:22-alpine sh -c "npm ci && npx tsc --noEmit"
  • Tudo passou: siga para a etapa 7.
  • Algo falhou: pare aqui, salve a saída completa e traga para correção.

Os arquivos go.sum e package-lock.json fazem parte do projeto e fixam as versões das dependências. Não rode go mod tidy no servidor: com Go 1.25 ele tenta baixar versões novas que exigem Go 1.26 e o build quebra.


7. Subir os containers

docker compose up -d --build

O primeiro build leva alguns minutos. Depois confira:

docker compose ps -a

Resultado esperado:

Serviço Estado
postgres running (healthy)
migrate exited (0)
api running (healthy)
worker running (healthy)
web running (healthy)
backup running

Logs, se algo não ficar saudável:

docker compose logs --tail=100 migrate api worker

O worker deve registrar liderança obtida; worker ativo e bot do Telegram iniciado.

Teste local da porta (no próprio servidor; troque 3000 se usou outro BP_WEB_PORT):

curl -s http://192.168.18.50:3000/healthz

Deve responder {"status":"ok"}.


8. Firewall da porta do painel

Portas publicadas pelo Docker ignoram regras comuns de ufw/iptables INPUT. A regra vai na cadeia DOCKER-USER, aplicada por um serviço do systemd.

Outra porta: se definiu BP_WEB_PORT (por exemplo 3010), use essa porta no lugar de 3000 nesta etapa, no WEB_PORT do serviço de firewall e no Forward Port do NPM (etapa 9). Dentro do Docker o painel continua na 3000.

Coloque o IP do NPM no arquivo:

sed -i 's/IP_DO_NPM/192.168.18.10/' deploy/botpontos-firewall.service

Só se BP_WEB_PORT não for 3000, ajuste também a porta (troque 3010 pela sua):

sed -i 's/WEB_PORT=3000/WEB_PORT=3010/' deploy/botpontos-firewall.service

Instale e ative:

cp deploy/botpontos-firewall.service /etc/systemd/system/
systemctl daemon-reload && systemctl enable --now botpontos-firewall

Confira a regra:

iptables -S DOCKER-USER

Deve aparecer uma linha com --ctorigdstport 3000 (ou a sua porta) e ! -s 192.168.18.10/32 -j DROP.

Teste de aceite: de um computador da rede que não seja o NPM, o acesso direto precisa falhar (timeout):

curl -m 5 http://192.168.18.50:3000/healthz

9. DNS, certificado e Proxy Host

9.1 DNS interno

No DNS da rede (roteador, Pi-hole, AdGuard etc.), crie:

botpontos.lan  →  192.168.18.10   (IP do NPM, não do servidor Docker)

9.2 Certificado no NPM

No NPM: SSL Certificates → Add SSL Certificate → Custom. Envie o .crt (com a cadeia, se houver) e a .key emitidos pela CA interna para botpontos.lan.

O certificado raiz da CA interna precisa estar instalado nos computadores que acessam o painel.

9.3 Proxy Host

Hosts → Proxy Hosts → Add Proxy Host:

Aba Campo Valor
Details Domain Names botpontos.lan
Details Scheme http
Details Forward Hostname / IP 192.168.18.50
Details Forward Port 3000 (ou o valor de BP_WEB_PORT)
Details Block Common Exploits ligado
Details Websockets Support desligado
Details Access List uma lista que permita só a rede local (ex.: 192.168.18.0/24)
SSL SSL Certificate o certificado da etapa 9.2
SSL Force SSL / HTTP/2 / HSTS ligados

Abra https://botpontos.lan num computador da rede: deve aparecer a tela de login, sem alerta de certificado.

Anote a data de validade do certificado interno. Com HSTS ligado, um certificado vencido bloqueia o acesso em vez de só mostrar aviso.


10. Primeiro acesso e primeiro admin

10.1 Conta owner do painel

No servidor (a senha, de no mínimo 12 caracteres, é pedida sem eco):

docker compose run --rm api admin create-owner --username admin

Entre em https://botpontos.lan com essa conta.

10.2 Primeiro admin do Telegram

  1. A pessoa que será admin envia /cadastrar ao bot no Telegram.
  2. No painel, Usuários → abra o cadastro.
  3. Em Credencial PontoMais, informe e-mail e senha → Validar e salvar. A credencial é testada na hora; se a PontoMais recusar, nada é gravado.
  4. Clique em Ativar.
  5. Clique em Tornar admin do bot.
  6. A pessoa envia /ativarponto e confere com /status.

A partir daí, novos pedidos de cadastro chegam como aviso no Telegram desse admin.

10.3 Demais usuários

Para cada usuário: /cadastrar no Telegram → credencial no painel → Ativar → o usuário envia /ativarponto.

Recomendação: comece com 1 a 3 usuários (piloto) por alguns dias antes de liberar para todos.

10.4 Conta de leitura para um usuário (opcional)

Para que uma pessoa acompanhe só os próprios dados no painel:

  1. O usuário já deve estar cadastrado e ativo (item 10.2 ou 10.3).
  2. Entre com o owner → Contas → Nova conta: defina usuário e senha, papel leitura e escolha o Usuário do Telegram.
  3. Entregue a senha à pessoa. Ela verá apenas Início, Usuários, Lembretes e Feriados, e só os próprios dados.

Não há migration nem variável de ambiente nova para isso: o vínculo usa a coluna web_accounts.user_id, que já existe. O backend e o painel precisam ser atualizados juntos.

10.5 Feriados

Em Feriados, clique em Sincronizar para o ano atual (se houver chave da feriadosapi.com) e cadastre feriados manuais que faltarem.


11. Backup fora da máquina

O container backup faz um pg_dump por dia em /opt/botpontos/backups, guardando 14 dias. Um backup no mesmo disco não protege contra falha do disco. Copie para outra máquina ou NAS.

Exemplo com um compartilhamento montado em /mnt/nas e cron diário às 04:00:

mkdir -p /mnt/nas/botpontos
echo '0 4 * * * root cp -n /opt/botpontos/backups/*.dump /mnt/nas/botpontos/ 2>/dev/null' > /etc/cron.d/botpontos-backup

Guarde também, fora do servidor:

  • a encryption_key (etapa 5.3);
  • o arquivo .env;
  • uma cópia da pasta secrets/ (num local seguro).

Restaurar um backup

Pare os serviços que escrevem no banco:

docker compose stop api worker web

Restaure (troque o nome do arquivo):

docker compose exec -T postgres pg_restore -U botpontos -d botpontos --clean --if-exists --no-owner < backups/botpontos-AAAAMMDD-HHMMSS.dump

Suba de novo:

docker compose up -d

A encryption_key usada no restore precisa ser a mesma da época do backup.


12. Conferência final

  • docker compose ps -a com todos saudáveis e migrate em exited (0).
  • timedatectl mostra o relógio sincronizado.
  • Acesso direto à porta do painel (3000 ou BP_WEB_PORT) bloqueado para quem não é o NPM.
  • https://botpontos.lan abre sem alerta de certificado.
  • Login no painel funciona; conta owner criada.
  • Admin do Telegram recebe aviso ao surgir um novo /cadastrar.
  • Um usuário piloto recebeu o lembrete de retorno do almoço.
  • Feriados do ano sincronizados ou cadastrados.
  • Backup do dia existe em backups/ e foi copiado para fora.
  • encryption_key guardada no cofre de senhas.

Atualizar a aplicação

  1. Gere um backup manual antes:

    docker compose exec backup sh -c 'PGPASSWORD=$(cat /run/secrets/pg_password) pg_dump -h postgres -U botpontos -d botpontos -Fc -f /backups/antes-da-atualizacao.dump'
    
  2. Copie a nova versão do código para /opt/botpontos como na etapa 4. O pacote não leva .env nem backups/, e secrets/ só tem o README.md, então os arquivos do servidor são mantidos.

  3. Reconstrua e suba (as migrations rodam sozinhas no serviço migrate):

    docker compose up -d --build
    
  4. Confira com docker compose ps -a e os logs.

Versão com feriadosapi.com e conta de leitura restrita. Ao atualizar de uma versão anterior (que usava a feriados.dev):

  • Troque o conteúdo de secrets/holiday_api_key pela chave da feriadosapi.com (a chave antiga não serve).
  • No .env, troque BP_HOLIDAY_LOCATION de MG-belo-horizonte para 3106200 (código IBGE). Se esquecer, os feriados não sincronizam e vale segunda a sexta, com aviso aos admins.
  • A migration 00002 roda sozinha no serviço migrate. Ela só amplia o CHECK de holidays.source; os feriados já gravados continuam valendo, e o ano atual não é baixado de novo.
  • Para corrigir o ano com a nova API, use Feriados → Sincronizar: só entram datas que ainda não estão na tabela.
  • A conta de leitura restrita (10.4) não exige configuração extra, mas backend e painel precisam subir juntos.

Versão com exclusão de contas e porta configurável.

  • A migration 00003 roda sozinha. Ela cria audit_logs.actor_account_name (preenchida para os eventos antigos) e ajusta as chaves estrangeiras para permitir Contas → Excluir sem perder a auditoria.

  • BP_WEB_PORT é opcional: sem ela, o painel continua na 3000. Se for trocar a porta, ajuste o .env, o WEB_PORT do firewall e o Forward Port do NPM. A regra antiga continua no iptables até o próximo boot; para aplicar a nova na hora:

    cp deploy/botpontos-firewall.service /etc/systemd/system/ && systemctl daemon-reload && systemctl restart botpontos-firewall
    

    Lembre de pôr de novo o IP do NPM (e a porta) no arquivo antes de copiar, como na etapa 8, porque a nova versão do arquivo volta com IP_DO_NPM.

Atualize também o sistema e as imagens base de tempos em tempos:

apt update && apt full-upgrade -y
docker compose pull postgres backup && docker compose up -d --build

Problemas comuns

Sintoma Causa provável O que fazer
docker compose up reclama de BP_HOST_LAN_IP .env ausente ou sem a variável etapa 5.1
web não sobe: address already in use / port is already allocated outra aplicação usa a porta do painel defina outro BP_WEB_PORT (etapa 5.1) e ajuste firewall e NPM
secret ... file not found algum arquivo de secrets/ não existe etapa 5.2 (o holiday_api_key pode ser vazio, mas precisa existir)
migrate termina com erro de senha senhas do banco trocadas depois da primeira subida as senhas só valem na 1ª inicialização do volume; veja secrets/README.md
api/worker saem com BP_ENCRYPTION_KEY ... obrigatório chave ausente ou com menos de 32 caracteres refaça secrets/encryption_key
worker sai com BP_TELEGRAM_TOKEN ... obrigatório token ausente etapa 5.2
Worker com falha no getUpdates repetido token errado ou servidor sem internet confira o token e a saída para a internet
Painel abre, mas o login diz "Origem da requisição não permitida" BP_PUBLIC_URL diferente do endereço usado no navegador ajuste o .env (com https://, sem barra no fim) e rode docker compose up -d
Login funciona, mas toda ação diz "Token CSRF inválido" cookies de outra origem ou cache apague os cookies do site e entre de novo; use sempre o mesmo endereço
Após login, volta para a tela de login BP_COOKIE_SECURE=true acessando por http:// acesse pelo NPM com https://
Navegador alerta sobre o certificado CA interna não instalada no computador, ou nome fora do SAN etapa 9.2
Lembretes não chegam usuário sem /ativarponto, dia não útil, fora da janela, ou dia sem pontos /status no Telegram; tela do usuário e Auditoria no painel
Lembrete aparece como expirado ponto detectado com mais de 10 min de atraso normal em almoço muito cedo ou atraso da PontoMais; ver ARQUITETURA.md 9.3
Usuário pausado "após falhas na PontoMais" 3 falhas seguidas de consulta teste a credencial no painel e religue os lembretes
Alerta de "PontoMais fora do ar" todas as consultas do ciclo falharam aguarde; ninguém é pausado; o admin recebe aviso quando voltar