| 1 | # Portal - 面向 localhost 的自托管中继隧道 |
| 2 | |
| 3 | [English](./README.md) | [简体中文](./README.zh-CN.md) |
| 4 | |
| 5 | <p align="center"><img width="800" alt="Portal Demo" src="./portal.gif" /></p> |
| 6 | |
| 7 | <p align="center"><b>通过自托管或公共中继公开本地服务。</b><br/>无需端口转发。无需入站防火墙规则。无需手动 DNS 配置。无需账户。</p> |
| 8 | |
| 9 | ## 为什么选择 Portal? |
| 10 | |
| 11 | Portal 是一个本地隧道运行时和中继网络。它通过自托管或公共中继发布本地服务,把路由策略保留在隧道进程中,并避免依赖托管式厂商账户。 |
| 12 | |
| 13 | - **自托管,完全开源** - 用一条命令运行你自己的中继。中继采用 MIT 许可证,没有企业版层级,没有功能门槛,也不会回传遥测。你的中继,你的规则。 |
| 14 | |
| 15 | - **匿名中继网络** - 无需托管账户或中心化运营方即可连接公共中继。你可以把自托管中继和公共中继组合到一个池中,把信任拆分给你选择的多个独立运营方。 |
| 16 | |
| 17 | - **端到端租户 TLS 和 ECH** - 因为中继是不可信的,Portal 会在用户端点而不是中继处终止租户 TLS。Portal 还提供 ECH,避免真实主机名以明文 SNI 暴露。 |
| 18 | |
| 19 | - **内置 MITM 检测** - Portal 会在真实流量开始后主动自探测自己的连接。它会比较两端导出的 TLS 密钥材料,并把不匹配视为疑似中继侧 TLS 终止。 |
| 20 | |
| 21 | - **多跳中继路由** - 将多个中继串联起来,使单个中继无法同时知道来源和目的地。使用 `--multi-hop-depth 3` 可以自动选择三跳路由。 |
| 22 | |
| 23 | - **无账户,无 API Key** - 身份认证使用本地生成的 secp256k1 密钥对进行 SIWE 兼容签名。无需邮箱,无需注册,也没有厂商锁定。 |
| 24 | |
| 25 | - **原生 x402 支付** - Routed HTTP 路径可以在代理前要求 Sui gasless USDC x402 支付。浏览器应用可以导入 `/x402/client.js`,原生客户端可以直接调用 `/x402/prepare` 并发送 `X-PAYMENT`。 |
| 26 | |
| 27 | ## 对比 |
| 28 | |
| 29 | | | Portal | ngrok | Cloudflare Tunnel | frp | |
| 30 | |---|---|---|---|---| |
| 31 | | 公共 localhost URL | **是** | 是 | 是 | 是 | |
| 32 | | 可自托管 | **是** | 仅企业版 | 否 | 是 | |
| 33 | | 开源 | **MIT** | 否 | 仅客户端 | Apache 2.0 | |
| 34 | | 自定义域名 | **是** | 付费套餐 | 是 | 是 | |
| 35 | | 端到端租户 TLS | **是** | 否 | 否 | 否 | |
| 36 | | SNI 隐藏 (ECH) | **是** | 否 | 否 | 否 | |
| 37 | | MITM 自探测 | **内置** | 否 | 否 | 否 | |
| 38 | | 多中继故障切换 | **是** | 托管 | 内置 | 否 | |
| 39 | | 多跳路由 | **是** | 否 | 否 | 否 | |
| 40 | | 需要账户 | **否** | 是 | 是 | 否 | |
| 41 | | 原生 x402 支付 | **是** | 否 | 否 | 否 | |
| 42 | |
| 43 | ## 快速开始 |
| 44 | |
| 45 | ### 公开本地服务 |
| 46 | |
| 47 | **macOS / Linux:** |
| 48 | |
| 49 | ```bash |
| 50 | curl -fsSL https://github.com/gosuda/portal-tunnel/releases/latest/download/install.sh | bash |
| 51 | portal expose 3000 |
| 52 | ``` |
| 53 | |
| 54 | **Windows (PowerShell):** |
| 55 | |
| 56 | ```powershell |
| 57 | $ProgressPreference = 'SilentlyContinue' |
| 58 | irm https://github.com/gosuda/portal-tunnel/releases/latest/download/install.ps1 | iex |
| 59 | portal expose 3000 |
| 60 | ``` |
| 61 | |
| 62 | Portal 会立即为你的本地应用打印一个公共 HTTPS URL。更多示例: |
| 63 | |
| 64 | ```bash |
| 65 | # 自定义名称和中继 |
| 66 | portal expose 3000 --name myapp --relays https://portal.example.com --discovery=false |
| 67 | |
| 68 | # 把前端和 API 挂到同一个 URL 后面 |
| 69 | portal expose --name myapp \ |
| 70 | --http-route /api=http://127.0.0.1:3001 \ |
| 71 | --http-route /=http://127.0.0.1:5173 |
| 72 | |
| 73 | # 在代理某个路由前要求 Sui USDC x402 支付 |
| 74 | portal expose --name paid-app \ |
| 75 | --http-route "/paid=http://127.0.0.1:3001 GET:0.01" \ |
| 76 | --http-route /=http://127.0.0.1:5173 \ |
| 77 | --x402-pay-to 0x... |
| 78 | |
| 79 | # 原始 TCP 端口(Minecraft、数据库、SSH) |
| 80 | portal expose localhost:25565 --name minecraft --tcp |
| 81 | |
| 82 | # 三跳路由,获得更高匿名性 |
| 83 | portal expose 3000 --multi-hop-depth 3 |
| 84 | ``` |
| 85 | |
| 86 | 对于付费路由,支付策略运行在隧道进程内,而不是中继上。默认使用 Sui mainnet;加上 `--x402-testnet` 可切换到 Sui testnet,这个选择与中继自身的支付设置无关。隧道会在同一个公共 origin 上提供 `/x402/client.js` 和 `/x402/prepare`。浏览器前端可以导入 `/x402/client.js` 并调用 `x402Fetch()`;原生客户端可以直接调用 `/x402/prepare`,用自己的 Sui 运行时签名返回的交易,并发送签名后的 `X-PAYMENT`。 |
| 87 | |
| 88 | 完整路由语法请参阅 [CLI Reference](cmd/portal-tunnel/README.md),x402 helper endpoint 请参阅 [API Reference](docs/src/routes/api-reference/+page.md#payments)。 |
| 89 | |
| 90 | ### 使用 Portal Agent 持续运行隧道 |
| 91 | |
| 92 | 当隧道需要在终端之外持续运行时,使用 `portal agent run`。它会作为本地 OS 服务运行,在一个 TOML 配置中保持所有隧道在线,并提供用于中继和多跳管理的 dashboard。 |
| 93 | |
| 94 | ```bash |
| 95 | portal agent run --config config.toml |
| 96 | portal agent dashboard --config config.toml |
| 97 | portal agent restart |
| 98 | portal agent stop |
| 99 | |
| 100 | # 前台模式会跳过 OS 服务安装。 |
| 101 | portal agent run --config config.toml --foreground |
| 102 | ``` |
| 103 | |
| 104 | 配置格式请参阅 [Portal Agent](docs/src/routes/portal-agent/+page.md)。 |
| 105 | |
| 106 | ### 运行你自己的中继 |
| 107 | |
| 108 | ```bash |
| 109 | git clone https://github.com/gosuda/portal-tunnel |
| 110 | cd portal-tunnel && cp .env.example .env |
| 111 | docker compose up |
| 112 | ``` |
| 113 | |
| 114 | 关于带 DNS 自动化(ACME)、TCP/UDP 端口范围和中继策略的公网部署,请参阅 [Deployment](docs/src/routes/deployment/+page.md)。 |
| 115 | |
| 116 | ## 端到端加密如何工作 |
| 117 | |
| 118 | ```text |
| 119 | Browser |
| 120 | -> Relay SNI router (只读取路由 token,转发原始字节) |
| 121 | -> Reverse session |
| 122 | -> Portal tunnel (在本地执行 TLS 握手,派生 session key) |
| 123 | -> Local service |
| 124 | ``` |
| 125 | |
| 126 | 1. 中继接受传入连接,并只读取 TLS ClientHello 中用于 SNI 路由的信息。 |
| 127 | 2. 中继通过反向 session 转发原始加密流,而不终止 TLS。 |
| 128 | 3. 你这边的 Portal 隧道在本地完成 TLS 握手。Session key 在你的机器上派生。 |
| 129 | 4. 对于中继托管域名,隧道会通过 `/v1/sign` 获取证书签名,把中继仅用作 keyless signing oracle。中继签署握手摘要,但永远不会接收 session key。 |
| 130 | 5. 握手完成后,中继继续转发密文,无法访问明文。 |
| 131 | |
| 132 | 启用 ECH 时,中继也看不到真实租户主机名。它会通过从隧道身份派生出的不透明 token 进行路由,而真实 SNI 保留在 ECH 保护的 ClientHello 中。 |
| 133 | |
| 134 | ## 多跳路由如何工作 |
| 135 | |
| 136 | ```text |
| 137 | Browser |
| 138 | -> Entry relay (只看到不透明 route hostname) |
| 139 | -> Middle relay (只看到 next-hop token) |
| 140 | -> Exit relay (只看到 reverse session token) |
| 141 | -> Portal tunnel |
| 142 | -> Local service |
| 143 | ``` |
| 144 | |
| 145 | 链中的每个中继只知道自己的直接相邻节点。没有任何单个中继掌握完整路径。租户 TLS 仍然只在你这边终止,因此链中的任何中继都不会收到租户 TLS 明文。 |
| 146 | |
| 147 | ## 公共中继 Registry |
| 148 | |
| 149 | Portal 官方公共中继 registry 是: |
| 150 | |
| 151 | ```text |
| 152 | https://raw.githubusercontent.com/gosuda/portal-tunnel/main/registry.json |
| 153 | ``` |
| 154 | |
| 155 | 隧道客户端默认包含这个 registry。如果你运营公共 Portal 中继,可以提交 pull request,把你的中继 URL 添加到 `registry.json`。 |
| 156 | |
| 157 | ## 文档 |
| 158 | |
| 159 | - [CLI Reference](cmd/portal-tunnel/README.md) |
| 160 | - [Concepts](docs/src/routes/concepts/+page.md) |
| 161 | - [Portal Agent](docs/src/routes/portal-agent/+page.md) |
| 162 | - [Wallet and ENS](docs/src/routes/wallet-and-ens/+page.md) |
| 163 | - [Security Model](docs/src/routes/security-model/+page.md) |
| 164 | - [Architecture](docs/src/routes/architecture/+page.md) |
| 165 | - [Deployment](docs/src/routes/deployment/+page.md) |
| 166 | - [Configuration Reference](docs/src/routes/configuration/+page.md) |
| 167 | |
| 168 | ## 贡献 |
| 169 | |
| 170 | 1. Fork 这个仓库。 |
| 171 | 2. 创建功能分支(`git checkout -b feature/amazing-feature`)。 |
| 172 | 3. 用聚焦的测试或文档完成修改。 |
| 173 | 4. 打开 pull request。 |
| 174 | |
| 175 | ## 许可证 |
| 176 | |
| 177 | MIT License - see [LICENSE](LICENSE). |