shivao-projeto/server/README.md
PontualTech / Karlão 5b02feae50 chore: initial commit + security hardening (4 runs squad shivao-melhoria)
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>
2026-04-27 13:24:08 -03:00

184 lines
6.2 KiB
Markdown

# 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
1. **+ New Resource** → **Application****Public/Private Repository**
2. Cole a URL do seu repositório
3. **Build Pack**: `Dockerfile`
4. **Port**: `3000`
5. **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)
1. No Telegram, fale com [@BotFather](https://t.me/BotFather) → `/newbot`
2. Escolha um nome e username → ele dá um **token**
3. Inicie uma conversa com o bot que você criou (mande qualquer mensagem)
4. Acesse `https://api.telegram.org/bot<SEU_TOKEN>/getUpdates` no navegador
5. No JSON, procure `"chat":{"id":123456789...}` — esse é seu `TELEGRAM_CHAT_IDS`
6. 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)
1. Instale o app **ntfy** ([Android](https://play.google.com/store/apps/details?id=io.heckel.ntfy) / [iOS](https://apps.apple.com/us/app/ntfy/id1625396347))
2. No app, **Subscribe** → invente um tópico **único e secreto** (ex: `shivao-aljg29x71kqp`)
3. Coloque esse mesmo nome em `NTFY_TOPIC` no 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)**:
1. Ative **verificação em 2 etapas** na conta Google
2. Vá em [Senhas de app](https://myaccount.google.com/apppasswords)
3. Gere uma senha → use ela em `SMTP_PASS`
4. 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.
1. Crie conta em [twilio.com](https://www.twilio.com)
2. Para SMS: compre um número (Brasil custa ~$1/mês + $0.05 por SMS)
3. 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
1. Quando você toca em **"Fundear"** no app, ele avisa o servidor
2. Enquanto a vigia está ativa, o app envia um **heartbeat** a cada 30 segundos: "estou vivo, GPS aqui"
3. Se o servidor não receber heartbeat por 5 minutos (`HEARTBEAT_TIMEOUT_SEC` configurável), ele assume que algo deu errado (celular morreu, perdeu sinal, app fechou) e **dispara o alarme remoto** automaticamente para todos os contatos
4. 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)
```bash
cp .env.example .env
# edite .env
npm install
npm start
```
Ou com Docker:
```bash
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_TOKEN` que 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.