保姆级教程:
目录
- 保姆级教程:
-
- 前言
- 一、先搞懂原理(否则坑都不知道怎么来的)
- 二、⚠️ 开始前必须搞清楚的 5 条约束(本文精华)
- 三、服务器端部署(Docker,5 分钟)
-
- 3.1 准备镜像
- 3.2 生成并固定 master secret(⚠️ 存好,永不更改)
- 3.3 启动容器
- 3.4 自检
- 四、HTTPS + 域名 + 反向代理(用 Nginx Proxy Manager)
- 五、手机端配置(关键:官方 App 的服务器入口是隐藏的!)
- 六、终端配对
- 七、日常使用:控制权 & 权限
-
- 7.1 remote mode 与本地切换
- 7.2 少被权限弹窗打断
- 八、🕳️ 踩坑大全(重点中的重点)
-
- 坑① http 死活连不上
- 坑② Settings 里找不到填服务器的地方
- 坑③ 配对卡住、收不到消息
- 坑④ 一直 401「Auth failed – invalid token」,清了又来
- 坑⑤ 卸载重装 App 也没用,令牌还是同一个
- 坑⑥ 改了 secret 后全员掉线
- 九、故障排查速查图
- 十、选型对比:Happy 不是唯一解
- 总结
📌 本文所有域名、密钥、IP 均为占位/脱敏示例,实操时替换为你自己的值。 约定:域名用 happyserver.example.com;密钥用 <YOUR_MASTER_SECRET>(64 位随机十六进制)。
前言
把 Claude Code 跑在自己的 Mac / 服务器上,然后用 iPhone 随时随地远程操控,是很多人想要的工作流。开源项目 Happy(slopus/happy) 正好干这件事:它给 Claude Code 套了一层端到端加密的中转通道,手机当远程终端。
官方有云端中转,但 国内网络 + 隐私 + 用 API Key / Bedrock 的同学,更适合 自托管。我在一台 AWS ARM 服务器上把这套完整跑通,中间踩了一堆坑(401、令牌失效、HTTPS、数据卷污染……),这篇把 部署步骤 + 约束条件 + 避坑点 一次性讲透。
本文环境:服务器 = AWS Graviton(ARM / Ubuntu)+ Docker;客户端 = iPhone 官方 App Store 版 Happy;被控端 = Mac 上的 Claude Code。
一、先搞懂原理(否则坑都不知道怎么来的)
Happy 不是远程桌面 / 不是屏幕共享,它是一条 加密的「文字消息转发通道」:Claude Code 始终只跑在你的 Mac 上,手机只是个「加密远程键盘 + 屏幕」;中转服务器只搬运密文,读不到你的代码和对话(master secret 只在手机上)。
#mermaid-svg-6kcmQWxGk0MoqDmF{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6kcmQWxGk0MoqDmF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6kcmQWxGk0MoqDmF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6kcmQWxGk0MoqDmF .error-icon{fill:#552222;}#mermaid-svg-6kcmQWxGk0MoqDmF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6kcmQWxGk0MoqDmF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6kcmQWxGk0MoqDmF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6kcmQWxGk0MoqDmF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6kcmQWxGk0MoqDmF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6kcmQWxGk0MoqDmF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6kcmQWxGk0MoqDmF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6kcmQWxGk0MoqDmF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6kcmQWxGk0MoqDmF .marker.cross{stroke:#333333;}#mermaid-svg-6kcmQWxGk0MoqDmF svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6kcmQWxGk0MoqDmF p{margin:0;}#mermaid-svg-6kcmQWxGk0MoqDmF .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-6kcmQWxGk0MoqDmF .cluster-label text{fill:#333;}#mermaid-svg-6kcmQWxGk0MoqDmF .cluster-label span{color:#333;}#mermaid-svg-6kcmQWxGk0MoqDmF .cluster-label span p{background-color:transparent;}#mermaid-svg-6kcmQWxGk0MoqDmF .label text,#mermaid-svg-6kcmQWxGk0MoqDmF span{fill:#333;color:#333;}#mermaid-svg-6kcmQWxGk0MoqDmF .node rect,#mermaid-svg-6kcmQWxGk0MoqDmF .node circle,#mermaid-svg-6kcmQWxGk0MoqDmF .node ellipse,#mermaid-svg-6kcmQWxGk0MoqDmF .node polygon,#mermaid-svg-6kcmQWxGk0MoqDmF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6kcmQWxGk0MoqDmF .rough-node .label text,#mermaid-svg-6kcmQWxGk0MoqDmF .node .label text,#mermaid-svg-6kcmQWxGk0MoqDmF .image-shape .label,#mermaid-svg-6kcmQWxGk0MoqDmF .icon-shape .label{text-anchor:middle;}#mermaid-svg-6kcmQWxGk0MoqDmF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6kcmQWxGk0MoqDmF .rough-node .label,#mermaid-svg-6kcmQWxGk0MoqDmF .node .label,#mermaid-svg-6kcmQWxGk0MoqDmF .image-shape .label,#mermaid-svg-6kcmQWxGk0MoqDmF .icon-shape .label{text-align:center;}#mermaid-svg-6kcmQWxGk0MoqDmF .node.clickable{cursor:pointer;}#mermaid-svg-6kcmQWxGk0MoqDmF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6kcmQWxGk0MoqDmF .arrowheadPath{fill:#333333;}#mermaid-svg-6kcmQWxGk0MoqDmF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6kcmQWxGk0MoqDmF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6kcmQWxGk0MoqDmF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6kcmQWxGk0MoqDmF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6kcmQWxGk0MoqDmF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6kcmQWxGk0MoqDmF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6kcmQWxGk0MoqDmF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6kcmQWxGk0MoqDmF .cluster text{fill:#333;}#mermaid-svg-6kcmQWxGk0MoqDmF .cluster span{color:#333;}#mermaid-svg-6kcmQWxGk0MoqDmF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6kcmQWxGk0MoqDmF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6kcmQWxGk0MoqDmF rect.text{fill:none;stroke-width:0;}#mermaid-svg-6kcmQWxGk0MoqDmF .icon-shape,#mermaid-svg-6kcmQWxGk0MoqDmF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6kcmQWxGk0MoqDmF .icon-shape p,#mermaid-svg-6kcmQWxGk0MoqDmF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6kcmQWxGk0MoqDmF .icon-shape .label rect,#mermaid-svg-6kcmQWxGk0MoqDmF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6kcmQWxGk0MoqDmF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6kcmQWxGk0MoqDmF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6kcmQWxGk0MoqDmF :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
💻 Mac (happy CLI 包着 claude)
☁️ 你的中转服务器 happy-server
📱 iPhone (Happy App)
WebSocket 端到端加密
WebSocket 端到端加密
输出加密回传
回传密文
你打字 / 看输出
只转发密文读不到内容
claude 进程真正干活
记住这张图:后面所有「连不上 / 401」的坑,本质都是这条链路上某一环没对齐。
二、⚠️ 开始前必须搞清楚的 5 条约束(本文精华)
这 5 条没搞清楚,99% 会卡住:
| 1 | 必须 HTTPS,且是受系统信任的证书 | iOS 的 ATS 机制禁止明文 http;自签证书除非把 CA 装进 iPhone 并信任,否则也不行。最省事:域名 + Let’s Encrypt。 |
| 2 | HANDY_MASTER_SECRET 一旦定下,永远别改 | 它是服务器签发 / 校验登录令牌(JWT)的种子。改了 = 所有已配对账号的令牌全部失效(401)。 |
| 3 | 数据卷 /data 不能「脏」 | 卷里若残留「用旧密钥建的账号」,新密钥校验不过 → 一直 401。换密钥务必清卷重来(见踩坑④)。 |
| 4 | 手机不能带着「云端旧令牌」连自建服务器 | App 之前在官方云建过号,令牌是云端密钥签的,你的服务器认不了 → 401。必须重新建号(见踩坑⑤)。 |
| 5 | 反向代理必须开 WebSocket | 不开 WS,实时消息通道建不起来,表现为「能登录但收不到消息 / 配对卡住」。 |
三、服务器端部署(Docker,5 分钟)
3.1 准备镜像
git clone https://github.com/slopus/happy.git
cd happy
# 仓库根目录自带 standalone Dockerfile(内置 PGlite + 本地存储,无需额外装 PG/Redis)
docker build -t happy-standalone .
3.2 生成并固定 master secret(⚠️ 存好,永不更改)
openssl rand -hex 32
# 输出一串 64 位十六进制,妥善保存,后面用 <YOUR_MASTER_SECRET> 代表它
3.3 启动容器
docker run -d –name happy-server \\
-p 13005:3005 \\
-e HANDY_MASTER_SECRET=<YOUR_MASTER_SECRET> \\
-e PUBLIC_URL=https://happyserver.example.com \\
-v happy-data:/data \\
–restart unless-stopped \\
happy-standalone
关键点:-p 13005:3005(容器内监听 3005)、PUBLIC_URL 填你的 HTTPS 域名、–restart unless-stopped 保证重启自恢复。
3.4 自检
curl -I http://127.0.0.1:13005/
# 期望:HTTP 200,页面 Welcome to Happy Server!
curl http://127.0.0.1:13005/health
# 期望:{"status":"ok",…}
四、HTTPS + 域名 + 反向代理(用 Nginx Proxy Manager)
iOS 必须 HTTPS,所以前面挂一层反代,自动签 Let’s Encrypt 证书。
#mermaid-svg-cxzfEbdi6joRNKe0{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cxzfEbdi6joRNKe0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cxzfEbdi6joRNKe0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cxzfEbdi6joRNKe0 .error-icon{fill:#552222;}#mermaid-svg-cxzfEbdi6joRNKe0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cxzfEbdi6joRNKe0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cxzfEbdi6joRNKe0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cxzfEbdi6joRNKe0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cxzfEbdi6joRNKe0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cxzfEbdi6joRNKe0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cxzfEbdi6joRNKe0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cxzfEbdi6joRNKe0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cxzfEbdi6joRNKe0 .marker.cross{stroke:#333333;}#mermaid-svg-cxzfEbdi6joRNKe0 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cxzfEbdi6joRNKe0 p{margin:0;}#mermaid-svg-cxzfEbdi6joRNKe0 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-cxzfEbdi6joRNKe0 .cluster-label text{fill:#333;}#mermaid-svg-cxzfEbdi6joRNKe0 .cluster-label span{color:#333;}#mermaid-svg-cxzfEbdi6joRNKe0 .cluster-label span p{background-color:transparent;}#mermaid-svg-cxzfEbdi6joRNKe0 .label text,#mermaid-svg-cxzfEbdi6joRNKe0 span{fill:#333;color:#333;}#mermaid-svg-cxzfEbdi6joRNKe0 .node rect,#mermaid-svg-cxzfEbdi6joRNKe0 .node circle,#mermaid-svg-cxzfEbdi6joRNKe0 .node ellipse,#mermaid-svg-cxzfEbdi6joRNKe0 .node polygon,#mermaid-svg-cxzfEbdi6joRNKe0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cxzfEbdi6joRNKe0 .rough-node .label text,#mermaid-svg-cxzfEbdi6joRNKe0 .node .label text,#mermaid-svg-cxzfEbdi6joRNKe0 .image-shape .label,#mermaid-svg-cxzfEbdi6joRNKe0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-cxzfEbdi6joRNKe0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cxzfEbdi6joRNKe0 .rough-node .label,#mermaid-svg-cxzfEbdi6joRNKe0 .node .label,#mermaid-svg-cxzfEbdi6joRNKe0 .image-shape .label,#mermaid-svg-cxzfEbdi6joRNKe0 .icon-shape .label{text-align:center;}#mermaid-svg-cxzfEbdi6joRNKe0 .node.clickable{cursor:pointer;}#mermaid-svg-cxzfEbdi6joRNKe0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cxzfEbdi6joRNKe0 .arrowheadPath{fill:#333333;}#mermaid-svg-cxzfEbdi6joRNKe0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cxzfEbdi6joRNKe0 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cxzfEbdi6joRNKe0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cxzfEbdi6joRNKe0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cxzfEbdi6joRNKe0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cxzfEbdi6joRNKe0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cxzfEbdi6joRNKe0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cxzfEbdi6joRNKe0 .cluster text{fill:#333;}#mermaid-svg-cxzfEbdi6joRNKe0 .cluster span{color:#333;}#mermaid-svg-cxzfEbdi6joRNKe0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cxzfEbdi6joRNKe0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cxzfEbdi6joRNKe0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-cxzfEbdi6joRNKe0 .icon-shape,#mermaid-svg-cxzfEbdi6joRNKe0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cxzfEbdi6joRNKe0 .icon-shape p,#mermaid-svg-cxzfEbdi6joRNKe0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cxzfEbdi6joRNKe0 .icon-shape .label rect,#mermaid-svg-cxzfEbdi6joRNKe0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cxzfEbdi6joRNKe0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cxzfEbdi6joRNKe0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cxzfEbdi6joRNKe0 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
HTTPS 443
http 13005
📱 iPhone
Nginx Proxy ManagerLet's Encrypt 证书✅ WebSocket
happy-server 容器:3005
- Domain Names:happyserver.example.com
- Forward Hostname/IP:容器 IP 或 127.0.0.1,端口 13005
- ✅ 勾选 Websockets Support(约束⑤,必开!)
- SSL 选项卡:Request a new SSL Certificate + Force SSL
curl -I https://happyserver.example.com/
# 期望:HTTP/2 200 … Welcome to Happy Server!
外网能 200,说明 DNS + HTTPS + 反代 + 容器整条链路通了。
五、手机端配置(关键:官方 App 的服务器入口是隐藏的!)
很多人卡在这一步:Settings 里根本找不到填服务器的地方。因为它藏在 开发者模式 里:
App 内部逻辑:自定义服务器地址存在 custom-server-url,退出登录也不会被清掉,所以填一次即可。
六、终端配对
Mac 上:
export HAPPY_SERVER_URL=https://happyserver.example.com
happy
终端出二维码 → 手机 Happy 扫码 → 配对成功后,Mac 显示 Remote Mode – Claude Messages,手机即可发消息控制。
配对 / 认证的完整时序如下:
📱 iPhone
☁️ happy-server
💻 Mac (happy CLI)
📱 iPhone
☁️ happy-server
💻 Mac (happy CLI)
#mermaid-svg-BkGyDdPDYbESqGtL{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BkGyDdPDYbESqGtL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BkGyDdPDYbESqGtL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BkGyDdPDYbESqGtL .error-icon{fill:#552222;}#mermaid-svg-BkGyDdPDYbESqGtL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BkGyDdPDYbESqGtL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BkGyDdPDYbESqGtL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BkGyDdPDYbESqGtL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BkGyDdPDYbESqGtL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BkGyDdPDYbESqGtL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BkGyDdPDYbESqGtL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BkGyDdPDYbESqGtL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BkGyDdPDYbESqGtL .marker.cross{stroke:#333333;}#mermaid-svg-BkGyDdPDYbESqGtL svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BkGyDdPDYbESqGtL p{margin:0;}#mermaid-svg-BkGyDdPDYbESqGtL .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BkGyDdPDYbESqGtL text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-BkGyDdPDYbESqGtL .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-BkGyDdPDYbESqGtL .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-BkGyDdPDYbESqGtL .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-BkGyDdPDYbESqGtL .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-BkGyDdPDYbESqGtL #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-BkGyDdPDYbESqGtL .sequenceNumber{fill:white;}#mermaid-svg-BkGyDdPDYbESqGtL #sequencenumber{fill:#333;}#mermaid-svg-BkGyDdPDYbESqGtL #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-BkGyDdPDYbESqGtL .messageText{fill:#333;stroke:none;}#mermaid-svg-BkGyDdPDYbESqGtL .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BkGyDdPDYbESqGtL .labelText,#mermaid-svg-BkGyDdPDYbESqGtL .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-BkGyDdPDYbESqGtL .loopText,#mermaid-svg-BkGyDdPDYbESqGtL .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-BkGyDdPDYbESqGtL .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-BkGyDdPDYbESqGtL .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-BkGyDdPDYbESqGtL .noteText,#mermaid-svg-BkGyDdPDYbESqGtL .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-BkGyDdPDYbESqGtL .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BkGyDdPDYbESqGtL .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BkGyDdPDYbESqGtL .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BkGyDdPDYbESqGtL .actorPopupMenu{position:absolute;}#mermaid-svg-BkGyDdPDYbESqGtL .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-BkGyDdPDYbESqGtL .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BkGyDdPDYbESqGtL .actor-man circle,#mermaid-svg-BkGyDdPDYbESqGtL line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-BkGyDdPDYbESqGtL :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
用 HANDY_MASTER_SECRET
校验手机令牌
POST /v1/auth/request (终端公钥)
200,返回待批准 + 出二维码
用手机账号令牌批准该终端
校验通过(200)
配对完成,建立加密 WebSocket
发消息(密文)
转发密文 → 喂给 claude
claude 输出(密文)
回传显示
实时盯日志确认:
docker logs -f happy-server
# 配对成功:手机的 /v1/account/profile、/v1/sessions 返回 200(不是 401)
七、日常使用:控制权 & 权限
7.1 remote mode 与本地切换
- 手机发消息 → Mac 自动进 Remote Mode(手机主控);
- 想在 Mac 上敲命令:键盘按任意键 → 切回本地完整交互式 claude;
- 同一个会话,谁动谁接管,一键来回切。
7.2 少被权限弹窗打断
手机端最烦的「限制」其实是权限确认弹窗,启动时放宽即可:
happy -p bypassPermissions # 或 -p acceptEdits
# 注意:不同版本参数可能不同,先 happy –help 确认
真正手机上用不了的,只有需要方向键交互的菜单(如 /resume 列表选择、plan 模式选项),这类操作按键切回 Mac 本地做。
八、🕳️ 踩坑大全(重点中的重点)
坑① http 死活连不上
现象:export HAPPY_SERVER_URL=http://ip:port 手机一直连不上。 原因:iOS ATS 禁明文 http。 解决:换 HTTPS 域名(见第四节)。
坑② Settings 里找不到填服务器的地方
原因:官方 App 把入口藏进了开发者模式。 解决:连点版本号 → Network → API Endpoint(见第五节)。
坑③ 配对卡住、收不到消息
原因:反代没开 WebSocket。 解决:NPM Proxy Host 勾上 Websockets Support。
坑④ 一直 401「Auth failed – invalid token」,清了又来
现象:手机请求都打到服务器了,但每个 /v1/… 都 401,日志 invalid token。 根因之一:数据卷里残留了用「旧 / 不同 HANDY_MASTER_SECRET」建的账号,当前密钥校验不过。 解决(⚠️ 会清空数据,从零开始):
docker rm -f happy-server
docker volume rm happy-data
# 用固定不变的 secret 重新 run(见 3.3)
我本人最后就是靠清卷重建彻底解决 401 的。
坑⑤ 卸载重装 App 也没用,令牌还是同一个
现象:退出登录 / 卸载重装后,日志里手机发的令牌一字不变。 根因:iOS 钥匙串(Keychain)在 App 卸载后不会被删,旧云令牌被一直复用。 解决:App 内 Settings → Account → 底部红色 Log out(这才会清钥匙串),然后在自建服务器上重新建号;光卸载重装无效。
坑⑥ 改了 secret 后全员掉线
原因:违反约束②,JWT 签名密钥变了。 解决:永远不要改 HANDY_MASTER_SECRET;非改不可就当全新服务器,清卷 + 所有端重新建号。
九、故障排查速查图
#mermaid-svg-RkkSTzfpzc9Ldhvg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RkkSTzfpzc9Ldhvg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RkkSTzfpzc9Ldhvg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RkkSTzfpzc9Ldhvg .error-icon{fill:#552222;}#mermaid-svg-RkkSTzfpzc9Ldhvg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RkkSTzfpzc9Ldhvg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RkkSTzfpzc9Ldhvg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RkkSTzfpzc9Ldhvg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RkkSTzfpzc9Ldhvg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RkkSTzfpzc9Ldhvg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RkkSTzfpzc9Ldhvg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RkkSTzfpzc9Ldhvg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RkkSTzfpzc9Ldhvg .marker.cross{stroke:#333333;}#mermaid-svg-RkkSTzfpzc9Ldhvg svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RkkSTzfpzc9Ldhvg p{margin:0;}#mermaid-svg-RkkSTzfpzc9Ldhvg .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-RkkSTzfpzc9Ldhvg .cluster-label text{fill:#333;}#mermaid-svg-RkkSTzfpzc9Ldhvg .cluster-label span{color:#333;}#mermaid-svg-RkkSTzfpzc9Ldhvg .cluster-label span p{background-color:transparent;}#mermaid-svg-RkkSTzfpzc9Ldhvg .label text,#mermaid-svg-RkkSTzfpzc9Ldhvg span{fill:#333;color:#333;}#mermaid-svg-RkkSTzfpzc9Ldhvg .node rect,#mermaid-svg-RkkSTzfpzc9Ldhvg .node circle,#mermaid-svg-RkkSTzfpzc9Ldhvg .node ellipse,#mermaid-svg-RkkSTzfpzc9Ldhvg .node polygon,#mermaid-svg-RkkSTzfpzc9Ldhvg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RkkSTzfpzc9Ldhvg .rough-node .label text,#mermaid-svg-RkkSTzfpzc9Ldhvg .node .label text,#mermaid-svg-RkkSTzfpzc9Ldhvg .image-shape .label,#mermaid-svg-RkkSTzfpzc9Ldhvg .icon-shape .label{text-anchor:middle;}#mermaid-svg-RkkSTzfpzc9Ldhvg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RkkSTzfpzc9Ldhvg .rough-node .label,#mermaid-svg-RkkSTzfpzc9Ldhvg .node .label,#mermaid-svg-RkkSTzfpzc9Ldhvg .image-shape .label,#mermaid-svg-RkkSTzfpzc9Ldhvg .icon-shape .label{text-align:center;}#mermaid-svg-RkkSTzfpzc9Ldhvg .node.clickable{cursor:pointer;}#mermaid-svg-RkkSTzfpzc9Ldhvg .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RkkSTzfpzc9Ldhvg .arrowheadPath{fill:#333333;}#mermaid-svg-RkkSTzfpzc9Ldhvg .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RkkSTzfpzc9Ldhvg .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RkkSTzfpzc9Ldhvg .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RkkSTzfpzc9Ldhvg .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RkkSTzfpzc9Ldhvg .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RkkSTzfpzc9Ldhvg .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RkkSTzfpzc9Ldhvg .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RkkSTzfpzc9Ldhvg .cluster text{fill:#333;}#mermaid-svg-RkkSTzfpzc9Ldhvg .cluster span{color:#333;}#mermaid-svg-RkkSTzfpzc9Ldhvg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RkkSTzfpzc9Ldhvg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RkkSTzfpzc9Ldhvg rect.text{fill:none;stroke-width:0;}#mermaid-svg-RkkSTzfpzc9Ldhvg .icon-shape,#mermaid-svg-RkkSTzfpzc9Ldhvg .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RkkSTzfpzc9Ldhvg .icon-shape p,#mermaid-svg-RkkSTzfpzc9Ldhvg .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RkkSTzfpzc9Ldhvg .icon-shape .label rect,#mermaid-svg-RkkSTzfpzc9Ldhvg .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RkkSTzfpzc9Ldhvg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RkkSTzfpzc9Ldhvg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RkkSTzfpzc9Ldhvg :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
否
是
没到
到了但 401
是
否
到了 200 但卡住
出问题了
外网 curl能 200 吗?
查 DNS / 安全组 443 / 反代配置
手机请求到服务器了吗?看 docker logs
查 App 的 API Endpoint是否填对 HTTPS 域名
令牌是不是云端旧令牌?
App 内 Log out → 重新建号坑⑤
清数据卷重建secret 保持不变坑④⑥
反代开 WebSocket坑③
| 外网 curl 不通 | DNS / 安全组 / 反代 | 解析、放行 443、NPM 配置 |
| Welcome 出不来 | 容器健康 | docker logs、/health |
| 全部 401 | 令牌 / 密钥 / 数据卷 | 坑④⑤⑥ |
| 配对卡住 | WebSocket | NPM 开 WS |
| 手机连不上但浏览器能开 | 已知 Issue #501 | App 自托管支持有未修 bug,优先清卷 + 重新建号 |
十、选型对比:Happy 不是唯一解
| Happy(自托管) | 有(手机主控时部分受限) | 任意:API Key / Bedrock / 订阅 | 国内网络、隐私、非订阅后端 |
| 官方 Remote Control(claude –remote-control) | 无,本地 + 手机同时输入 | 仅 claude.ai 订阅,不支持 API Key / Bedrock | 用订阅且能稳连 claude.ai |
| SSH + tmux(Termius / Blink) | 无,就是原生终端 | 任意 | 要零限制、所有交互命令都能用 |
一句话:用 claude.ai 订阅 + 网络能连 → 官方 Remote Control 更香;用 API Key / Bedrock 或国内网络受限 → Happy 自托管不可替代;要完全零限制 → SSH + tmux。
总结
自托管 Happy 的难点不在部署,而在 那 5 条约束:HTTPS 必须、secret 不可变、数据卷不能脏、手机别带云端旧令牌、反代开 WS。把这几条捋顺,401 和「连不上」基本都能避开。
如果这篇帮你少踩了坑,点个 赞 👍 + 收藏 ⭐ + 关注。后续会再写 官方 Remote Control 和 SSH + tmux 手机操控 Claude Code 的对比实战。有问题评论区见~
关键词:Happy / Claude Code / 自托管 / iPhone 远程开发 / Docker / Nginx Proxy Manager / 端到端加密
网硕互联帮助中心



评论前必须登录!
注册