Skip to content

Commit a984511

Browse files
Merge pull request #7 from pathbit/feat/acesso-remoto-e-portas
feat: acesso remoto documentado e cada gateway na sua porta no host
2 parents 67b15b2 + 1865598 commit a984511

8 files changed

Lines changed: 364 additions & 12 deletions

File tree

‎README.md‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,17 @@ lado. O mesmo vale para os gateways: cada um tem a sua.
109109

110110
| Serviço | Porta interna | Publicada no host |
111111
| :--- | :--- | :--- |
112-
| 9Router | `20128` | `20128` |
113-
| OmniRoute | `20128` | `20129` |
114-
| LiteLLM | `4000` | `20130` |
112+
| 9Router | `20128` | `8081` |
113+
| OmniRoute | `20128` | `8082` |
114+
| LiteLLM | `4000` | `8083` |
115115
| 9RTKSync (painel) | `9090` | `9091` |
116116
| OminiRTkSync (painel) | `9090` | `9092` |
117117
| LiteLlmRTKSync (painel) | `9090` | `9093` |
118118

119+
A stack dos artigos (`claudegravity`) fica com a **`20128`**, a porta padrão do
120+
9Router. As stacks dos repositórios saem dessa faixa de propósito: assim você
121+
roda o artigo e os três sincronizadores ao mesmo tempo, sem conflito.
122+
119123
Tudo preso a `127.0.0.1`: o gateway carrega credenciais reais e não deve ficar
120124
acessível na rede local. Para mudar qualquer uma, altere o lado esquerdo do
121125
mapeamento no compose — o lado direito é a porta interna, que o processo escuta.
@@ -138,7 +142,9 @@ services:
138142
container_name: claudegravity-router
139143
restart: unless-stopped
140144
ports:
141-
- "127.0.0.1:20128:20128"
145+
# 20128 dentro do container; 8081 no host, para nao disputar a porta
146+
# padrao do 9Router com a stack do artigo.
147+
- "127.0.0.1:8081:20128"
142148
volumes:
143149
- 9router_data:/app/data
144150
healthcheck:

‎docker-compose.example.yml‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ services:
66
container_name: 9router
77
restart: unless-stopped
88
ports:
9-
- "127.0.0.1:20128:20128"
9+
# Porta interna 20128 (padrao do 9Router); publicada em 8081 no host.
10+
# A 20128 do host fica para a stack do artigo (claudegravity), que usa
11+
# a porta padrao -- assim as duas rodam juntas sem colidir.
12+
- "127.0.0.1:8081:20128"
1013
environment:
1114
- DATA_DIR=/app/data
1215
- PORT=20128

‎docs/wiki/Home.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ republishes these pages automatically. Editing a page directly here will be over
2121
| [Authentication](Authentication) | Credentials, headless mode, break-glass recovery |
2222
| [Logging](Logging) | Persistent file log, rotation, 30-day retention |
2323
| [Architecture](Architecture) | How the sync engine talks to the 9Router database |
24+
| [Remote Access](Remote-Access) | Tunnel, Tailscale, and what has to be on before either |
2425
| [Egress and Multi-Session](Egress-And-Multi-Session) | Why several accounts sharing one outbound address is the risk, and what the gateway models |
2526
| [Troubleshooting](Troubleshooting) | Concrete symptoms and what they actually mean |
2627
| [Upstream Fixes](Upstream-Fixes) | Bugs found in the gateways and the patches sent upstream |

‎docs/wiki/Installation.md‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ services:
2323
container_name: 9router
2424
restart: unless-stopped
2525
ports:
26-
- "127.0.0.1:20128:20128"
26+
# 20128 dentro do container; 8081 no host, para nao disputar a porta
27+
# padrao do 9Router com a stack do artigo.
28+
- "127.0.0.1:8081:20128"
2729
environment:
2830
- DATA_DIR=/app/data
2931
- PORT=20128
@@ -169,12 +171,16 @@ Same for the gateways.
169171

170172
| Service | Inside | Published |
171173
| :--- | :--- | :--- |
172-
| 9Router | `20128` | `20128` |
173-
| OmniRoute | `20128` | `20129` |
174-
| LiteLLM | `4000` | `20130` |
174+
| 9Router | `20128` | `8081` |
175+
| OmniRoute | `20128` | `8082` |
176+
| LiteLLM | `4000` | `8083` |
175177
| 9RTKSync panel | `9090` | `9091` |
176178
| OminiRTkSync panel | `9090` | `9092` |
177179
| LiteLlmRTKSync panel | `9090` | `9093` |
178180

181+
The article stack (`claudegravity`) keeps **`20128`**, 9Router's default port.
182+
The repository stacks stay out of that range on purpose, so you can run the
183+
article and all three synchronizers at once without a conflict.
184+
179185
All bound to `127.0.0.1`: the gateway holds real credentials and should not be
180186
reachable from the local network.

‎docs/wiki/Remote-Access.md‎

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
# Remote access: tunnel, Tailscale and what must come first
2+
3+
*(Versão em português ao final.)*
4+
5+
Reaching the gateway from another machine — your laptop away from home, a
6+
teammate, a phone — has three usual answers. They differ in who can reach you,
7+
and the order in which you turn things on decides whether that is safe.
8+
9+
> **This page is about getting IN.** Sending traffic OUT through a chosen
10+
> address, so each account has its own source IP, is a different problem with a
11+
> different answer — see [Egress and Multi-Session](Egress-And-Multi-Session).
12+
> Tailscale appears in both pages doing two unrelated jobs.
13+
14+
---
15+
16+
## Read this before enabling anything
17+
18+
The stacks in this repository ship with:
19+
20+
```yaml
21+
- REQUIRE_API_KEY=false
22+
- REQUIRE_LOGIN=false
23+
```
24+
25+
That is **safe while the port is bound to `127.0.0.1`**, which is how every
26+
compose here publishes it: only your own machine can reach it, and demanding a
27+
password from yourself on localhost adds friction without adding safety.
28+
29+
The moment you expose the gateway, that reasoning inverts. 9Router's dashboard
30+
says it in a yellow banner, and it is not decoration:
31+
32+
> *Enable "Require login" and set a custom password before activating the tunnel.*
33+
34+
Two things make this sharper than it looks:
35+
36+
1. **`/v1` is a public prefix.** In `src/dashboardGuard.js`, the gateway lists its
37+
own inference endpoints — `/v1`, `/v1beta`, `/api/v1`, `/codex`, `/responses` —
38+
as public, because they are meant to be called by your coding tools without a
39+
dashboard session. With `REQUIRE_API_KEY=false` and the gateway on a public URL,
40+
**anyone who learns the URL can spend your accounts**.
41+
2. **The gateway's admin routes fall open too.** Its `/api/settings`, `/api/keys`,
42+
`/api/providers` and the rest of the gateway's admin surface are protected
43+
*only when `requireLogin` is on*.
44+
With it off, an exposed gateway hands over its own configuration.
45+
46+
So the order is not a preference:
47+
48+
```
49+
1. REQUIRE_LOGIN=true + a password you chose
50+
2. REQUIRE_API_KEY=true + a key for your tools
51+
3. only then, the tunnel or Tailscale
52+
```
53+
54+
---
55+
56+
## Option 1 — Cloudflare tunnel (built into 9Router)
57+
58+
The dashboard's **API Endpoint** screen has a `Tunnel` button. It registers a
59+
Cloudflare quick tunnel and gives you a public `https://…trycloudflare.com`
60+
address that reaches your gateway without opening any port on your router.
61+
62+
**When it fits:** you need a URL reachable from anywhere, including devices you
63+
do not control, and you accept that the address is public to whoever has it.
64+
65+
**What to know:**
66+
67+
- The URL is **public**. There is no allow-list — the only thing between the
68+
internet and your accounts is `REQUIRE_LOGIN` and `REQUIRE_API_KEY`.
69+
- The address changes every time the tunnel is re-enabled, unless you bring your
70+
own named Cloudflare tunnel.
71+
- The dashboard blocks the button while login is off — but that gate lives in the
72+
screen. The gateway's own `POST /api/tunnel/enable` does not re-check it, so a script or an
73+
extension can enable the tunnel while login is still off. Set the two flags
74+
first and the question does not arise.
75+
76+
---
77+
78+
## Option 2 — Tailscale (built into 9Router, and the one to prefer)
79+
80+
The same screen has a `Tailscale` button, which installs and connects the
81+
daemon. Your machine joins your private tailnet and the gateway becomes
82+
reachable at a `100.x.y.z` address, or at a MagicDNS name like
83+
`http://your-host:20128`.
84+
85+
**When it fits:** almost always. Only devices you enrolled in your tailnet can
86+
reach the gateway — the address is not public, and there is nothing for a
87+
stranger to find.
88+
89+
**Setting it up by hand**, which is also how you do it for OmniRoute and LiteLLM:
90+
91+
```bash
92+
# 1. On the host that runs the gateway
93+
curl -fsSL https://tailscale.com/install.sh | sh
94+
sudo tailscale up
95+
96+
# 2. Find the address it received
97+
tailscale ip -4 # e.g. 100.101.102.103
98+
99+
# 3. Publish the gateway on the tailnet interface instead of loopback
100+
# (in the compose, replace 127.0.0.1 with the tailnet address)
101+
ports:
102+
- "100.101.102.103:20128:20128"
103+
104+
# 4. From another device already in the tailnet
105+
curl http://100.101.102.103:20128/v1/models
106+
```
107+
108+
Binding to the tailnet address rather than `0.0.0.0` matters: `0.0.0.0` also
109+
exposes the gateway to the local network — the café Wi-Fi, the office VLAN —
110+
which is exactly what you were avoiding.
111+
112+
**MagicDNS** makes this readable: with it on, `http://your-host:20128` works from
113+
any device in the tailnet, and the address survives a change of IP.
114+
115+
---
116+
117+
## Option 3 — your own reverse proxy
118+
119+
A VPS with Caddy or nginx in front, TLS terminated there, Basic Auth or mTLS on
120+
top. More work, and the only option that lets you put your own authentication
121+
layer in front of the gateway instead of relying on its flags.
122+
123+
Worth it when several people share one gateway and you want access logs and
124+
revocation per person — neither of which the gateway's own login gives you.
125+
126+
---
127+
128+
## Which one
129+
130+
| | Tunnel | Tailscale | Reverse proxy |
131+
| :--- | :--- | :--- | :--- |
132+
| Who can reach it | anyone with the URL | only your tailnet | whoever you let through |
133+
| Setup | one button | one button, or 3 commands | real work |
134+
| Stable address | no (unless named) | yes (MagicDNS) | yes |
135+
| Works for OmniRoute / LiteLLM | no built-in button | **yes**, same recipe | yes |
136+
| Sensible default | for a one-off demo | **for everyday use** | shared or audited setups |
137+
138+
---
139+
140+
## After exposing it, check what you exposed
141+
142+
```bash
143+
# From another device, WITHOUT credentials — both should refuse
144+
curl -si https://<your-address>/v1/models | head -1 # expect 401
145+
curl -si https://<your-address>/ | head -1 # expect 401 or a login page
146+
147+
# The synchronizer panel should not be exposed at all
148+
curl -si https://<your-address>:9091/ | head -1 # expect connection refused
149+
```
150+
151+
The panel of this synchronizer has no reason to leave the machine: it reads the
152+
gateway's database and shows credentials' health. Keep its port on `127.0.0.1`
153+
and reach it through the same tunnel or tailnet you use for everything else.
154+
155+
---
156+
157+
# Em português
158+
159+
Alcançar o gateway de outra máquina tem três respostas usuais. Elas diferem em
160+
**quem consegue chegar até você**, e a ordem em que você liga as coisas decide se
161+
isso é seguro.
162+
163+
> **Esta página é sobre entrar.** Fazer o tráfego **sair** por um endereço
164+
> escolhido, para que cada conta tenha o seu IP, é outro problema — veja
165+
> [Egress and Multi-Session](Egress-And-Multi-Session). O Tailscale aparece nas
166+
> duas páginas fazendo trabalhos diferentes.
167+
168+
## Leia antes de ligar qualquer coisa
169+
170+
As stacks deste repositório sobem com `REQUIRE_API_KEY=false` e
171+
`REQUIRE_LOGIN=false`. Isso é **seguro enquanto a porta está presa em
172+
`127.0.0.1`** — só a sua máquina alcança, e exigir senha de si mesmo no
173+
localhost acrescenta atrito sem acrescentar segurança.
174+
175+
No instante em que o gateway é exposto, o raciocínio se inverte. O painel do
176+
9Router avisa, e o aviso não é decorativo:
177+
178+
> *Enable "Require login" and set a custom password before activating the tunnel.*
179+
180+
Dois detalhes tornam isso mais sério do que parece:
181+
182+
1. **`/v1` é prefixo público.** Em `src/dashboardGuard.js`, os endpoints de
183+
inferência do gateway (`/v1`, `/v1beta`, `/api/v1`, `/codex`, `/responses`)
184+
são públicos por projeto — é assim que as ferramentas de código chamam o gateway
185+
sem sessão. Com `REQUIRE_API_KEY=false` e o gateway numa URL pública,
186+
**qualquer um que descubra o endereço gasta as suas contas**.
187+
2. **As rotas administrativas do gateway também caem.** As dele — `/api/settings`,
188+
`/api/keys`, `/api/providers` e afins do gateway — só são protegidas **quando `requireLogin` está
189+
ligado**. Com ele desligado, um gateway exposto entrega a própria
190+
configuração.
191+
192+
A ordem, portanto, não é preferência:
193+
194+
```
195+
1. REQUIRE_LOGIN=true + uma senha escolhida por você
196+
2. REQUIRE_API_KEY=true + uma chave para as suas ferramentas
197+
3. só então, o túnel ou o Tailscale
198+
```
199+
200+
## Opção 1 — túnel Cloudflare (nativo do 9Router)
201+
202+
A tela **API Endpoint** tem o botão `Tunnel`. Ele registra um quick tunnel da
203+
Cloudflare e devolve um endereço público `https://…trycloudflare.com` que
204+
alcança o gateway sem abrir porta nenhuma no seu roteador.
205+
206+
**Quando serve:** você precisa de uma URL alcançável de qualquer lugar,
207+
inclusive de dispositivos que você não controla, e aceita que o endereço seja
208+
público para quem o tiver.
209+
210+
**O que saber:** a URL é pública e não há lista de permissão — entre a internet
211+
e as suas contas existem apenas `REQUIRE_LOGIN` e `REQUIRE_API_KEY`. O endereço
212+
muda a cada reativação, a menos que você use um túnel nomeado seu. E o bloqueio
213+
do botão enquanto o login está desligado vive **na tela**: o `POST
214+
/api/tunnel/enable` do gateway não reavalia a condição. Ligue as duas variáveis antes e a
215+
questão não se coloca.
216+
217+
## Opção 2 — Tailscale (nativo do 9Router, e o que preferir)
218+
219+
A mesma tela tem o botão `Tailscale`, que instala e conecta o daemon. A sua
220+
máquina entra na sua tailnet e o gateway passa a ser alcançável num endereço
221+
`100.x.y.z`, ou num nome MagicDNS como `http://seu-host:20128`.
222+
223+
**Quando serve:** quase sempre. Só os dispositivos que você cadastrou alcançam o
224+
gateway — o endereço não é público e não há o que um estranho descubra.
225+
226+
**Configurando à mão**, que é também como se faz no OmniRoute e no LiteLLM:
227+
228+
```bash
229+
# 1. No host que roda o gateway
230+
curl -fsSL https://tailscale.com/install.sh | sh
231+
sudo tailscale up
232+
233+
# 2. Descubra o endereço recebido
234+
tailscale ip -4 # ex.: 100.101.102.103
235+
236+
# 3. Publique o gateway na interface da tailnet, em vez do loopback
237+
ports:
238+
- "100.101.102.103:20128:20128"
239+
240+
# 4. De outro dispositivo já na tailnet
241+
curl http://100.101.102.103:20128/v1/models
242+
```
243+
244+
Prender no endereço da tailnet em vez de `0.0.0.0` importa: `0.0.0.0` também
245+
expõe o gateway à rede local — o Wi-Fi do café, a VLAN do escritório — que é
246+
justamente o que se queria evitar.
247+
248+
## Opção 3 — proxy reverso próprio
249+
250+
Um VPS com Caddy ou nginx na frente, TLS terminado ali, Basic Auth ou mTLS por
251+
cima. Dá mais trabalho, e é a única opção que permite colocar a **sua** camada
252+
de autenticação na frente do gateway em vez de depender das flags dele.
253+
254+
Compensa quando várias pessoas dividem um gateway e você quer log de acesso e
255+
revogação por pessoa — coisas que o login do gateway não oferece.
256+
257+
## Depois de expor, confira o que você expôs
258+
259+
```bash
260+
# De outro dispositivo, SEM credencial — os dois têm de recusar
261+
curl -si https://<seu-endereco>/v1/models | head -1 # espera-se 401
262+
curl -si https://<seu-endereco>/ | head -1 # espera-se 401 ou tela de login
263+
```
264+
265+
O painel deste sincronizador não tem motivo para sair da máquina: ele lê o banco
266+
do gateway e mostra a saúde das credenciais. Mantenha a porta dele em
267+
`127.0.0.1` e alcance-o pelo mesmo túnel ou tailnet que você já usa.

‎docs/wiki/_Sidebar.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- [Logging](Logging)
99
- [Architecture](Architecture)
1010
- [Egress and Multi-Session](Egress-And-Multi-Session)
11+
- [Remote Access](Remote-Access)
1112
- [Troubleshooting](Troubleshooting)
1213
- [Upstream Fixes](Upstream-Fixes)
1314

0 commit comments

Comments
 (0)