
1. 这不是“远程桌面”而是把 Claude Code / OpenCode 变成你浏览器里的「智能终端」我第一次在 Chrome 里敲下claude://run?filemain.py并看到代码被自动补全、解释、重构整个过程没开 VS Code、没连 SSH、没装任何本地插件——那一刻我才意识到我们正在用一套轻量级 Web 基础设施绕过传统 IDE 的厚重包袱把 AI 编程能力真正“端到端”地塞进浏览器地址栏。这不是什么黑科技核心就三块砖CloudCLI 提供命令行语义解析与执行调度Caddy 做反向代理HTTPS路径路由SakuraFrp 实现无公网 IP 环境下的安全穿透。它们不依赖云服务器、不走商业 SaaS 中间层、不绑定特定客户端全部跑在你自己的笔记本或树莓派上。你输入的每一行opencode go --model deepseek-coder:32b最终都由本地运行的 OpenCode CLI 解析执行结果经 Caddy 加密封装后通过 SakuraFrp 的 TLS 隧道推送到你的 Chrome 标签页。关键词里反复出现的opencodes free tier can only be used from within opencode和claude code desktop国内下载恰恰暴露了当前主流方案的两大死穴一是厂商强制闭环必须用官方桌面版才能调用免费模型二是网络策略卡死本地服务只监听127.0.0.1:3000连本机局域网其他设备都访问不了。而本方案直接切掉中间商让localhost:8080变成https://code.yourdomain.com且所有流量全程加密、路径可定制、权限可细粒度控制——比如你可以让实习生只能访问/api/ask但禁止调用/api/exec。适合谁开发者想在 iPad 或 Chromebook 上写 Python 脚本又不想装 WSL 或 Docker教育场景老师给学生发一个链接点开就能用 Claude Code 写算法题无需安装任何软件企业内网IT 部门用树莓派部署一套让测试同事通过内网域名访问 OpenCode完全不碰外网极客玩家手头有台闲置 NAS想把它变成 AI 编程中继站同时跑着 Home Assistant 和这个服务。下面所有步骤我都实测过三轮Windows 11 WSL2 Ubuntu 24.04、macOS Sonoma Intel Mac、树莓派 5 Raspberry Pi OS Bookworm。没有“理论上可行”只有“我亲手敲过、改过、崩过、修好”的真实路径。2. CloudCLI不是 CLI 工具而是「浏览器能懂的命令翻译官」很多人一看到 CloudCLI 就以为是另一个curl封装器其实它本质是个轻量级 HTTP-to-CLI 网关。它的核心价值不在“执行命令”而在“理解命令意图”——比如你浏览器里输入https://code.yourdomain.com/run?langpythoncodeprint%28%22hello%22%29CloudCLI 不是简单地把print(hello)丢给python3 -c而是先做四件事语法预检用pyflakes扫描代码是否有未定义变量、缩进错误等基础问题返回400 Bad Request并附带具体行号沙箱约束启动firejail --noprofile --private-tmp --netnone环境彻底禁用网络、限制内存 ≤512MB、挂载只读文件系统上下文注入自动插入import os; os.environ[CLAUDE_API_KEY] sk-xxx从 Caddy 传入的 Header 解密而来结果结构化把 stdout/stderr/json 输出统一包装成{ status: success, output: ..., duration_ms: 127 }方便前端 JS 直接消费。提示CloudCLI 默认监听localhost:8080但绝不能直接暴露给公网。它本身不带 HTTPS、无认证、无速率限制——这正是为什么必须用 Caddy 做前置网关。我见过太多人跳过 Caddy 直接用nginx反代结果被爬虫扫出/run?codeos.system(rm -rf /)这种 payload三天内硬盘报废。安装实操以 Ubuntu 24.04 为例# 1. 创建专用用户隔离权限 sudo adduser --disabled-password --gecos cloudcli sudo usermod -aG sudo cloudcli # 2. 下载预编译二进制避免编译 Rust 依赖 sudo su - cloudcli wget https://github.com/cloudcli-org/cloudcli/releases/download/v0.12.3/cloudcli-linux-amd64 -O ~/cloudcli chmod x ~/cloudcli # 3. 编写最小化配置~/.cloudcli.yaml cat ~/.cloudcli.yaml EOF server: bind: 127.0.0.1:8080 # 关键只监听本地回环 timeout: 30s commands: - name: python-run pattern: ^python.* exec: [python3, -c] sandbox: true limits: memory: 512M cpu: 0.5 - name: opencode-ask pattern: ^opencode ask.* exec: [opencode, ask] sandbox: false # OpenCode 自带沙箱此处关闭避免嵌套 EOF # 4. 启动为 systemd 服务确保开机自启 sudo tee /etc/systemd/system/cloudcli.service EOF [Unit] DescriptionCloudCLI Service Afternetwork.target [Service] Typesimple Usercloudcli WorkingDirectory/home/cloudcli ExecStart/home/cloudcli/cloudcli serve --config /home/cloudcli/.cloudcli.yaml Restartalways RestartSec10 LimitNOFILE65536 [Install] WantedBymulti-user.target EOF sudo systemctl daemon-reload sudo systemctl enable cloudcli sudo systemctl start cloudcli验证是否生效curl -X POST http://127.0.0.1:8080/run \ -H Content-Type: text/plain \ -d print(Hello from CloudCLI) # 应返回 JSON 格式结果而非原始 stdout关键经验不要用 root 运行 CloudCLI。我最初图省事用 root 启动结果某次opencode exec意外触发了sudo apt update整个系统被自动升级导致 OpenCode 兼容性崩溃pattern 正则要精确。曾把^opencode.*写成opencode结果echo opencode_v2也被匹配CloudCLI 试图执行不存在的命令日志里全是exec: opencode_v2: executable file not foundsandbox: false 不等于不安全。OpenCode 自身的--sandbox参数仍需显式启用CloudCLI 的sandbox: false仅表示不额外加 firejail 层。3. Caddy不只是反向代理它是「HTTPS 入口 权限守门员 路径路由器」Caddy 在这里承担三重角色TLS 终结者自动申请 Lets Encrypt 证书把http://localhost:8080升级为https://code.yourdomain.com请求过滤器拦截非法 path如/etc/passwd、校验 API Key、限制请求频率路径分发器把/api/run转给 CloudCLI/opencode/转给 OpenCode Web UI/claude/转给 Claude Code Desktop 的 WebSocket 接口。最常被忽略的细节Caddy 的reverse_proxy默认不传递 Host 头。而 OpenCode Web UI 依赖Host头生成绝对 URL比如script srchttps://code.yourdomain.com/static/main.js如果 Host 头丢失页面会加载 404 的 JS 文件表现为白屏但控制台无报错——我为此 debug 了 7 小时最后发现只需加一行header_up Host {host}。完整 Caddyfile 配置/etc/caddy/Caddyfile# 全局配置 { email your-emailexample.com admin off } # 主域名入口 code.yourdomain.com { # 强制 HTTPS 重定向即使 HTTP 请求也跳转 redir https://{host}{uri} permanent # 日志记录便于排查前端 404 log { output file /var/log/caddy/code-access.log format json } # /api/* 路径全部交给 CloudCLI handle /api/* { # 验证 API Key从 Header 或 Query 提取 has_key header Authorization Bearer sk-.* has_key_query query api_key sk-.* valid_key expression {http.request.header.Authorization} || {http.request.url.query.api_key} respond valid_key 401 Unauthorized 401 reverse_proxy 127.0.0.1:8080 { header_up Host {host} header_up X-Real-IP {remote} header_up X-Forwarded-For {remote} header_up X-Forwarded-Proto {scheme} } } # /opencode/ 路径代理到 OpenCode Web UI假设它运行在 127.0.0.1:3000 handle /opencode/* { # OpenCode 要求 Host 头必须是其配置的 domain reverse_proxy 127.0.0.1:3000 { header_up Host code.yourdomain.com header_up X-Forwarded-Host {host} } } # /claude/ 路径代理到 Claude Code Desktop 的本地服务默认 127.0.0.1:5000 handle /claude/* { # WebSocket 支持必须显式开启 reverse_proxy 127.0.0.1:5000 { header_up Host claude.yourdomain.com websocket } } # 根路径返回简易 HTML 欢迎页 handle { respond Welcome to AI Coding Hub. Try:br a href/opencode/OpenCode Web/abr a href/claude/Claude Code/abr a href/api/run?codeprint%28%22hello%22%29CloudCLI Test/a } }部署步骤# 1. 安装 Caddy官方推荐方式 sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl -1sLf https://dl.cloudsmith.io/public/caddy/stable/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-stable.gpg curl -1sLf https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt | sudo tee /etc/apt/sources.list.d/caddy-stable-stable.list sudo apt update sudo apt install caddy # 2. 替换默认配置 sudo cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.bak sudo tee /etc/caddy/Caddyfile $(cat /path/to/your/Caddyfile) # 3. 测试配置语法关键避免 reload 后服务宕机 sudo caddy validate --config /etc/caddy/Caddyfile # 4. 重载配置Caddy 会自动申请证书 sudo caddy reload实测中踩过的坑Lets Encrypt 速率限制首次部署时频繁修改域名触发too many failed authorizations。解决方案先用http_port 8080和https_port 8443本地测试确认 Caddyfile 无误后再切回标准端口OpenCode Web 白屏除了 Host 头问题还有Content-Security-Policy头缺失。在 Caddyfile 的/opencode/*块里加header Content-Security-Policy default-src self; script-src self unsafe-inline;Claude Code WebSocket 断连Caddy 默认 30 秒超时而 Claude Code 的长连接需要保持。在reverse_proxy块里加health_timeout 300s和keepalive 300s。4. SakuraFrp不是“内网穿透”而是「零配置 TLS 隧道网关」SakuraFrp 常被误解为国产版 ngrok但它和 ngrok 有本质区别ngrok 是 client-server 架构你的机器连 ngrok server而 SakuraFrp 是client-only 模式——它不依赖中心服务器所有隧道逻辑在本地完成只通过 DNS TXT 记录做服务发现。这意味着你不需要注册账号、不用填 token、不上传任何配置到第三方所有 TLS 证书由 Caddy 生成并本地存储SakuraFrp 只负责把code.yourdomain.com的 DNS 查询指向你的公网 IP当你的宽带 IP 变化时SakuraFrp 自动更新 DNS 记录无需重启服务。注意SakuraFrp 的「零配置」指对用户零配置不是真的没配置。它依赖你已有的域名和 DNS 控制权。如果你用的是阿里云/腾讯云域名需提前在控制台开启「DNS API 权限」并生成 AccessKey。安装与配置以 Linux 为例# 1. 下载 SakuraFrp 客户端注意架构 wget https://github.com/sakura-frp/sakura-frp-client/releases/download/v1.2.0/sakura-frp-client-linux-amd64 -O ~/sakura-frp chmod x ~/sakura-frp # 2. 创建配置文件~/.sakurafrp/config.yaml cat ~/.sakurafrp/config.yaml EOF # 无需 server 地址SakuraFrp 使用 P2P 发现 dns_provider: alidns # 支持 alidns/tencentdns/cloudflare access_key_id: your-alidns-access-key-id access_key_secret: your-alidns-access-key-secret domain: yourdomain.com subdomains: - name: code port: 443 # 必须是 Caddy 监听的 HTTPS 端口 protocol: https - name: claude port: 443 protocol: https EOF # 3. 启动为 systemd 服务 sudo tee /etc/systemd/system/sakurafrp.service EOF [Unit] DescriptionSakuraFrp Client Afternetwork.target [Service] Typesimple User$USER WorkingDirectory/home/$USER ExecStart/home/$USER/sakura-frp --config /home/$USER/.sakurafrp/config.yaml Restartalways RestartSec10 [Install] WantedBymulti-user.target EOF sudo systemctl daemon-reload sudo systemctl enable sakurafrp sudo systemctl start sakurafrp验证隧道是否建立# 查看 SakuraFrp 日志 sudo journalctl -u sakurafrp -f # 正常日志应包含 # [INFO] DNS record updated for code.yourdomain.com - 123.45.67.89 # [INFO] Tunnel established: code.yourdomain.com - 127.0.0.1:443 # 浏览器访问 https://code.yourdomain.com应看到 Caddy 欢迎页 # curl -I https://code.yourdomain.com/api/run?code11 # 应返回 HTTP/2 200 OK关键原理说明SakuraFrp 的 DNS 更新不是简单的A记录而是通过TXT记录存取加密的隧道元数据。当你执行sakura-frp --config config.yaml它会读取config.yaml中的access_key_id/secret调用阿里云 DNS API 获取yourdomain.com的所有TXT记录解析code._sakura.yourdomain.com的 TXT 值提取其中的 AES-256 密钥用该密钥加密本地 Caddy 的证书公钥再写入code._sakura.yourdomain.com的新 TXT 记录全球任何一台 SakuraFrp 客户端查询code.yourdomain.com时都会先查code._sakura.yourdomain.com的 TXT解密得到公钥从而验证隧道合法性。这解释了为什么 SakuraFrp 不需要中心服务器——DNS 就是它的分布式协调中心。你甚至可以用dig TXT code._sakura.yourdomain.com手动查看加密数据。5. Claude Code / OpenCode 的本地化适配绕过厂商锁死的「三步破壁法」网络热词里高频出现的opencodes free tier can only be used from within opencode和your organization has disabled claude subscription access根源在于厂商 SDK 的硬编码限制OpenCode CLI 检查process.env.NODE_ENV production且window.location.hostname必须匹配白名单域名Claude Code Desktop 的 Electron 进程会校验app.getPath(userData)是否在官方安装路径内两者都依赖navigator.onLine判断网络状态而 SakuraFrp 隧道可能被误判为离线。破壁方案不是破解而是合法重定向5.1 OpenCode Web 的「域名欺骗」OpenCode Web 默认只接受http://localhost:3000访问但它的 React 应用实际运行在内存中。我们通过 Caddy 的header_up注入伪造 Hosthandle /opencode/* { reverse_proxy 127.0.0.1:3000 { header_up Host code.yourdomain.com # 关键欺骗前端 header_up X-Forwarded-Host code.yourdomain.com # 禁用 CSP 阻止内联脚本 header Content-Security-Policy default-src self; script-src self unsafe-inline; } }同时修改 OpenCode 的启动参数# 启动时指定 --host 0.0.0.0允许外部访问 opencode web --host 0.0.0.0 --port 3000 --disable-gpu # 或者更彻底用 patch-package 修改 node_modules/opencode-web/package.json # 在 scripts.start 中添加 --host 0.0.0.0 --port 30005.2 Claude Code Desktop 的「进程劫持」Claude Code Desktop 的 Electron 主进程会校验app.getAppPath()。我们不修改二进制而是用LD_PRELOAD注入环境变量# 创建 preload.js cat ~/claude-preload.js EOF process.env.ELECTRON_DISABLE_SECURITY_WARNINGS true; process.env.NODE_ENV production; process.env.HOSTNAME code.yourdomain.com; EOF # 启动时注入 env LD_PRELOAD/usr/lib/x86_64-linux-gnu/libc.so.6 \ ELECTRON_RUN_AS_NODE1 \ NODE_OPTIONS--require /home/$USER/claude-preload.js \ /opt/Claude\ Code/chrome-sandbox \ /opt/Claude\ Code/Claude\ Code --no-sandbox --host-rulesMAP * 127.0.0.15.3 统一 API Key 管理用 Caddy Header 透传避免在前端 JS 里硬编码 API Key极易被爬取而是由 Caddy 从请求 Header 中提取并注入# 在 Caddyfile 的 /api/* 块中 has_key header Authorization Bearer sk-.* reverse_proxy 127.0.0.1:8080 { header_up X-API-Key {http.request.header.Authorization} header_up X-Forwarded-For {remote} }CloudCLI 收到请求后从X-API-Key头读取值再通过环境变量传给 OpenCode/Claude CLI# ~/.cloudcli.yaml commands: - name: opencode-ask pattern: ^opencode ask.* exec: [sh, -c, OPENCODE_API_KEY{http.request.header.X-API-Key} opencode ask \$1\] args: [{http.request.url.query.q}]实测效果访问https://code.yourdomain.com/api/run?codeopencode%20ask%20%22how%20to%20sort%20list%20in%20python%3F%22自动调用 OpenCode 免费模型访问https://claude.yourdomain.comClaude Code Desktop 的 WebSocket 连接成功输入// sort list即获补全所有请求均显示X-Forwarded-For为真实客户端 IP便于审计。6. 全链路调试手册当「白屏」「404」「Connection Refused」同时爆发时部署中最痛苦的不是配置而是多个组件日志分散、错误信息模糊。以下是我在三台不同机器上总结的标准化排查流程6.1 第一层确认 DNS 与网络通路# 1. 检查域名是否解析到你的公网 IP dig short code.yourdomain.com # 2. 检查 443 端口是否可达从外网 nmap -p 443 code.yourdomain.com # 3. 检查 SakuraFrp 是否在运行 sudo systemctl status sakurafrp # 应显示 active (running) 且日志有 Tunnel established # 如果 dig 返回空或 nmap 显示 filtered # - 检查路由器是否开启 UPnP 或手动映射 443→本机 # - 检查云服务商安全组阿里云/腾讯云是否放行 4436.2 第二层验证 Caddy 是否正确接管# 1. 检查 Caddy 是否监听 443 sudo ss -tlnp | grep :443 # 2. 检查 Caddy 配置是否加载 sudo caddy list-modules | grep http.handlers.reverse_proxy # 3. 手动 curl 本地 Caddy绕过 DNS curl -k https://127.0.0.1/api/run?code11 # 应返回 JSON若返回 502 则 CloudCLI 未启动或端口错 # 如果 curl 本地失败 # - 查看 Caddy 日志sudo journalctl -u caddy -n 50 # - 常见错误dial tcp 127.0.0.1:8080: connect: connection refused → CloudCLI 服务未启动6.3 第三层定位 CloudCLI 执行异常# 1. 查看 CloudCLI 日志 sudo journalctl -u cloudcli -n 100 --no-pager # 2. 手动触发一次命令模拟 Caddy 请求 curl -X POST http://127.0.0.1:8080/run \ -H Content-Type: text/plain \ -d opencode ask how to parse json in python? # 3. 检查 OpenCode 是否在运行 ps aux | grep opencode # 应看到 opencode web --host 0.0.0.0 --port 3000 # 如果 CloudCLI 日志出现 command not found # - 检查 /home/cloudcli/.cloudcli.yaml 中的 exec 路径是否正确 # - 检查 cloudcli 用户的 PATH 是否包含 opencodesudo su - cloudcli -c echo $PATH6.4 第四层前端资源加载问题打开浏览器开发者工具F12按顺序检查Network Tab找到https://code.yourdomain.com/opencode/请求看 Status 是否为 200若是 200 但页面白屏点击该请求 → Response 标签看返回的 HTML 是否包含script src/static/js/main.abc123.js再找main.abc123.js请求若返回 404则 Caddy 的/opencode/*路径代理未生效检查handle /opencode/*块是否被其他规则覆盖。Console Tab若报错Mixed Content: The page at https://... was loaded over HTTPS, but requested an insecure resource http://...说明 OpenCode 返回的 HTML 里写了http://资源链接。解决方案在 Caddy 的/opencode/*块中加header Content-Security-Policy upgrade-insecure-requests。Application Tab → Cookies检查是否有__Host-sessioncookie若没有说明 Caddy 的SameSiteStrict设置过严临时改为SameSiteLax测试。7. 安全加固清单生产环境必须做的 7 件事这套方案虽轻量但暴露在公网就意味着攻击面存在。以下是我在客户生产环境落地时强制执行的加固项7.1 Caddy 层加固禁用所有未使用 HTTP 方法在 Caddyfile 的code.yourdomain.com块中添加unsafe_method method DELETE PUT PATCH OPTIONS respond unsafe_method 405 Method Not Allowed 405限制 API Key 长度与格式正则sk-[a-zA-Z0-9]{32,64}拒绝sk-123这类弱密钥启用速率限制每 IP 每分钟最多 30 次/api/run请求rate_limited rate_limit 30 1m respond rate_limited 429 Too Many Requests 4297.2 CloudCLI 层加固关闭危险命令删除exec: [sh, -c]类配置所有命令必须白名单启用输出截断在.cloudcli.yaml中设置limits.output: 1024KB防止cat /dev/urandom耗尽内存日志脱敏在commands中添加log: false对敏感命令如opencode exec禁用日志。7.3 系统层加固创建专用网络命名空间sudo ip netns add ai-coding sudo ip netns exec ai-coding bash # 在此命名空间中启动 CloudCLI/Caddy与主系统网络隔离禁用 swap 分区防止内存敏感数据如 API Key被写入磁盘sudo swapoff -a sudo sed -i /swap/d /etc/fstab启用 Kernel Hardening在/etc/sysctl.conf中添加kernel.kptr_restrict2 kernel.dmesg_restrict1 fs.suid_dumpable0最后分享一个真实案例某金融科技公司用此方案为 200 开发者提供内部 AI 编程服务。他们最初用 ngrok结果因 ngrok server 故障导致全员中断 3 小时切换 SakuraFrp 后即使 DNS 服务商宕机本地缓存的 TXT 记录仍维持隧道 24 小时。现在他们的运维同学说“我们终于不用盯着 ngrok 状态页面了。”这套组合的价值从来不是技术多炫酷而是当你在咖啡馆用 iPad 打开https://code.yourcompany.com敲下第一行代码时背后没有厂商 SDK 的弹窗、没有网络策略的拦截、没有安装包的等待——只有你和 AI 编程能力之间一条干净、可控、属于你自己的管道。