# frp 反向隧道部署（工作台线上出口，替代 ts.net）

2026-09-08 完整落地。目标：把 Mac 上的工作台 8765 暴露到国内公网，摆脱 Tailscale Funnel（ts.net 国内不可达）。

## 架构

```
销售员浏览器 → https://api.biorisales.site (Caddy HTTPS) → 127.0.0.1:8765 (frp 隧道) → Mac 8765 工作台
前端仍由 CloudBase 托管；API 走 api.biorisales.site（CHAT_APIS 首选 https://api.biorisales.site/api/chat）。
```

服务器：腾讯云轻量 123.207.243.239（Ubuntu 24.04 x86_64，广州）。SSH 私钥 ~/.ssh/HermesAIcopilot.pem。

## 落地步骤（已验证）

1. **开轻量服务器**：用户手动在控制台建（无 API 凭证时 agent 无法建 CVM）。地域广州、2核2G、Ubuntu 22.04/24.04、SSH 密钥登录。
2. **服务器装 frps**：下载 linux_amd64 frp → `/usr/local/bin/frps` + `/etc/frp/frps.toml` + systemd `frps.service`（`ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml`，Restart=on-failure）。
3. **Mac 装 frpc**：`~/frp/frpc` + `~/frp/frpc.toml` + launchd `~/Library/LaunchAgents/com.biori.frpc.plist`（RunAtLoad + KeepAlive）。
4. **HTTPS**：服务器装 Caddy（Cloudsmith 官方源），Caddyfile `api.biorisales.site { reverse_proxy 127.0.0.1:8765 }`，自动签 Let's Encrypt。
5. **DNS**：DNSPod 加 A 记录 `api` → 服务器 IP。
6. **防火墙**：Lighthouse 防火墙手动开 7000（frp 控制）+ 80 + 443（Caddy/HTTPS）。（8765 无需对外开——Caddy 走 127.0.0.1 内部转发。）
7. **前端**：`工具/index.html`+`app.html` 的 `CHAT_APIS` 第一位改成 `https://api.biorisales.site/api/chat`，deploy 到 CloudBase。

## 关键配置

### frps.toml（服务器）
```toml
bindPort = 7000
auth.method = "token"
auth.token = "<32位随机>"
```

### frpc.toml（Mac）
```toml
serverAddr = "<服务器IP>"
serverPort = 7000
auth.method = "token"
auth.token = "<同 token>"
loginFailExit = false   # ⚠️ 必须在 [[proxies]] 之前（顶层键）

[[proxies]]
name = "workbench"
type = "tcp"
localIP = "127.0.0.1"
localPort = 8765
remotePort = 8765
```

### Caddyfile（服务器）
```
{
    acme_ca https://acme-v02.api.letsencrypt.org/directory
    email admin@biorisales.site
}
api.biorisales.site {
    reverse_proxy 127.0.0.1:8765
}
```

## 踩坑记录

- **SSH 用户是 `ubuntu` 不是 root**（腾讯云 Lighthouse Ubuntu 镜像），sudo 免密（`sudo -n`）。SSH 登录用密钥：`ssh -i ~/.ssh/HermesAIcopilot.pem ubuntu@IP`。
- **Lighthouse 防火墙默认不开 80/443/7000/8765**，必须控制台手动加规则（22 默认开）。用 `nc -z -w 5 IP 端口` 从 Mac 测放行状态。
- **frp 二进制 GitHub release 被墙**（api.github.com 可达但 objects.githubusercontent.com 超时），走镜像 `https://ghfast.top/https://github.com/...`。
- **frpc 的 `loginFailExit=false` 若 append 到 `[[proxies]]` 之后会变成 proxies 子键**，TOML 解析异常——必须重写把顶层键放 `[[proxies]]` 之前。
- **Caddy 首签证书可能报 NXDOMAIN**（DNS 刚加未全球生效），等几分钟 reload 即可；若日志出现 `acme-staging-v02`（残留 staging 账户），显式 `acme_ca` 生产地址 + reload 解决。
- **curl 默认不走系统代理**，从 Mac 测 ts.net/Funnel 时直连正常 ≠ 浏览器（走代理）正常；模拟浏览器要 `curl -x http://127.0.0.1:7897`。
- **frpc 用 launchd 托管**（不是 tmux），`loginFailExit=false` 让它断线自动重连，Mac 重启后隧道自动恢复。

## 验证

```bash
# 服务器侧隧道通不通（应返回 200）
ssh -i ~/.ssh/HermesAIcopilot.pem ubuntu@IP "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8765/"
# HTTPS 全链路
curl -s -o /dev/null -w '%{http_code}' https://api.biorisales.site/
# CORS 预检（CloudBase 跨域调用）
curl -s -i -X OPTIONS https://api.biorisales.site/api/chat -H "Origin: https://bier-sales-...tcloudbaseapp.com" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: content-type" | grep -i access-control
```
