Share
## https://sploitus.com/exploit?id=1FF17BFB-42C6-5866-9667-464BD03C4921
# Aerobi POC — Simulação local de monitoramento de câmeras

Laboratório 100% local (sem Cloudflare, sem câmeras reais) para validar a
arquitetura futura de monitoramento multi-aeródromo: VPN privada com
**Headscale + Tailscale**, streaming via **MediaMTX**, orquestração em
**NestJS**, player web em **React + hls.js**.

> Tudo sobe com `docker compose up`. Câmeras são simuladas pelo `ffmpeg`
> (color bars + áudio sintético). Nenhum hardware é necessário.

---

## Sumário

- [Arquitetura](#arquitetura)
- [Fluxo do vídeo](#fluxo-do-v%C3%ADdeo)
- [Papel de cada serviço](#papel-de-cada-servi%C3%A7o)
- [Como subir o ambiente](#como-subir-o-ambiente)
- [Como testar streams](#como-testar-streams)
- [Como validar a VPN](#como-validar-a-vpn)
- [Como validar HLS](#como-validar-hls)
- [Como acessar o frontend](#como-acessar-o-frontend)
- [Como debugar MediaMTX](#como-debugar-mediamtx)
- [Como debugar Headscale](#como-debugar-headscale)
- [Estrutura do projeto](#estrutura-do-projeto)
- [Limites do POC](#limites-do-poc)

---

## Arquitetura

```
                        +-----------------------------+
                        |          Browser            |
                        |  http://localhost:5173      |
                        |  http://localhost:3001      |
                        +-------------+---------------+
                                      |
                       (rede do host) |
                                      v
   +---------------------------- VPS-NET ----------------------------+
   |                                                                 |
   |  +-------------+   +----------+   +-----------------------+    |
   |  |  frontend   |   | postgres |   |    tailscale-vps      |    |
   |  |   (nginx)   |   |          |   | (TUN, hostname vps-1) |    |
   |  +------+------+   +-----+----+   +-----------+-----------+    |
   |         |                |                    |                |
   |         |          (TCP) |                    | network_mode   |
   |         |                |                    v                |
   |         |          +-----+----+      +------------------+      |
   |         +--------->|  postgres| mediamtx --->  nest-api  ---> browser (hls.js)
                            ^   ^         (proxy)
                            |   |
                       cam-1..cam-4
```

1. `ffmpeg` gera 4 streams `testsrc` em loop infinito e publica via RTSP
   no MediaMTX (`rtsp://127.0.0.1:8554/cam-N`).
2. MediaMTX remuxa para HLS low-latency e expõe `http://aerodrome-1:8888/cam-N/index.m3u8`
   **somente na tailnet** (porta nunca é publicada no host).
3. NestJS recebe `GET /streams/:cameraId/index.m3u8` do browser, busca a
   playlist no MediaMTX via tailnet e faz **passthrough HTTP** (sem ffmpeg,
   sem transcoding) — apenas pipe de bytes.
4. hls.js no browser consome o m3u8 do NestJS como qualquer HLS comum.

---

## Papel de cada serviço

| serviço | papel | observações |
|---|---|---|
| `headscale` | control plane VPN (signaling + ACL + MagicDNS) | nunca processa tráfego de dados; só coordena |
| `headscale-bootstrap` | run-once: cria user `aerobi-poc` e duas pre-auth keys em `/shared/keys.env` | encerra após executar |
| `tailscale-vps` | nó Tailscale "VPS" (hostname `vps-1`) | publica a porta 3000 do nest-api ao host |
| `tailscale-aerodrome` | nó Tailscale "Aeródromo" (hostname `aerodrome-1`) | privado: nenhuma porta exposta ao host |
| `postgres` | banco do nest-api | uso interno na vps-net |
| `nest-api` | API REST + proxy HLS | **não** processa vídeo; apenas pipe HTTP |
| `mediamtx` | RTSP→HLS | escuta nas interfaces do tailscale-aerodrome |
| `ffmpeg-fake-camera` | gera 4 streams `testsrc` e publica RTSP | substituível por câmeras reais no futuro |
| `frontend` | nginx servindo o build Vite/React + hls.js | bate em `http://localhost:3001` |

---

## Como subir o ambiente

Pré-requisitos: Docker (≥ 24) e Docker Compose v2.

```bash
cd aerobi-poc
cp .env.example .env        # opcional — default já funciona
docker compose build
docker compose up -d
```

Os tailscales só ficam prontos depois que o `headscale-bootstrap` termina.
Acompanhe:

```bash
docker compose logs -f headscale headscale-bootstrap tailscale-vps tailscale-aerodrome
```

Quando aparecer `Success.` em ambos os tailscales, a tailnet está formada.

Para derrubar tudo (preservando volumes):

```bash
docker compose down
```

Para reset total (apaga banco, estado do tailscale, tudo):

```bash
docker compose down -v
```

---

## Como testar streams

Liste os aeródromos:

```bash
curl -s http://localhost:3001/aerodromes | jq
```

Liste as câmeras de um aeródromo:

```bash
curl -s http://localhost:3001/aerodromes/aero-1/cameras | jq
```

Cada item tem `streamUrl` apontando para o proxy do NestJS, p.ex.
`http://localhost:3001/streams/cam-1/index.m3u8`.

Pegue a playlist HLS via proxy:

```bash
curl -s http://localhost:3001/streams/cam-1/index.m3u8
```

Você deve receber um manifest m3u8 com `#EXT-X-VERSION:9` e segmentos
`.mp4`/`.m4s` (low-latency HLS).

---

## Como validar a VPN

Listar nós conhecidos pelo Headscale:

```bash
docker compose exec headscale headscale nodes list
```

Esperado: `vps-1` e `aerodrome-1`, ambos online.

Status do tailscale na VPS:

```bash
docker compose exec tailscale-vps tailscale status
```

Ping do tailscale-vps até o aeródromo (via interface tailnet):

```bash
docker compose exec tailscale-vps tailscale ping aerodrome-1
```

Resolução MagicDNS dentro do nest-api (que herda a stack do tailscale-vps):

```bash
docker compose exec tailscale-vps wget -qO- http://aerodrome-1:8888/cam-1/index.m3u8 | head
```

---

## Como validar HLS

Direto no MediaMTX (via tailnet, a partir do nó da VPS):

```bash
docker compose exec tailscale-vps wget -qO- http://aerodrome-1:8888/cam-1/index.m3u8 | head
```

Via proxy no NestJS (a partir do host):

```bash
curl -s http://localhost:3001/streams/cam-1/index.m3u8 | head
```

Os dois devem retornar a mesma playlist.

Para reproduzir manualmente fora do browser:

```bash
ffplay -loglevel warning http://localhost:3001/streams/cam-1/index.m3u8
# ou
vlc http://localhost:3001/streams/cam-1/index.m3u8
```

---

## Como acessar o frontend

`http://localhost:5173`

1. Selecione um aeródromo no dropdown.
2. As câmeras do aeródromo aparecem em grid.
3. Cada player faz request para `http://localhost:3001/streams//index.m3u8`,
   que internamente sai pela tailnet até o MediaMTX.

> **Importante**: o JS do frontend usa `VITE_API_URL` (default `http://localhost:3001`).
> O valor é gravado no build — ajuste no `.env` antes de `docker compose build`
> se for acessar de outra máquina.

---

## Como debugar MediaMTX

Logs:

```bash
docker compose logs -f mediamtx
```

Ver paths ativos (do dentro do container — porta só na tailnet):

```bash
docker compose exec tailscale-vps wget -qO- http://aerodrome-1:9997/v3/paths/list
```

(Habilite `api: yes` no `mediamtx.yml` se quiser usar o endpoint admin.)

Ver o que ffmpeg está publicando:

```bash
docker compose logs -f ffmpeg-fake-camera
docker compose exec ffmpeg-fake-camera tail -f /tmp/cam-1.log
```

Problemas comuns:

- **Player fica preto**: a primeira playlist HLS pode demorar 3–5s para
  ter segmentos. Recarregue.
- **404 no proxy**: verifique se ffmpeg está conectado. Veja
  `docker compose logs mediamtx` por linhas tipo `[RTSP] [conn] opened`.

---

## Como debugar Headscale

Healthcheck (a porta `80` do container fica exposta no host como `8080`):

```bash
curl -s http://localhost:8080/health
```

Logs:

```bash
docker compose logs -f headscale
```

Listar usuários e nós:

```bash
docker compose exec headscale headscale users list
docker compose exec headscale headscale nodes list -o json | jq '.[] | {hostname, online, ips: .ip_addresses}'
```

Recriar pre-auth keys (caso `keys.env` tenha sido perdido):

```bash
docker compose down
docker volume rm aerobi-poc_headscale-shared
docker compose up -d
```

ACLs estão em `headscale/acl.hujson` (tudo permite tudo no POC). Para
testar mudanças sem reiniciar o headscale:

```bash
docker compose exec headscale headscale policy check
```

---

## Estrutura do projeto

```
aerobi-poc/
├── docker-compose.yml
├── .env.example
├── README.md
├── plano.md
├── headscale/
│   ├── config.yaml
│   ├── acl.hujson
│   └── bootstrap.sh             # cria user + pre-auth keys
├── tailscale-node/              # imagem comum aos dois clients tailscale
│   ├── Dockerfile
│   └── entrypoint.sh
├── nest-api/
│   ├── Dockerfile
│   ├── package.json
│   ├── tsconfig.json
│   ├── nest-cli.json
│   └── src/
│       ├── main.ts
│       ├── app.module.ts
│       ├── common/health.controller.ts
│       ├── aerodromes/{entity, service, controller, module}.ts
│       ├── cameras/{entity, service, module}.ts
│       ├── streams/{controller, hls-proxy.service, module}.ts
│       └── seed/seed.service.ts
├── frontend/
│   ├── Dockerfile
│   ├── nginx.conf
│   ├── package.json
│   ├── tsconfig.json
│   ├── vite.config.ts
│   ├── index.html
│   └── src/
│       ├── main.tsx
│       ├── App.tsx
│       ├── styles.css
│       ├── api/client.ts
│       ├── components/{Player, AerodromeSelector, CameraGrid}.tsx
│       └── pages/Home.tsx
└── aerodrome-node/
    ├── mediamtx/mediamtx.yml
    └── ffmpeg/
        ├── Dockerfile
        └── entrypoint.sh
```

---

## Limites do POC

Fora do escopo (intencionalmente):

- Cloudflare, TLS, autenticação real
- Câmeras IP reais, ONVIF, descoberta automática
- NVR / replay / persistência de gravações
- Escala horizontal real (vários aeródromos físicos)
- CI/CD e testes automatizados