feat: add Ngrok tunnel provider (#1415)

Adds Ngrok quick tunnel support
Adds desktop tunnel docs across locales
Improves tunnel settings provider and TTL labels
This commit is contained in:
Bohdan Triapitsyn
2026-05-25 18:00:03 +03:00
committed by GitHub
parent 25ed8e6abb
commit 00a7807168
22 changed files with 893 additions and 28 deletions
@@ -0,0 +1,41 @@
---
title: Desktop Tunnels
description: Create Cloudflare or Ngrok tunnels from the desktop app.
---
# Desktop Tunnels
The desktop app can create a public tunnel from **Settings → OpenChamber → Tunnel**. You do not need to start OpenChamber from the CLI for this path.
## Install a provider
OpenChamber starts the provider CLI on your machine. Install the provider you want to use first:
```bash
brew install cloudflared
brew install ngrok
```
Cloudflare uses `cloudflared`. Ngrok requires an ngrok account and an authtoken from the ngrok dashboard:
```bash
ngrok config add-authtoken <your-ngrok-token>
```
## Start from the app
1. Open **Settings → OpenChamber → Tunnel**.
2. Choose **Cloudflare** or **Ngrok**.
3. Start a quick tunnel.
4. Scan the generated QR code from your phone.
Ngrok currently supports quick tunnels. Cloudflare supports quick tunnels and managed Cloudflare modes.
## Access protection
Even when the provider URL itself is public, OpenChamber still protects access with its own connect token. The generated connect link contains a one-time token, has a TTL, and old unused links are revoked when you generate a new one or stop/restart the tunnel.
## Related
- [Tunnels](/tunnels/) — CLI tunnel usage and managed Cloudflare modes
- [PWA & Mobile](/mobile/) — reach OpenChamber from your phone
@@ -0,0 +1,41 @@
---
title: Túneles de escritorio
description: Crea túneles de Cloudflare o Ngrok desde la app de escritorio.
---
# Túneles de escritorio
La app de escritorio puede crear un túnel público desde **Settings → OpenChamber → Tunnel**. No necesitas iniciar OpenChamber desde el CLI para este flujo.
## Instala un proveedor
OpenChamber inicia el CLI del proveedor en tu máquina. Instala primero el proveedor que quieras usar:
```bash
brew install cloudflared
brew install ngrok
```
Cloudflare usa `cloudflared`. Ngrok requiere una cuenta de ngrok y un authtoken del panel de ngrok:
```bash
ngrok config add-authtoken <your-ngrok-token>
```
## Inicia desde la app
1. Abre **Settings → OpenChamber → Tunnel**.
2. Elige **Cloudflare** o **Ngrok**.
3. Inicia un quick tunnel.
4. Escanea el código QR generado desde tu teléfono.
Ngrok actualmente soporta quick tunnels. Cloudflare soporta quick tunnels y modos gestionados de Cloudflare.
## Protección de acceso
Aunque la URL del proveedor sea pública, OpenChamber sigue protegiendo el acceso con su propio connect token. El enlace generado contiene un token de un solo uso, tiene TTL, y los enlaces antiguos sin usar se revocan cuando generas uno nuevo o detienes/reinicias el túnel.
## Relacionado
- [Túneles](/es/tunnels/) — uso de túneles por CLI y modos gestionados de Cloudflare
- [PWA y acceso móvil](/es/mobile/) — accede a OpenChamber desde tu teléfono
+31 -2
View File
@@ -7,7 +7,22 @@ description: Expón OpenChamber de forma segura para acceso remoto y móvil.
Un túnel es un enlace público a tu OpenChamber, para que puedas acceder a él desde tu teléfono u otra red. Usa `openchamber tunnel` para crear uno para una instancia en marcha.
## Inicio rápido (modo rápido de Cloudflare)
## Requisitos previos
OpenChamber inicia el CLI del proveedor de túnel en tu máquina. Instala primero el proveedor que quieras usar:
```bash
brew install cloudflared
brew install ngrok
```
Los túneles rápidos de Cloudflare pueden funcionar con `cloudflared`. Ngrok requiere una cuenta de ngrok y un authtoken del panel de ngrok:
```bash
ngrok config add-authtoken <your-ngrok-token>
```
## Inicio rápido
1. Inicia OpenChamber:
@@ -15,12 +30,18 @@ Un túnel es un enlace público a tu OpenChamber, para que puedas acceder a él
openchamber
```
2. Inicia un túnel:
2. Inicia un túnel de Cloudflare:
```bash
openchamber tunnel start --provider cloudflare --mode quick
```
O inicia un túnel de Ngrok:
```bash
openchamber tunnel start --provider ngrok --mode quick
```
3. Comprueba el estado:
```bash
@@ -31,6 +52,11 @@ Cuando el túnel está activo, `status` muestra una URL pública. Ábrela, o esc
Por defecto, OpenChamber imprime un código QR en sesiones TTY interactivas. Usa `--qr` para forzar la salida del QR, o `--no-qr` para desactivarla.
## Proveedores
- `cloudflare`: modos quick, managed remote y managed local
- `ngrok`: modo quick
## Modos gestionados
### Gestionado remoto
@@ -68,7 +94,9 @@ openchamber tunnel start --profile prod-main
```bash
openchamber tunnel providers
openchamber tunnel ready --provider cloudflare
openchamber tunnel ready --provider ngrok
openchamber tunnel doctor --provider cloudflare
openchamber tunnel doctor --provider ngrok
openchamber tunnel stop --port 3000
```
@@ -81,5 +109,6 @@ openchamber tunnel stop --port 3000
## Relacionado
- [Seguridad](/es/security/) — protege la interfaz antes de exponerla
- [Túneles de escritorio](/es/desktop-tunnels/) — configuración de túneles en la app de escritorio sin iniciar desde CLI
- [PWA y acceso móvil](/es/mobile/) — accede a OpenChamber desde tu teléfono
- [Resolución de problemas](/es/troubleshooting/) — si el enlace del túnel no funciona
@@ -0,0 +1,41 @@
---
title: 데스크톱 터널
description: 데스크톱 앱에서 Cloudflare 또는 Ngrok 터널을 만드세요.
---
# 데스크톱 터널
데스크톱 앱은 **Settings → OpenChamber → Tunnel**에서 공개 터널을 만들 수 있습니다. 이 경로에서는 OpenChamber를 CLI로 시작할 필요가 없습니다.
## 공급자 설치
OpenChamber는 사용자의 컴퓨터에서 공급자 CLI를 실행합니다. 먼저 사용할 공급자를 설치하세요:
```bash
brew install cloudflared
brew install ngrok
```
Cloudflare는 `cloudflared`를 사용합니다. Ngrok은 ngrok 계정과 ngrok 대시보드의 authtoken이 필요합니다:
```bash
ngrok config add-authtoken <your-ngrok-token>
```
## 앱에서 시작
1. **Settings → OpenChamber → Tunnel**을 엽니다.
2. **Cloudflare** 또는 **Ngrok**을 선택합니다.
3. quick tunnel을 시작합니다.
4. 휴대폰에서 생성된 QR 코드를 스캔합니다.
Ngrok은 현재 quick tunnels를 지원합니다. Cloudflare는 quick tunnels와 Cloudflare 관리형 모드를 지원합니다.
## 접근 보호
공급자 URL 자체가 공개되어 있어도 OpenChamber는 자체 connect token으로 접근을 보호합니다. 생성된 연결 링크에는 일회용 token이 들어 있고 TTL이 있으며, 새 링크를 만들거나 터널을 중지/재시작하면 이전의 사용되지 않은 링크는 무효화됩니다.
## 관련 문서
- [터널](/ko/tunnels/) — CLI 터널 사용법과 Cloudflare 관리형 모드
- [PWA 및 모바일 접속](/ko/mobile/) — 휴대폰에서 OpenChamber에 접속하세요
+31 -2
View File
@@ -7,7 +7,22 @@ description: 원격 및 모바일 접근을 위해 OpenChamber를 안전하게
터널은 OpenChamber로 연결되는 공개 링크로, 휴대폰이나 다른 네트워크에서 접근할 수 있게 해줍니다. 실행 중인 인스턴스에 터널을 만들려면 `openchamber tunnel`을 사용하세요.
## 빠른 시작 (Cloudflare 빠른 모드)
## 사전 요구 사항
OpenChamber는 사용자의 컴퓨터에서 터널 공급자 CLI를 실행합니다. 먼저 사용할 공급자를 설치하세요:
```bash
brew install cloudflared
brew install ngrok
```
Cloudflare 빠른 터널은 `cloudflared`로 실행할 수 있습니다. Ngrok은 ngrok 계정과 ngrok 대시보드의 authtoken이 필요합니다:
```bash
ngrok config add-authtoken <your-ngrok-token>
```
## 빠른 시작
1. OpenChamber를 시작합니다:
@@ -15,12 +30,18 @@ description: 원격 및 모바일 접근을 위해 OpenChamber를 안전하게
openchamber
```
2. 터널을 시작합니다:
2. Cloudflare 터널을 시작합니다:
```bash
openchamber tunnel start --provider cloudflare --mode quick
```
또는 Ngrok 터널을 시작합니다:
```bash
openchamber tunnel start --provider ngrok --mode quick
```
3. 상태를 확인합니다:
```bash
@@ -31,6 +52,11 @@ openchamber tunnel status
기본적으로 OpenChamber는 대화형 TTY 세션에서 QR 코드를 출력합니다. `--qr`로 QR 출력을 강제하거나 `--no-qr`로 비활성화하세요.
## 공급자
- `cloudflare`: quick, managed remote, managed local 모드
- `ngrok`: quick 모드
## 관리형 모드
### 관리형 원격
@@ -68,7 +94,9 @@ openchamber tunnel start --profile prod-main
```bash
openchamber tunnel providers
openchamber tunnel ready --provider cloudflare
openchamber tunnel ready --provider ngrok
openchamber tunnel doctor --provider cloudflare
openchamber tunnel doctor --provider ngrok
openchamber tunnel stop --port 3000
```
@@ -81,5 +109,6 @@ openchamber tunnel stop --port 3000
## 관련 문서
- [보안](/ko/security/) — 외부에 공개하기 전에 UI를 보호하세요
- [데스크톱 터널](/ko/desktop-tunnels/) — CLI 시작 없이 데스크톱 앱에서 터널 설정
- [PWA 및 모바일 접속](/ko/mobile/) — 휴대폰에서 OpenChamber에 접속하세요
- [문제 해결](/ko/troubleshooting/) — 터널 링크가 작동하지 않을 때
@@ -0,0 +1,41 @@
---
title: Tunele w aplikacji desktopowej
description: Twórz tunele Cloudflare lub Ngrok z aplikacji desktopowej.
---
# Tunele w aplikacji desktopowej
Aplikacja desktopowa może utworzyć publiczny tunel z poziomu **Settings → OpenChamber → Tunnel**. W tym trybie nie musisz uruchamiać OpenChamber z CLI.
## Zainstaluj dostawcę
OpenChamber uruchamia CLI dostawcy na Twoim komputerze. Najpierw zainstaluj dostawcę, którego chcesz użyć:
```bash
brew install cloudflared
brew install ngrok
```
Cloudflare używa `cloudflared`. Ngrok wymaga konta ngrok i authtokena z panelu ngrok:
```bash
ngrok config add-authtoken <your-ngrok-token>
```
## Uruchom z aplikacji
1. Otwórz **Settings → OpenChamber → Tunnel**.
2. Wybierz **Cloudflare** albo **Ngrok**.
3. Uruchom quick tunnel.
4. Zeskanuj wygenerowany kod QR telefonem.
Ngrok obsługuje teraz quick tunnels. Cloudflare obsługuje quick tunnels i zarządzane tryby Cloudflare.
## Ochrona dostępu
Nawet jeśli URL dostawcy jest publiczny, OpenChamber nadal chroni dostęp własnym connect tokenem. Wygenerowany link zawiera jednorazowy token, ma TTL, a stare nieużyte linki są unieważniane po wygenerowaniu nowego linku albo zatrzymaniu/restarcie tunelu.
## Powiązane
- [Tunele](/pl/tunnels/) — użycie tuneli przez CLI i zarządzane tryby Cloudflare
- [PWA i dostęp z telefonu](/pl/mobile/) — korzystaj z OpenChamber z telefonu
+31 -2
View File
@@ -7,7 +7,22 @@ description: Bezpiecznie udostępnij OpenChamber do dostępu zdalnego i mobilneg
Tunel to publiczny link do Twojego OpenChamber, dzięki któremu możesz dotrzeć do niego z telefonu lub z innej sieci. Użyj `openchamber tunnel`, aby utworzyć go dla działającej instancji.
## Szybki start (tryb szybki Cloudflare)
## Wymagania wstępne
OpenChamber uruchamia CLI dostawcy tunelu na Twoim komputerze. Najpierw zainstaluj dostawcę, którego chcesz użyć:
```bash
brew install cloudflared
brew install ngrok
```
Szybkie tunele Cloudflare działają przez `cloudflared`. Ngrok wymaga konta ngrok i authtokena z panelu ngrok:
```bash
ngrok config add-authtoken <your-ngrok-token>
```
## Szybki start
1. Uruchom OpenChamber:
@@ -15,12 +30,18 @@ Tunel to publiczny link do Twojego OpenChamber, dzięki któremu możesz dotrze
openchamber
```
2. Uruchom tunel:
2. Uruchom tunel Cloudflare:
```bash
openchamber tunnel start --provider cloudflare --mode quick
```
Albo uruchom tunel Ngrok:
```bash
openchamber tunnel start --provider ngrok --mode quick
```
3. Sprawdź status:
```bash
@@ -31,6 +52,11 @@ Gdy tunel działa, `status` pokazuje publiczny adres URL. Otwórz go lub zeskanu
Domyślnie OpenChamber wypisuje kod QR w interaktywnych sesjach TTY. Użyj `--qr`, aby wymusić wyświetlenie kodu QR, lub `--no-qr`, aby je wyłączyć.
## Dostawcy
- `cloudflare`: tryby quick, managed remote i managed local
- `ngrok`: tryb quick
## Tryby zarządzane
### Zarządzany zdalny
@@ -68,7 +94,9 @@ openchamber tunnel start --profile prod-main
```bash
openchamber tunnel providers
openchamber tunnel ready --provider cloudflare
openchamber tunnel ready --provider ngrok
openchamber tunnel doctor --provider cloudflare
openchamber tunnel doctor --provider ngrok
openchamber tunnel stop --port 3000
```
@@ -81,5 +109,6 @@ openchamber tunnel stop --port 3000
## Powiązane
- [Bezpieczeństwo](/pl/security/) — zabezpiecz interfejs przed udostępnieniem
- [Tunele w aplikacji desktopowej](/pl/desktop-tunnels/) — konfiguracja tunelu w aplikacji desktopowej bez startu z CLI
- [PWA i dostęp z telefonu](/pl/mobile/) — korzystaj z OpenChamber z telefonu
- [Rozwiązywanie problemów](/pl/troubleshooting/) — jeśli link tunelu nie działa
@@ -0,0 +1,41 @@
---
title: Túneis no desktop
description: Crie túneis Cloudflare ou Ngrok pelo app desktop.
---
# Túneis no desktop
O app desktop pode criar um túnel público em **Settings → OpenChamber → Tunnel**. Você não precisa iniciar o OpenChamber pelo CLI para esse fluxo.
## Instale um provedor
O OpenChamber inicia o CLI do provedor na sua máquina. Instale primeiro o provedor que você quer usar:
```bash
brew install cloudflared
brew install ngrok
```
A Cloudflare usa `cloudflared`. O Ngrok exige uma conta ngrok e um authtoken do painel do ngrok:
```bash
ngrok config add-authtoken <your-ngrok-token>
```
## Inicie pelo app
1. Abra **Settings → OpenChamber → Tunnel**.
2. Escolha **Cloudflare** ou **Ngrok**.
3. Inicie um quick tunnel.
4. Escaneie o QR code gerado pelo celular.
O Ngrok atualmente suporta quick tunnels. A Cloudflare suporta quick tunnels e modos gerenciados da Cloudflare.
## Proteção de acesso
Mesmo quando a URL do provedor é pública, o OpenChamber ainda protege o acesso com seu próprio connect token. O link gerado contém um token de uso único, tem TTL, e links antigos não utilizados são revogados quando você gera um novo ou para/reinicia o túnel.
## Relacionado
- [Túneis](/pt-br/tunnels/) — uso de túneis pelo CLI e modos gerenciados da Cloudflare
- [PWA e Acesso Móvel](/pt-br/mobile/) — acesse o OpenChamber pelo celular
+31 -2
View File
@@ -7,7 +7,22 @@ description: Exponha o OpenChamber com segurança para acesso remoto e móvel.
Um túnel é um link público para o seu OpenChamber, para que você possa acessá-lo pelo celular ou de outra rede. Use `openchamber tunnel` para criar um para uma instância em execução.
## Início rápido (modo rápido da Cloudflare)
## Pré-requisitos
O OpenChamber inicia o CLI do provedor de túnel na sua máquina. Instale primeiro o provedor que você quer usar:
```bash
brew install cloudflared
brew install ngrok
```
Os túneis rápidos da Cloudflare funcionam com `cloudflared`. O Ngrok exige uma conta ngrok e um authtoken do painel do ngrok:
```bash
ngrok config add-authtoken <your-ngrok-token>
```
## Início rápido
1. Inicie o OpenChamber:
@@ -15,12 +30,18 @@ Um túnel é um link público para o seu OpenChamber, para que você possa acess
openchamber
```
2. Inicie um túnel:
2. Inicie um túnel da Cloudflare:
```bash
openchamber tunnel start --provider cloudflare --mode quick
```
Ou inicie um túnel do Ngrok:
```bash
openchamber tunnel start --provider ngrok --mode quick
```
3. Verifique o status:
```bash
@@ -31,6 +52,11 @@ Quando o túnel estiver no ar, o `status` mostra uma URL pública. Abra-a, ou es
Por padrão, o OpenChamber imprime um QR code em sessões TTY interativas. Use `--qr` para forçar a saída do QR, ou `--no-qr` para desativá-la.
## Provedores
- `cloudflare`: modos quick, managed remote e managed local
- `ngrok`: modo quick
## Modos gerenciados
### Gerenciado remoto
@@ -68,7 +94,9 @@ openchamber tunnel start --profile prod-main
```bash
openchamber tunnel providers
openchamber tunnel ready --provider cloudflare
openchamber tunnel ready --provider ngrok
openchamber tunnel doctor --provider cloudflare
openchamber tunnel doctor --provider ngrok
openchamber tunnel stop --port 3000
```
@@ -81,5 +109,6 @@ openchamber tunnel stop --port 3000
## Relacionado
- [Segurança](/pt-br/security/) — proteja a interface antes de expô-la
- [Túneis no desktop](/pt-br/desktop-tunnels/) — configuração de túnel no app desktop sem iniciar pelo CLI
- [PWA e Acesso Móvel](/pt-br/mobile/) — acesse o OpenChamber pelo celular
- [Solução de problemas](/pt-br/troubleshooting/) — se o link do túnel não funcionar
+31 -2
View File
@@ -7,7 +7,22 @@ description: Expose OpenChamber safely for remote and mobile access.
A tunnel is a public link to your OpenChamber, so you can reach it from your phone or another network. Use `openchamber tunnel` to create one for a running instance.
## Quick start (Cloudflare quick mode)
## Prerequisites
OpenChamber starts the tunnel provider CLI on your machine. Install the provider you want to use first:
```bash
brew install cloudflared
brew install ngrok
```
Cloudflare quick tunnels can run with `cloudflared`. Ngrok requires an ngrok account and an authtoken from the ngrok dashboard:
```bash
ngrok config add-authtoken <your-ngrok-token>
```
## Quick start
1. Start OpenChamber:
@@ -15,12 +30,18 @@ A tunnel is a public link to your OpenChamber, so you can reach it from your pho
openchamber
```
2. Start a tunnel:
2. Start a Cloudflare tunnel:
```bash
openchamber tunnel start --provider cloudflare --mode quick
```
Or start an Ngrok tunnel:
```bash
openchamber tunnel start --provider ngrok --mode quick
```
3. Check status:
```bash
@@ -31,6 +52,11 @@ When the tunnel is up, `status` shows a public URL. Open it, or scan the QR code
By default, OpenChamber prints a QR code in interactive TTY sessions. Use `--qr` to force QR output, or `--no-qr` to disable it.
## Providers
- `cloudflare`: quick, managed remote, and managed local modes
- `ngrok`: quick mode
## Managed modes
### Managed remote
@@ -68,7 +94,9 @@ openchamber tunnel start --profile prod-main
```bash
openchamber tunnel providers
openchamber tunnel ready --provider cloudflare
openchamber tunnel ready --provider ngrok
openchamber tunnel doctor --provider cloudflare
openchamber tunnel doctor --provider ngrok
openchamber tunnel stop --port 3000
```
@@ -81,5 +109,6 @@ openchamber tunnel stop --port 3000
## Related
- [Security](/security/) — protect the UI before exposing it
- [Desktop Tunnels](/desktop-tunnels/) — desktop app tunnel setup without CLI startup
- [PWA & Mobile](/mobile/) — reach OpenChamber from your phone
- [Troubleshooting](/troubleshooting/) — if the tunnel link doesn't work
@@ -0,0 +1,41 @@
---
title: Десктопні тунелі
description: Створюйте Cloudflare або Ngrok тунелі з десктопного застосунку.
---
# Десктопні тунелі
Десктопний застосунок може створити публічний тунель через **Settings → OpenChamber → Tunnel**. Для цього не потрібно запускати OpenChamber через CLI.
## Встановіть провайдера
OpenChamber запускає CLI провайдера на вашій машині. Спочатку встановіть провайдера, який хочете використовувати:
```bash
brew install cloudflared
brew install ngrok
```
Cloudflare використовує `cloudflared`. Для Ngrok потрібен акаунт ngrok і authtoken з dashboard ngrok:
```bash
ngrok config add-authtoken <your-ngrok-token>
```
## Запуск із застосунку
1. Відкрийте **Settings → OpenChamber → Tunnel**.
2. Виберіть **Cloudflare** або **Ngrok**.
3. Запустіть quick tunnel.
4. Відскануйте згенерований QR-код з телефона.
Ngrok зараз підтримує quick tunnels. Cloudflare підтримує quick tunnels і керовані режими Cloudflare.
## Захист доступу
Навіть якщо URL провайдера публічний, OpenChamber все одно захищає доступ власним connect token. Згенероване посилання містить одноразовий token, має TTL, а старі невикористані посилання відкликаються, коли ви генеруєте нове або зупиняєте/перезапускаєте тунель.
## Пов'язане
- [Тунелі](/uk/tunnels/) — використання тунелів через CLI і керовані режими Cloudflare
- [PWA та мобільний доступ](/uk/mobile/) — відкривайте OpenChamber з телефона
+31 -2
View File
@@ -7,7 +7,22 @@ description: Безпечно відкрийте OpenChamber для віддал
Тунель — це публічне посилання на ваш OpenChamber, щоб дістатися до нього з телефона чи з іншої мережі. Скористайтеся `openchamber tunnel`, щоб створити його для запущеного інстансу.
## Швидкий старт (швидкий режим Cloudflare)
## Передумови
OpenChamber запускає CLI провайдера тунелю на вашій машині. Спочатку встановіть провайдера, який хочете використовувати:
```bash
brew install cloudflared
brew install ngrok
```
Швидкі тунелі Cloudflare працюють через `cloudflared`. Для Ngrok потрібен акаунт ngrok і authtoken з dashboard ngrok:
```bash
ngrok config add-authtoken <your-ngrok-token>
```
## Швидкий старт
1. Запустіть OpenChamber:
@@ -15,12 +30,18 @@ description: Безпечно відкрийте OpenChamber для віддал
openchamber
```
2. Запустіть тунель:
2. Запустіть тунель Cloudflare:
```bash
openchamber tunnel start --provider cloudflare --mode quick
```
Або запустіть тунель Ngrok:
```bash
openchamber tunnel start --provider ngrok --mode quick
```
3. Перевірте статус:
```bash
@@ -31,6 +52,11 @@ openchamber tunnel status
Типово OpenChamber виводить QR-код в інтерактивних TTY-сесіях. Використовуйте `--qr`, щоб примусово вивести QR, або `--no-qr`, щоб вимкнути його.
## Провайдери
- `cloudflare`: quick, managed remote і managed local режими
- `ngrok`: quick режим
## Керовані режими
### Кероване віддалене
@@ -68,7 +94,9 @@ openchamber tunnel start --profile prod-main
```bash
openchamber tunnel providers
openchamber tunnel ready --provider cloudflare
openchamber tunnel ready --provider ngrok
openchamber tunnel doctor --provider cloudflare
openchamber tunnel doctor --provider ngrok
openchamber tunnel stop --port 3000
```
@@ -81,5 +109,6 @@ openchamber tunnel stop --port 3000
## Пов'язане
- [Безпека](/uk/security/) — захистіть інтерфейс перед відкриттям доступу
- [Десктопні тунелі](/uk/desktop-tunnels/) — налаштування тунелю в десктопному застосунку без CLI-старту
- [PWA та мобільний доступ](/uk/mobile/) — відкривайте OpenChamber з телефона
- [Усунення несправностей](/uk/troubleshooting/) — якщо посилання тунелю не працює
@@ -0,0 +1,41 @@
---
title: 桌面端隧道
description: 从桌面应用创建 Cloudflare 或 Ngrok 隧道。
---
# 桌面端隧道
桌面应用可以在 **Settings → OpenChamber → Tunnel** 中创建公开隧道。这个流程不需要你从 CLI 启动 OpenChamber。
## 安装提供商
OpenChamber 会在你的机器上启动提供商 CLI。请先安装你想使用的提供商:
```bash
brew install cloudflared
brew install ngrok
```
Cloudflare 使用 `cloudflared`。Ngrok 需要 ngrok 账号,以及从 ngrok dashboard 获取的 authtoken
```bash
ngrok config add-authtoken <your-ngrok-token>
```
## 从应用启动
1. 打开 **Settings → OpenChamber → Tunnel**。
2. 选择 **Cloudflare** 或 **Ngrok**。
3. 启动 quick tunnel。
4. 用手机扫描生成的二维码。
Ngrok 目前支持 quick tunnels。Cloudflare 支持 quick tunnels 和 Cloudflare 托管模式。
## 访问保护
即使提供商 URL 本身是公开的,OpenChamber 仍会用自己的 connect token 保护访问。生成的连接链接包含一次性 token,有 TTL;当你生成新链接或停止/重启隧道时,旧的未使用链接会被撤销。
## 相关内容
- [隧道](/zh-cn/tunnels/) — CLI 隧道用法和 Cloudflare 托管模式
- [PWA 与移动访问](/zh-cn/mobile/) — 从手机访问 OpenChamber
+31 -2
View File
@@ -7,7 +7,22 @@ description: 安全地将 OpenChamber 开放给远程和移动访问。
隧道是指向你的 OpenChamber 的一个公开链接,让你可以从手机或另一个网络访问它。使用 `openchamber tunnel` 为正在运行的实例创建一个隧道。
## 快速开始(Cloudflare 快速模式)
## 前置要求
OpenChamber 会在你的机器上启动隧道提供商的 CLI。请先安装你想使用的提供商:
```bash
brew install cloudflared
brew install ngrok
```
Cloudflare 快速隧道可以通过 `cloudflared` 运行。Ngrok 需要 ngrok 账号,以及从 ngrok dashboard 获取的 authtoken
```bash
ngrok config add-authtoken <your-ngrok-token>
```
## 快速开始
1. 启动 OpenChamber
@@ -15,12 +30,18 @@ description: 安全地将 OpenChamber 开放给远程和移动访问。
openchamber
```
2. 启动隧道:
2. 启动 Cloudflare 隧道:
```bash
openchamber tunnel start --provider cloudflare --mode quick
```
或启动 Ngrok 隧道:
```bash
openchamber tunnel start --provider ngrok --mode quick
```
3. 查看状态:
```bash
@@ -31,6 +52,11 @@ openchamber tunnel status
默认情况下,OpenChamber 会在交互式 TTY 会话中输出二维码。使用 `--qr` 强制输出二维码,或使用 `--no-qr` 将其禁用。
## 提供商
- `cloudflare`quick、managed remote 和 managed local 模式
- `ngrok`quick 模式
## 托管模式
### 托管远程
@@ -68,7 +94,9 @@ openchamber tunnel start --profile prod-main
```bash
openchamber tunnel providers
openchamber tunnel ready --provider cloudflare
openchamber tunnel ready --provider ngrok
openchamber tunnel doctor --provider cloudflare
openchamber tunnel doctor --provider ngrok
openchamber tunnel stop --port 3000
```
@@ -81,5 +109,6 @@ openchamber tunnel stop --port 3000
## 相关内容
- [安全](/zh-cn/security/) — 在开放访问前保护 UI
- [桌面端隧道](/zh-cn/desktop-tunnels/) — 无需从 CLI 启动即可在桌面应用中设置隧道
- [PWA 与移动访问](/zh-cn/mobile/) — 从手机访问 OpenChamber
- [问题排查](/zh-cn/troubleshooting/) — 如果隧道链接无法使用
+12
View File
@@ -444,6 +444,18 @@
"pl": "Przeglądarka na pulpicie"
}
},
{
"label": "Desktop Tunnels",
"link": "/desktop-tunnels/",
"translations": {
"uk": "Десктопні тунелі",
"zh-CN": "桌面端隧道",
"es": "Túneles de escritorio",
"pt-BR": "Túneis no desktop",
"ko": "데스크톱 터널",
"pl": "Tunele w aplikacji desktopowej"
}
},
{
"label": "Updates",
"link": "/updates/",
@@ -149,16 +149,20 @@ const getProviderLabel = (provider: string): string => {
if (provider === 'cloudflare') {
return 'Cloudflare';
}
if (provider === 'ngrok') {
return 'Ngrok';
}
return provider;
};
const ProviderOptionLabel: React.FC<{ provider: string }> = ({ provider }) => {
const label = getProviderLabel(provider);
const isCloudflare = provider === 'cloudflare';
const isNgrok = provider === 'ngrok';
return (
<span className="flex items-center gap-2">
<Icon name="cloud" className={cn('size-4 shrink-0', isCloudflare ? 'text-[var(--status-warning)]' : 'text-muted-foreground')} />
<Icon name="cloud" className={cn('size-4 shrink-0', isCloudflare || isNgrok ? 'text-[var(--status-warning)]' : 'text-muted-foreground')} />
<span>{label}</span>
</span>
);
@@ -182,6 +186,11 @@ const ttlOptionValue = (options: TtlOption[], ttlMs: number | null, fallback: st
return matched?.value || fallback;
};
const ttlOptionLabel = (options: TtlOption[], ttlMs: number | null, fallback: string) => {
const value = ttlOptionValue(options, ttlMs, fallback);
return options.find((entry) => entry.value === value)?.label || value;
};
const formatRemaining = (remainingMs: number): string => {
const safeMs = Math.max(0, remainingMs);
const totalSeconds = Math.floor(safeMs / 1000);
@@ -361,6 +370,23 @@ export const TunnelSettings: React.FC = () => {
}
return null;
}, [localPort]);
const selectedProviderCapability = React.useMemo(() => {
return providerCapabilities.find((capability) => capability.provider === tunnelProvider) ?? null;
}, [providerCapabilities, tunnelProvider]);
const tunnelModeOptions = React.useMemo(() => {
const supportedModes = new Set(
selectedProviderCapability?.modes
?.map((mode) => mode.key)
.filter((mode): mode is TunnelMode => mode === 'quick' || mode === 'managed-remote' || mode === 'managed-local')
);
if (supportedModes.size === 0) {
return TUNNEL_MODE_OPTIONS;
}
return TUNNEL_MODE_OPTIONS.filter((option) => supportedModes.has(option.value));
}, [selectedProviderCapability]);
const installCommand = tunnelProvider === 'ngrok'
? 'brew install ngrok'
: 'brew install cloudflared';
const openExternal = React.useCallback(async (url: string) => {
await openExternalUrl(url);
}, []);
@@ -691,8 +717,12 @@ export const TunnelSettings: React.FC = () => {
const handleProviderChange = React.useCallback(async (provider: string) => {
setManagedRemoteValidationError(null);
setErrorMessage(null);
await saveTunnelSettings({ tunnelProvider: provider });
}, [saveTunnelSettings]);
const capability = providerCapabilities.find((entry) => entry.provider === provider);
const defaultMode = capability?.modes?.some((mode) => mode.key === tunnelMode)
? tunnelMode
: toUiTunnelMode(capability?.modes?.[0]?.key);
await saveTunnelSettings({ tunnelProvider: provider, tunnelMode: defaultMode });
}, [providerCapabilities, saveTunnelSettings, tunnelMode]);
const handleBrowseManagedLocalConfig = React.useCallback(async () => {
const result = await requestFileAccess({
@@ -1146,17 +1176,17 @@ export const TunnelSettings: React.FC = () => {
<div className="flex items-start gap-2 rounded-lg border border-[var(--status-warning)]/30 bg-[var(--status-warning)]/5 p-3">
<Icon name="error-warning" className="mt-0.5 size-4 shrink-0 text-[var(--status-warning)]" />
<div className="space-y-1">
<p className="typography-meta font-medium text-foreground">{t('settings.openchamber.tunnel.notAvailable.cloudflaredNotFound')}</p>
<p className="typography-meta font-medium text-foreground">{getProviderLabel(tunnelProvider)} tunnel dependency was not found.</p>
<p className="typography-meta text-muted-foreground/70">{t('settings.openchamber.tunnel.notAvailable.installHint')}</p>
<code className="typography-code block rounded bg-muted/50 px-2 py-1 text-xs text-foreground">
brew install cloudflared
{installCommand}
</code>
</div>
</div>
</section>
)}
{state !== 'not-available' && (
{(
<section className="space-y-4 px-2 pb-2 pt-0">
<div className="space-y-3">
<div className="space-y-1.5">
@@ -1193,7 +1223,7 @@ export const TunnelSettings: React.FC = () => {
<div className="space-y-1.5">
<p className="typography-ui-label text-foreground">{t('settings.openchamber.tunnel.field.tunnelType')}</p>
<div className="flex flex-wrap items-center gap-1">
{TUNNEL_MODE_OPTIONS.map((option) => (
{tunnelModeOptions.map((option) => (
<Tooltip key={option.value}>
<TooltipTrigger asChild>
<Button
@@ -1229,7 +1259,9 @@ export const TunnelSettings: React.FC = () => {
disabled={isSavingTtl || isSavingMode || state === 'starting' || state === 'stopping'}
>
<SelectTrigger className="max-w-[11rem] min-w-0">
<SelectValue className="truncate" />
<SelectValue className="truncate">
{ttlOptionLabel(BOOTSTRAP_TTL_OPTIONS, bootstrapTtlMs, '1800000')}
</SelectValue>
</SelectTrigger>
<SelectContent>
{BOOTSTRAP_TTL_OPTIONS.map((option) => (
@@ -1249,7 +1281,9 @@ export const TunnelSettings: React.FC = () => {
disabled={isSavingTtl || isSavingMode || state === 'starting' || state === 'stopping'}
>
<SelectTrigger className="max-w-[11rem] min-w-0">
<SelectValue className="truncate" />
<SelectValue className="truncate">
{ttlOptionLabel(SESSION_TTL_OPTIONS, sessionTtlMs, '28800000')}
</SelectValue>
</SelectTrigger>
<SelectContent>
{SESSION_TTL_OPTIONS.map((option) => (
+2
View File
@@ -14,6 +14,7 @@ import { createTunnelAuth } from './lib/opencode/tunnel-auth.js';
import { createManagedTunnelConfigRuntime } from './lib/tunnels/managed-config.js';
import { createTunnelProviderRegistry } from './lib/tunnels/registry.js';
import { createCloudflareTunnelProvider } from './lib/tunnels/providers/cloudflare.js';
import { createNgrokTunnelProvider } from './lib/tunnels/providers/ngrok.js';
import { createRequestSecurityRuntime } from './lib/security/request-security.js';
import {
TUNNEL_MODE_MANAGED_LOCAL,
@@ -449,6 +450,7 @@ let activeTunnelController = null;
let globalWatcherStartPromise = null;
const tunnelProviderRegistry = createTunnelProviderRegistry([
createCloudflareTunnelProvider(),
createNgrokTunnelProvider(),
]);
tunnelProviderRegistry.seal();
const tunnelAuthController = createTunnelAuth();
+3 -5
View File
@@ -637,14 +637,12 @@ export async function startCloudflareTunnel({ originUrl, port }) {
export function printTunnelWarning() {
console.log(`
Cloudflare Quick Tunnel Limitations:
Quick Tunnel Limitations:
Maximum 200 concurrent requests
Server-Sent Events (SSE) are NOT supported
Provider limits may apply
URLs are temporary and will expire when the tunnel stops
Password protection is required for tunnel access
For production use, set up a managed remote Cloudflare Tunnel:
https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/
For production use, set up a persistent provider tunnel or static domain.
`);
}
+209
View File
@@ -0,0 +1,209 @@
import { spawn, spawnSync } from 'child_process';
import fs from 'fs';
import path from 'path';
const DEFAULT_STARTUP_TIMEOUT_MS = 30000;
const NGROK_API_URL = 'http://127.0.0.1:4040/api/tunnels';
const NGROK_INSTALL_HELP = 'brew install ngrok';
const NGROK_AUTHTOKEN_HELP = 'Run: ngrok config add-authtoken <your-ngrok-token>';
async function searchPathFor(command) {
const pathValue = process.env.PATH || '';
const segments = pathValue.split(path.delimiter).filter(Boolean);
const WINDOWS_EXTENSIONS = process.platform === 'win32'
? (process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM')
.split(';')
.map((ext) => ext.trim().toLowerCase())
.filter(Boolean)
.map((ext) => (ext.startsWith('.') ? ext : `.${ext}`))
: [''];
for (const dir of segments) {
for (const ext of WINDOWS_EXTENSIONS) {
const fileName = process.platform === 'win32' ? `${command}${ext}` : command;
const candidate = path.join(dir, fileName);
try {
const stats = fs.statSync(candidate);
if (!stats.isFile()) {
continue;
}
if (process.platform !== 'win32') {
try {
fs.accessSync(candidate, fs.constants.X_OK);
} catch {
continue;
}
}
return candidate;
} catch {
continue;
}
}
}
return null;
}
export async function checkNgrokAvailable() {
const ngrokPath = await searchPathFor('ngrok');
if (ngrokPath) {
try {
const result = spawnSync(ngrokPath, ['version'], {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
windowsHide: true,
});
if (result.status === 0) {
return { available: true, path: ngrokPath, version: result.stdout.trim() || result.stderr.trim() };
}
} catch {
// Ignore and report unavailable below.
}
}
return { available: false, path: null, version: null };
}
export async function checkNgrokAuthtokenConfigured(ngrokPath = null) {
if (typeof process.env.NGROK_AUTHTOKEN === 'string' && process.env.NGROK_AUTHTOKEN.trim().length > 0) {
return { configured: true, detail: 'NGROK_AUTHTOKEN is set.' };
}
const resolvedPath = ngrokPath || await searchPathFor('ngrok');
if (!resolvedPath) {
return { configured: false, detail: `ngrok is not installed. Install it with: ${NGROK_INSTALL_HELP}` };
}
try {
const result = spawnSync(resolvedPath, ['config', 'check'], {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
windowsHide: true,
});
const output = `${result.stdout || ''}${result.stderr || ''}`.trim();
if (result.status === 0) {
return { configured: true, detail: output || 'ngrok config is valid.' };
}
return { configured: false, detail: output || NGROK_AUTHTOKEN_HELP };
} catch (error) {
return {
configured: false,
detail: error instanceof Error ? error.message : String(error),
};
}
}
export async function checkNgrokApiReachability({ fetchImpl = globalThis.fetch, timeoutMs = 5000 } = {}) {
if (typeof fetchImpl !== 'function') {
return { reachable: false, status: null, error: 'Fetch API is unavailable in this runtime.' };
}
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetchImpl('https://api.ngrok.com/', {
method: 'GET',
signal: controller.signal,
});
return { reachable: true, status: response.status, error: null };
} catch (error) {
return {
reachable: false,
status: null,
error: error instanceof Error ? error.message : String(error),
};
} finally {
clearTimeout(timeout);
}
}
const spawnNgrok = (args, resolvedBinaryPath = 'ngrok') => spawn(resolvedBinaryPath, args, {
stdio: ['ignore', 'pipe', 'pipe'],
windowsHide: true,
env: process.env,
killSignal: 'SIGINT',
});
async function fetchNgrokPublicUrl(fetchImpl = globalThis.fetch) {
if (typeof fetchImpl !== 'function') {
return null;
}
try {
const response = await fetchImpl(NGROK_API_URL, { method: 'GET' });
if (!response.ok) {
return null;
}
const payload = await response.json();
const tunnels = Array.isArray(payload?.tunnels) ? payload.tunnels : [];
const httpsTunnel = tunnels.find((entry) => entry?.proto === 'https' && typeof entry?.public_url === 'string');
const fallbackTunnel = tunnels.find((entry) => typeof entry?.public_url === 'string');
return httpsTunnel?.public_url || fallbackTunnel?.public_url || null;
} catch {
return null;
}
}
export async function startNgrokQuickTunnel({ port }) {
const ngrokCheck = await checkNgrokAvailable();
if (!ngrokCheck.available) {
throw new Error(`ngrok is not installed. Install it with: ${NGROK_INSTALL_HELP}`);
}
const authtokenCheck = await checkNgrokAuthtokenConfigured(ngrokCheck.path);
if (!authtokenCheck.configured) {
throw new Error(`ngrok authtoken is not configured. ${NGROK_AUTHTOKEN_HELP}`);
}
if (!Number.isFinite(port)) {
throw new Error('A local port is required to start an ngrok tunnel');
}
const child = spawnNgrok(['http', String(port)], ngrokCheck.path);
let publicUrl = null;
child.stdout.on('data', () => {
// Keep stream drained; ngrok exposes the URL via its local API.
});
child.stderr.on('data', (chunk) => {
process.stderr.write(chunk.toString('utf8'));
});
child.on('error', (error) => {
console.error(`Ngrok error: ${error.message}`);
});
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
clearInterval(checkReady);
try { child.kill('SIGINT'); } catch { /* ignore */ }
reject(new Error('Ngrok tunnel URL not received within 30 seconds'));
}, DEFAULT_STARTUP_TIMEOUT_MS);
const checkReady = setInterval(async () => {
publicUrl = await fetchNgrokPublicUrl();
if (publicUrl) {
clearTimeout(timeout);
clearInterval(checkReady);
resolve(null);
}
}, 250);
child.once('exit', (code) => {
clearTimeout(timeout);
clearInterval(checkReady);
reject(new Error(`Ngrok exited while starting (code ${code ?? 'unknown'})`));
});
});
return {
mode: 'quick',
stop: () => {
try {
child.kill('SIGINT');
} catch {
// Ignore.
}
},
process: child,
getPublicUrl: () => publicUrl,
};
}
@@ -10,6 +10,7 @@ This module contains tunnel provider orchestration for OpenChamber, including pr
- `packages/web/server/lib/tunnels/routes.js`: tunnel API route registration and request orchestration runtime.
- `packages/web/server/lib/tunnels/types.js`: tunnel constants, normalization, and shared type helpers.
- `packages/web/server/lib/tunnels/providers/cloudflare.js`: Cloudflare tunnel provider implementation.
- `packages/web/server/lib/tunnels/providers/ngrok.js`: Ngrok quick tunnel provider implementation.
## Public exports (routes.js)
- `createTunnelRoutesRuntime(dependencies)`: creates tunnel routes runtime and helpers.
@@ -0,0 +1,117 @@
import {
checkNgrokApiReachability,
checkNgrokAuthtokenConfigured,
checkNgrokAvailable,
startNgrokQuickTunnel,
} from '../../ngrok-tunnel.js';
import {
TUNNEL_INTENT_EPHEMERAL_PUBLIC,
TUNNEL_MODE_QUICK,
TUNNEL_PROVIDER_NGROK,
TunnelServiceError,
} from '../types.js';
export const ngrokTunnelProviderCapabilities = {
provider: TUNNEL_PROVIDER_NGROK,
defaults: {
mode: TUNNEL_MODE_QUICK,
optionDefaults: {},
},
modes: [
{
key: TUNNEL_MODE_QUICK,
label: 'Quick Tunnel',
intent: TUNNEL_INTENT_EPHEMERAL_PUBLIC,
requires: [],
supports: ['sessionTTL'],
stability: 'beta',
},
],
};
export function createNgrokTunnelProvider() {
return {
id: TUNNEL_PROVIDER_NGROK,
capabilities: ngrokTunnelProviderCapabilities,
checkAvailability: async () => {
const result = await checkNgrokAvailable();
if (result.available) {
return result;
}
return {
...result,
message: 'ngrok is not installed. Install it with: brew install ngrok',
};
},
diagnose: async () => {
const dependency = await checkNgrokAvailable();
const authtoken = await checkNgrokAuthtokenConfigured(dependency.path);
const network = await checkNgrokApiReachability();
const startupReady = dependency.available && authtoken.configured && network.reachable;
const providerChecks = [
{
id: 'dependency',
label: 'ngrok installed',
status: dependency.available ? 'pass' : 'fail',
detail: dependency.available
? (dependency.version || dependency.path || 'ngrok available')
: 'ngrok is not installed. Install it with: brew install ngrok',
},
{
id: 'authtoken',
label: 'ngrok authtoken configured',
status: authtoken.configured ? 'pass' : 'fail',
detail: authtoken.configured
? authtoken.detail
: (authtoken.detail || 'Run: ngrok config add-authtoken <your-ngrok-token>'),
},
{
id: 'network',
label: 'ngrok API reachable',
status: network.reachable ? 'pass' : 'fail',
detail: network.reachable
? (network.status ? `HTTP ${network.status}` : 'Reachable')
: (network.error || 'Could not reach api.ngrok.com'),
},
];
return {
providerChecks,
modes: [
{
mode: TUNNEL_MODE_QUICK,
checks: [
{
id: 'startup_readiness',
label: 'Provider startup readiness',
status: startupReady ? 'pass' : 'fail',
detail: startupReady
? 'Provider dependency, auth, and network checks passed.'
: 'Resolve provider checks before starting tunnels.',
},
],
summary: {
ready: startupReady,
failures: startupReady ? 0 : 1,
warnings: 0,
},
ready: startupReady,
blockers: startupReady ? [] : ['Resolve provider checks before starting tunnels.'],
},
],
};
},
start: async (request, context = {}) => {
if (request.mode !== TUNNEL_MODE_QUICK) {
throw new TunnelServiceError('mode_unsupported', `Ngrok only supports '${TUNNEL_MODE_QUICK}' mode right now`);
}
return startNgrokQuickTunnel({ port: context.activePort });
},
stop: (controller) => {
controller?.stop?.();
},
resolvePublicUrl: (controller) => controller?.getPublicUrl?.() ?? null,
getMetadata: () => null,
};
}
+2
View File
@@ -2,6 +2,7 @@ import os from 'os';
import path from 'path';
export const TUNNEL_PROVIDER_CLOUDFLARE = 'cloudflare';
export const TUNNEL_PROVIDER_NGROK = 'ngrok';
export const TUNNEL_MODE_QUICK = 'quick';
export const TUNNEL_MODE_MANAGED_REMOTE = 'managed-remote';
@@ -34,6 +35,7 @@ export class TunnelServiceError extends Error {
const SUPPORTED_TUNNEL_PROVIDERS = new Set([
TUNNEL_PROVIDER_CLOUDFLARE,
TUNNEL_PROVIDER_NGROK,
]);
export function normalizeTunnelProvider(value) {