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>
184 lines
6.2 KiB
Markdown
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.
|