# turbgate

> 👷 **Técnico instalando Orange Pi novo no cliente?** → [`INSTALADOR.md`](INSTALADOR.md)

Túnel reverso de alta performance — substitui `sctunnel_server` + `sctunnel_client`. Uma única conexão TLS multiplexada (yamux) por OrangePi/Windows, mTLS por cliente, binário Go único. Frota horizontal com round-robin DNS.

| | |
|---|---|
| Entry-point dos clientes | `turbgate.botbox.info:7000` (round-robin) |
| Dashboard live | <https://turbgate.botbox.info/dashboard.html> |
| Página de downloads | <https://turbgate.botbox.info/> |
| Repo | <https://github.com/denoww/turbgate> |

## 🌐 Frota (multi-server)

```
                    turbgate.botbox.info  ← entry-point único, Route53 multivalue (TTL 60s)
                       /          \
              turbgate1.botbox.info   turbgate2.botbox.info   …   turbgateN.botbox.info
              (54.94.23.109)          (54.94.227.38)              (cada um IP fixo)
              sa-east-1               sa-east-1                    qualquer região AWS
```

- **Cliente** conecta em `turbgate.botbox.info:7000` → DNS retorna 1 IP aleatório → conecta no server correspondente
- **Server** responde com seu `public_host` canônico (`turbgateN.botbox.info`) — usado no `update_tunnel_devices` do ERP
- **URL pública** pro device tunelado: `turbgateN.botbox.info:porta` — estável independente do round-robin
- **Cert mTLS** cobre todos os SANs (`turbgate.botbox.info` + cada `turbgateN`) — qualquer server valida pra qualquer cliente
- **CA + admin_token** compartilhados entre os servers (replicados no spinup)

Adicionar/remover server é 1 comando — ver [Fleet management](#-fleet-management) abaixo.

## 🤖 Use com Claude Code

Boa parte da operação é "fala uma frase, o Claude executa". Abra o Claude Code dentro de `~/workspace/turbgate` e mande:

### Fleet management

```text
sobe mais uma máquina
```

Roda `release/spinup_server.sh` — auto-detecta próximo `turbgateN` livre. Cria SG + EIP + EC2 t3.medium em `sa-east-1` (default), provisiona via SSH, copia CA + admin_token de uma máquina existente, emite cert com SAN cobrindo a frota, registra A record no Route53, e adiciona o IP novo ao multivalue `turbgate.botbox.info`. Cliente novo já balanceia nele em ≤60s (TTL DNS). Tempo total: ~3min.

```text
quero ficar com 2 máquinas
```

Lista a frota, mantém os menores (`turbgate1`, `turbgate2`) e roda `teardown_server.sh` nos extras. Avisa antes se houver clientes ativos no server (sugere aguardar ciclo de reconcile pra balancear pro restante). `turbgate1` e `turbgate2` são protegidos — só destroem com `--force`.

```text
lista a frota turbgate
```

Roda `release/list_servers.sh`: tabela com nome, hostname, IP, região, instance, status. Mostra round-robin entry point.

### Frota Orange Pi (clientes)

```text
ache todos orangepi da rede e instale no cliente <id>
```

Varre a sub-rede local, filtra por OUI Allwinner / banner SSH, instala turbgate em todos os Orange Pi candidatos, devolve uma tabela:

| IP | Hostname | Cliente | Status | SSH externo (self-tunnel) |
|---|---|---|---|---|
| 192.168.1.4 | orangepi3-lts | 51 | ✅ instalado | `ssh -p 30013 orangepi@turbgate1.botbox.info` |

Pré-req: `~/.sctunnel/orangepi_password` (chmod 600) com a senha SSH dos OrangePi, AWS CLI logada (lê o `PORTARIA_SERVER_SALT` do S3 sem deixar passar pelo log).

```text
migra essa máquina do sctunnel velho pro turbgate
```

Instala turbgate, valida conexão, mata os `ssh -N -R` do sctunnel antigo, desabilita os crons. Idempotente.

### Build & deploy

```text
faz novo build e sobe
```

Cross-build (linux amd64/arm64 + windows amd64), gera `.deb` (nfpm) + `.zip` (Inno-style), `scp` pra `sctunnel1:/var/www/html/`. URLs `latest` continuam estáveis.

```text
atualiza o turbgated em todos os servers
```

(Só se mexeu em `cmd/turbgated/`.) Reinstala o binário em cada `turbgateN` da frota, restart sequencial — clientes balanceiam pro restante durante a janela de cada restart (~3s/server).

### Bateria de testes (validar robustez do tunnel)

```text
rode bateria de testes no orangepi 192.168.1.5
```

10 cenários (~15min): reboot Orange 3x, restart turbgated 3x, kill -9, network outage 90s, reboot EC2, force tunnel_me=true. Gera relatório em markdown em `/tmp/turbgate-orange-test-<ip>-<timestamp>.md`. Manual: `bash release/test_orange.sh --ip <ip>`. Pra rodar em **todos** os Orange Pi da rede de uma vez: `--all`. Pra pular o teste mais disruptivo (reboot EC2 afeta toda a frota): `--skip-reboot-ec2`.

### Outros prompts úteis

```text
abre o dashboard
```

Te dá a URL com o token preenchido.

```text
smoke test no orangepi 192.168.1.X
```

SSH no Orange, mostra logs, valida self-tunnel.

```text
mostra qual cliente está em cada porta agora
```

Bate em `/turbgate-admin/stats` de cada server da frota e formata.

Playbooks completos em [`CLAUDE.md`](CLAUDE.md).

## 📦 Downloads (sempre **latest**)

Compartilhe estas URLs com clientes/técnicos — atualizadas a cada `faz novo build e sobe`.

Página: <https://turbgate.botbox.info/>

### 🐧 Linux (Debian / Ubuntu / OrangePi)

**1-liner**:

```bash
curl -fsSL https://turbgate.botbox.info/install.sh | sudo bash -s -- <cliente_id>
```

Default aponta pra `turbgate.botbox.info:7000` (round-robin). Pra forçar 1 server específico: `TURBGATE_SERVER_HOST=turbgate2.botbox.info:7000 curl ... | bash -s -- <id>`.

Pacotes diretos:

| arch | URL |
|---|---|
| amd64 | <https://turbgate.botbox.info/turbgate_amd64.deb> |
| arm64 (Orange Pi 3 LTS / 3B) | <https://turbgate.botbox.info/turbgate_arm64.deb> |

### 🪟 Windows 10/11 x64

**Instalador wizard (recomendado)**: 1 click pra zelador.

1. Baixe: <https://turbgate.botbox.info/setup.exe>
2. Duplo-clique no `turbgate-setup.exe`
3. Wizard pede o **código do cliente** (cliente_id) — só digitar e Avançar
4. Aguarda instalação (Npcap + service Windows automático)
5. Pronto

> **Sem token, sem clique-direito, sem cmd.** O instalador é Inno Setup com auto-elevação UAC, igual programa profissional.

Build: gerado pelo workflow [`.github/workflows/windows-installer.yml`](.github/workflows/windows-installer.yml) a cada push em `main`. Validado por [`test-windows-installer.yml`](.github/workflows/test-windows-installer.yml) (silent install + validações + uninstall).

Modo manual (caso do mantenedor — sem wizard, via cmd):
- <https://turbgate.botbox.info/turbgate_windows.zip> → extrair → `install.bat`

### 🟧 MicroSD Orange Pi (criar do zero)

Suporta **Orange Pi 3 LTS** e **Orange Pi 3B** (Allwinner H6/Rockchip RK3566). Imagem é o sistema base; depois disso o instalador 1-liner do turbgate faz o resto.

> **⚠️ É microSD, não pen drive USB**:
>
> O **Orange Pi 3 LTS NÃO boota de USB-A**. Sua boot order do BROM é: SD → SPI → eMMC → USB-OTG (modo FEL). O cartão de boot é sempre **microSD**, plugado no slot microSD da placa.
>
> O que parece "pen drive" no kit é na verdade um **adaptador USB com slot microSD** (leitor de cartão USB). Função dele: gravar o `.img.xz` no microSD usando o notebook (porta USB-A). Depois você tira o microSD do adaptador e pluga **direto no Orange Pi**.
>
> Pra instalar do zero num Orange virgem: pluga **microSD** no slot do Orange, boota o sistema base, roda `setup_orange.sh`, depois `sudo nand-sata-install` pra gravar no eMMC, desliga, **remove microSD**, religa → Orange boota standalone do eMMC interno. O microSD volta livre pro próximo Orange.

**1. Baixe a imagem do Debian (correta pro modelo)**

| Modelo | Link Google Drive | Arquivo | Tamanho |
|---|---|---|---|
| Orange Pi 3 LTS | [drive](https://drive.google.com/drive/folders/1ctuKgHNN9r517tiAv9GGGaR7UYQgZiXP) | `Orangepi3-lts_3.0.8_debian_bullseye_server_linux.img.xz` | 394 MB |
| Orange Pi 3B | [drive](https://drive.google.com/drive/folders/1-mcXPDx1QpE9ZI8oTivmJ1Nd5HfU5nFv) | `Orangepi3b_1.0.8_debian_bookworm_server_linux.img.xz` | 729 MB |

**2. Baixe o gravador**: <https://etcher.balena.io/> (Windows / Mac / Linux)

**3. Grave o microSD (mín. 8 GB real — cartão falso é comum):**

1. Coloca o microSD no adaptador USB e pluga no notebook
2. Abre balenaEtcher → **Flash from file** → escolhe o `.img.xz`
3. **Select target** → escolhe o microSD (confira letra+tamanho — se errar formata seu HD)
4. **Flash!** → ~3 min

> **MicroSD falsificado**: se o tamanho declarado for grande demais ou flash falhar com "no space left", valide com `f3`:
> ```bash
> sudo apt install -y f3
> sudo f3probe --destructive --time-ops /dev/sdX
> ```

**4. Boote o Orange Pi**:

1. **Tira o microSD do adaptador USB e pluga direto no slot microSD do Orange Pi** (não no USB-A — Allwinner H6 não boota de USB-A).
2. Liga na tomada (~1-2 min pra subir)
3. Conecta no SSH:
   - usuário: `orangepi`
   - senha: `orangepi` (padrão da imagem) — você muda no install
4. Confirme conectividade: `ping 8.8.8.8`

**5. Bootstrap completo + instalar turbgate** (1 comando faz **tudo**):

```bash
ssh orangepi@<ip-do-orange>
curl -fsSL https://turbgate.botbox.info/setup_orange.sh \
  | sudo bash -s -- --cliente <ID> --password '<senha_nova>'
```

O instalador NÃO pede mais `PORTARIA_SERVER_SALT` (zelador instala sem token). O endpoint `/portarias/issue_tunnel_cert.json` é público com rate limit por IP — cert mTLS + erp_token vêm automaticamente do response.

O `setup_orange.sh` faz, em uma única passada:

- Troca senha do `orangepi` (e bloqueia login direto do `root`)
- Configura teclado layout pt-BR (Generic 105-key + Brazilian)
- Locale `pt_BR.UTF-8` + timezone `America/Sao_Paulo`
- Hostname `orange-cliente<id>` (identifica na rede)
- `apt update && upgrade` (pula com `--skip-upgrade` pra ir mais rápido)
- Instala turbgate via `install.sh`

Ou via Claude Code (mais cômodo, varre rede automaticamente):

```text
ache todos orangepi da rede e instale no cliente <id>
```

Versão **mínima** (mantém senha default `orangepi`, só instala turbgate):

```bash
curl -fsSL https://turbgate.botbox.info/install.sh | sudo bash -s -- <cliente_id>
```

**6. Gravar no eMMC interno** (Orange standalone, sem microSD plugado):

Conectado por SSH no Orange (que está bootado do microSD), rode:

```bash
sudo nand-sata-install
```

No menu, escolha:
1. **"2 Boot from eMMC - system on eMMC"** → Enter
2. Warning **"This script will erase your eMMC. Continue?"** → Enter (Yes)
3. **"Select filesystem type for eMMC"** → **"1 ext4"** (Orange Pi 3 LTS) ou **"5 btrfs"** (Orange Pi 3B) → Enter
4. Aguarda barra de progresso (~3-5min) + "Cleaning up"
5. Mensagem final → Enter

> O `nand-sata-install` usa `dialog` em `/dev/tty` direto, então **não dá pra automatizar via SSH expect** — precisa do passo manual interativo. Leva ~1min do seu tempo. Use `ssh -t orangepi@<ip> 'sudo nand-sata-install'` pra forçar TTY.

Após concluir:
1. `sudo poweroff`
2. **Remova o microSD do Orange Pi** (importante — senão continua bootando dele)
3. Religue na tomada — Orange boota standalone do eMMC com a imagem nova + turbgate já configurado

O microSD volta livre pra montar o próximo Orange Pi.

### 🧬 Clonar microSDs (distribuição em massa)

Quando você tem 1 microSD já configurado e funcionando, pode clonar pra outros microSDs — preservando U-Boot SPL (offset 8 KiB, fora da partição) e UUID do rootfs.

**Pré-req**: 2 microSDs em adaptadores USB plugados no notebook (origem com Orange Pi + destino com **≥ 4 GB real**).

```bash
bash installer/orangepi/clone_cartao.sh
```

Auto-detecta fonte/destino, pede confirmação. Flags:

| Flag | Uso |
|---|---|
| `--src /dev/sdX` | fonte explícita |
| `--dst /dev/sdY` | destino explícito |
| `--img ~/orange.img` | exporta `.img` no final pra flashar em vários microSDs depois |
| `--yes` | pula confirmação (CI) |

Ou via Claude Code:

```text
clona o microSD do orange pi
```

> **NUNCA** `dd if=/dev/sdX1 of=/dev/sdY` (clone só da partição) — não boota porque o U-Boot SPL do Allwinner H6 vive no offset 8 KiB **do disco cru**, antes da primeira partição. O `clone_cartao.sh` faz certo.

### 📊 Dashboard

<https://turbgate.botbox.info/dashboard.html>

Precisa do `TURBGATE_ADMIN_TOKEN` (vive em `/etc/turbgate/admin_token` em qualquer server da frota; salvo em localStorage no primeiro acesso). Mostra sessões ativas, devices tunelados (incluindo self-tunnel), bytes in/out, uptime.

### 🔢 Checksums

<https://turbgate.botbox.info/checksums.txt>

## 🛠️ Fleet management (manual)

Os 3 prompts da seção [Use com Claude Code](#fleet-management) chamam estes scripts:

```bash
# subir nova máquina (auto-detecta próximo turbgateN livre)
bash release/spinup_server.sh

# subir em região específica / nome forçado
bash release/spinup_server.sh --region us-east-1 --name turbgate3

# listar a frota inteira
bash release/list_servers.sh

# destruir uma máquina (DNS + EC2 + EIP + SG + key pair)
bash release/teardown_server.sh --name turbgate3

# destruir master (turbgate1/turbgate2 — protegido)
bash release/teardown_server.sh --name turbgate1 --force
```

Pré-req: `~/turbgate1.pem` (chave do server source, usado pra copiar CA + admin_token), AWS CLI com perm em EC2 + Route53, `dist/turbgated_linux_amd64` atual.

### O que o spinup faz automaticamente

1. SG `turbgateN` com regras 22/80/443/7000/30000-58000 inbound
2. EIP fixo + EC2 t3.medium Ubuntu 24.04 (associa EIP)
3. SSH provisiona: `apt install nginx`, copia binário Go, copia CA + admin_token do server source
4. Re-emite cert do servidor com SANs cobrindo `turbgate.botbox.info` + todos os `turbgateN.botbox.info` existentes
5. Escreve `/etc/turbgate/server.yaml` com `public_host: turbgateN.botbox.info`
6. systemd unit + nginx admin proxy (`/turbgate-admin/{issue,stats}`)
7. Route53: A record `turbgateN.botbox.info` → EIP, e adiciona o EIP ao multivalue `turbgate.botbox.info`
8. Cliente novo já balanceia nele em até 60s (TTL DNS)

### O que o teardown faz

1. Remove A record `turbgateN.botbox.info` do Route53
2. Remove o IP do multivalue `turbgate.botbox.info` (clientes deixam de ser balanceados pra ele)
3. Termina EC2, libera EIP, deleta SG e key pair AWS
4. PEM local em `~/turbgateN.pem` é preservada (apague manualmente se quiser)

## ⚖️ Comparado ao sctunnel antigo

| | sctunnel | turbgate |
|---|---|---|
| Transporte | 1 processo `ssh -N -R` por device | 1 conn TLS+yamux por OrangePi |
| Auth | PEM compartilhado | mTLS por cliente, com revogação |
| Reconnect | `ServerAliveInterval=20s` | backoff exponencial < 2s |
| Limite | ~50k portas × N proc SSH | 1 TCP/Orange (50k+ frotas/server) |
| Distribuição | bash + Python + Scapy | binário Go único, .deb + .zip |
| Telemetria | só `tunnel_address` no ERP | dashboard live + push opcional pro ERP |
| Acesso remoto Orange | tunnel SSH manual | self-tunnel automático rotacionado |
| **Escala horizontal** | manual (sem scripts) | `sobe mais uma máquina` em 3 min |
| **Latência (Brasil)** | ~1.2s TTFB (Virginia) | ~230ms TTFB cold, 175ms warm (SP) |

ERP integration (`/portarias/get_tunnel_devices`, `/portarias/update_tunnel_devices`) é **idêntica** ao sctunnel — `tunnel_address` continua `host:porta`. Endpoint novo: `/portarias/issue_tunnel_cert.json` (mTLS por cliente).

## 📚 Documentação técnica

| | |
|---|---|
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | desenho do control + data plane |
| [docs/PROTOCOL.md](docs/PROTOCOL.md) | mensagens JSON-line do mux |
| [docs/MIGRATION.md](docs/MIGRATION.md) | rodar paralelo com sctunnel velho |
| [docs/RAILS_INTEGRATION.md](docs/RAILS_INTEGRATION.md) | endpoints novos no seucondominio (mTLS issue + telemetria push) |
| [docs/GOLIVE_CHECKLIST.md](docs/GOLIVE_CHECKLIST.md) | checklist pre-go-live + plano de rollback (1ª instalação em cliente real) |
| [CLAUDE.md](CLAUDE.md) | playbooks completos pros prompts acima |

## 📁 Estrutura

```
turbgate/
├── cmd/{turbgated,turbgate}/   daemon e cliente
├── internal/                   pacotes do core
│   ├── control/                control plane (mTLS+yamux+JSON-line)
│   ├── admin/                  HTTP loopback (issue cert + stats)
│   ├── auth/                   CA ECDSA
│   ├── ports/                  port allocator (sticky por device)
│   ├── proxy/                  data plane bidi
│   ├── erp/                    cliente HTTP do seucondominio
│   ├── scan/                   ARP/neighbor discovery
│   └── config/                 YAML + env
├── installer/
│   ├── linux/install.sh        1-liner curl
│   ├── debian/                 nfpm + systemd
│   ├── windows/                install.bat + uninstall.bat
│   └── dashboard/              HTML do dashboard
├── release/
│   ├── build_packages.sh       cross-build amd64/arm64 + .deb + zip
│   ├── upload.sh               scp dist/* pro nginx
│   ├── spinup_server.sh        +1 server na frota (Route53 + EC2 + provisão)
│   ├── teardown_server.sh      −1 server (DNS + EC2 + EIP + SG)
│   ├── list_servers.sh         snapshot da frota
│   └── server_install.sh       reinstala turbgated nos servers existentes
└── docs/                       arquitetura, protocolo, migração, Rails
```

## 🧪 Desenvolvimento

```bash
go test ./... -race    # e2e roda em loopback (CA, mTLS, yamux, proxy)
make build             # binários em dist/
```

Pré-req pra build: Go 1.22+, `nfpm` no PATH (`GOBIN=$HOME/.local/bin go install github.com/goreleaser/nfpm/v2/cmd/nfpm@latest`).

## 📈 Status

Em produção desde 2026-05-08. 2 servers ativos (`turbgate1` + `turbgate2` em sa-east-1) com round-robin via `turbgate.botbox.info`. 1 piloto rodando em OrangePi 3 LTS (cliente 51), tunelando câmera Hikvision facial real. Dashboard live, CI verde. Roadmap: telemetria push pro ERP (contrato pronto, falta o lado Rails).
