Importação inicial do projeto Shivão (Diário de Bordo do veleiro) em estado pronto pra produção, já incluindo as 5 mudanças de hardening implementadas pela squad shivao-melhoria em 2026-04-27. Mudanças de hardening (HANDOFF.md seção "✅ Pronto"): 1. **Rate limiting nos 3 endpoints públicos de share** - express-rate-limit ^8.4.1, 60 req/min/IP - server/src/index.js linhas 38, 262, 271, 279 2. **Tamanho de upload reduzido pra 50MB** (era 200MB) - multer linha 84 3. **Validação Zod nos endpoints autenticados** (POST /api/data e /zones) - novo arquivo server/src/schemas/index.js - middleware validate() retorna 400 com top 5 issues 4. **Audit log de ações sensíveis** - nova tabela audit_log + funções db.audit() e db.recentAudit() - 6 endpoints instrumentados (state_set, media_insert/delete, share_create/revoke/zones_update) - novo endpoint GET /api/audit (autenticado) 5. **Catch silencioso de webhook em zona PROIBIDA tratado** - app/diario-bordo.html + server/public/index.html linha 3280 - agora loga erro + exibe toast ao usuário Status final do HANDOFF: - 🔴 Críticos restantes: 1 (CORS — decisão consciente single-tenant, não-acionável) - 🟡 Importantes: 4 itens (testes, vigia reconnect, refator frontend, demais catches) - 🟢 Bom-ter: 5 itens Stack: Node 20 ESM + Express + better-sqlite3 + Docker (Coolify) Single-tenant pessoal · single-file frontend HTML · offline-first Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6.2 KiB
Shivao Cloud
Backend para o Diário de Bordo do Shivao com:
- ☁️ Sincronização dos dados na nuvem (multi-dispositivo)
- 📷 Mídia (fotos, áudios, vídeos) armazenada no servidor
- 🚨 Notificações automáticas (Telegram, e-mail, SMS, WhatsApp, ntfy, webhook) — sem precisar tocar em botão
- 🛡️ Dead-man switch: se o celular perde sinal/bateria enquanto fundeado, o servidor mesmo dispara o alarme
Deploy no Coolify (passo a passo)
1. Subir o código para um repositório Git
Crie um repo no GitHub/GitLab e envie esta pasta. Ou use o "Public Repository" do Coolify direto.
2. No painel Coolify
- + New Resource → Application → Public/Private Repository
- Cole a URL do seu repositório
- Build Pack:
Dockerfile - Port:
3000 - Domain: configure seu domínio (ex:
shivao.seu-dominio.com) — Coolify gera SSL automático
3. Variáveis de ambiente
Em Environment Variables no Coolify, adicione (mínimo):
| Variável | Valor |
|---|---|
BOAT_TOKEN |
string aleatória longa (ex: openssl rand -hex 32) |
TELEGRAM_BOT_TOKEN |
seu token do BotFather |
TELEGRAM_CHAT_IDS |
seu chat ID |
Veja .env.example para todas as opções (e-mail, SMS, etc.). Configure pelo menos um canal.
4. Volume persistente
Em Storages → Persistent Storage:
- Name:
shivao-data - Mount Path:
/data
Sem isso, ao reiniciar o container você perde o banco e as mídias.
5. Deploy
Clique em Deploy. Aguarde o build. Acesse https://shivao.seu-dominio.com — vai aparecer o app.
Configurando o Telegram (5 minutos, recomendado)
- No Telegram, fale com @BotFather →
/newbot - Escolha um nome e username → ele dá um token
- Inicie uma conversa com o bot que você criou (mande qualquer mensagem)
- Acesse
https://api.telegram.org/bot<SEU_TOKEN>/getUpdatesno navegador - No JSON, procure
"chat":{"id":123456789...}— esse é seuTELEGRAM_CHAT_IDS - Cole no Coolify e redeploy
Para receber em grupo: adicione o bot ao grupo, mande uma mensagem, repita o passo 4 (chat ID será negativo).
Configurando ntfy.sh (sem cadastro, push grátis)
- Instale o app ntfy (Android / iOS)
- No app, Subscribe → invente um tópico único e secreto (ex:
shivao-aljg29x71kqp) - Coloque esse mesmo nome em
NTFY_TOPICno Coolify
⚠️ Como ntfy é público, qualquer pessoa que adivinhe o tópico vê suas notificações. Use algo aleatório e longo.
Configurando E-mail SMTP
Gmail (mais comum):
- Ative verificação em 2 etapas na conta Google
- Vá em Senhas de app
- Gere uma senha → use ela em
SMTP_PASS - Configure:
SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_SECURE=false SMTP_USER=seu-email@gmail.com SMTP_PASS=senha-de-app-de-16-caracteres SMTP_FROM=Shivao Alertas <seu-email@gmail.com> SMTP_TO=destinatario@email.com,outro@email.com
Resend, SendGrid, Mailgun: similar, basta seguir o painel deles e usar SMTP.
Configurando Twilio (SMS / WhatsApp — pago)
Apenas se quiser SMS ou WhatsApp realmente automático.
- Crie conta em twilio.com
- Para SMS: compre um número (Brasil custa ~$1/mês + $0.05 por SMS)
- Para WhatsApp: ative o sandbox grátis OU peça aprovação Business
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxx
TWILIO_AUTH_TOKEN=xxxxxxxxxxxxx
TWILIO_FROM_NUMBER=+15551234567
TWILIO_SMS_TO=+5521999998888
TWILIO_WHATSAPP_FROM=whatsapp:+14155238886
TWILIO_WHATSAPP_TO=whatsapp:+5521999998888
Como funciona o Dead-Man Switch
- Quando você toca em "Fundear" no app, ele avisa o servidor
- Enquanto a vigia está ativa, o app envia um heartbeat a cada 30 segundos: "estou vivo, GPS aqui"
- Se o servidor não receber heartbeat por 5 minutos (
HEARTBEAT_TIMEOUT_SECconfigurável), ele assume que algo deu errado (celular morreu, perdeu sinal, app fechou) e dispara o alarme remoto automaticamente para todos os contatos - Quando você levanta âncora normalmente, o app avisa o servidor para parar a vigia
Isso garante que mesmo se você dormir e o celular morrer, alguém vai ser avisado.
Endpoints da API (referência)
Todas requerem header Authorization: Bearer <BOAT_TOKEN> (exceto /api/health).
| Método | Rota | Função |
|---|---|---|
| GET | /api/health |
Status público |
| GET | /api/info |
Canais configurados |
| GET | /api/data |
Baixar todo o estado |
| POST | /api/data |
Enviar todo o estado |
| POST | /api/media |
Upload de arquivo (multipart) |
| GET | /api/media/list |
Lista de mídias |
| GET | /api/media/:id |
Baixar mídia |
| DELETE | /api/media/:id |
Apagar mídia |
| POST | /api/anchor/start |
Iniciar vigia no servidor |
| POST | /api/anchor/heartbeat |
Manter vivo |
| POST | /api/anchor/alarm |
Disparar alarme imediato |
| POST | /api/anchor/stop |
Encerrar vigia |
| GET | /api/anchor/status |
Estado atual |
| POST | /api/test |
Disparar mensagem de teste |
| GET | /api/alarms |
Histórico de alarmes |
| POST | /api/share/create |
Criar link público temporário |
| GET | /api/share/list |
Listar shares ativos |
| DELETE | /api/share/:token |
Revogar share |
| POST | /api/share/position |
Postar posição (boat → server) |
| GET | /share/:token |
Página pública do link (sem auth) |
| GET | /api/share/:token/info |
Info do share (público) |
| GET | /api/share/:token/positions |
Posições do share (público) |
Rodando localmente (dev)
cp .env.example .env
# edite .env
npm install
npm start
Ou com Docker:
docker compose up --build
Acesse http://localhost:3000
Conectando o app
No app (Diário de Bordo), vá em Arquivo → Configurações da Nuvem, preencha:
- Servidor:
https://shivao.seu-dominio.com - Token: o mesmo
BOAT_TOKENque você definiu
Toque em Testar conexão → deve receber a mensagem de teste em todos os canais configurados.
A partir daí o app sincroniza automaticamente.