- 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>
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_PORTno.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);/setcommandscom 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,.nextnem o histórico do Git. A pastasecrets/deve ter só oREADME.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.sumepackage-lock.jsonfazem parte do projeto e fixam as versões das dependências. Não rodego mod tidyno 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 exemplo3010), use essa porta no lugar de 3000 nesta etapa, noWEB_PORTdo 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
- A pessoa que será admin envia
/cadastrarao bot no Telegram. - No painel, Usuários → abra o cadastro.
- Em Credencial PontoMais, informe e-mail e senha → Validar e salvar. A credencial é testada na hora; se a PontoMais recusar, nada é gravado.
- Clique em Ativar.
- Clique em Tornar admin do bot.
- A pessoa envia
/ativarpontoe 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:
- O usuário já deve estar cadastrado e ativo (item 10.2 ou 10.3).
- Entre com o owner → Contas → Nova conta: defina usuário e senha, papel leitura e escolha o Usuário do Telegram.
- 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 -acom todos saudáveis emigrateemexited (0).timedatectlmostra o relógio sincronizado.- Acesso direto à porta do painel (3000 ou
BP_WEB_PORT) bloqueado para quem não é o NPM. https://botpontos.lanabre 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_keyguardada no cofre de senhas.
Atualizar a aplicação
-
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' -
Copie a nova versão do código para
/opt/botpontoscomo na etapa 4. O pacote não leva.envnembackups/, esecrets/só tem oREADME.md, então os arquivos do servidor são mantidos. -
Reconstrua e suba (as migrations rodam sozinhas no serviço
migrate):docker compose up -d --build -
Confira com
docker compose ps -ae 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_keypela chave da feriadosapi.com (a chave antiga não serve). - No
.env, troqueBP_HOLIDAY_LOCATIONdeMG-belo-horizontepara3106200(código IBGE). Se esquecer, os feriados não sincronizam e vale segunda a sexta, com aviso aos admins. - A migration
00002roda sozinha no serviçomigrate. Ela só amplia oCHECKdeholidays.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
00003roda sozinha. Ela criaaudit_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, oWEB_PORTdo firewall e o Forward Port do NPM. A regra antiga continua noiptablesaté o próximo boot; para aplicar a nova na hora:cp deploy/botpontos-firewall.service /etc/systemd/system/ && systemctl daemon-reload && systemctl restart botpontos-firewallLembre 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 |