自托管 Happy 服务器,用 iPhone 远程操控 Claude Code(含全部踩坑+约束清单)
把 Claude Code 跑在自己的 Mac / 服务器上,然后用iPhone 随时随地远程操控,是很多人想要的工作流。开源项目正好干这件事:它给 Claude Code 套了一层端到端加密的中转通道,手机当远程终端。官方有云端中转,但国内网络 + 隐私 + 用 API Key / Bedrock的同学,更适合自托管。我在一台 AWS ARM 服务器上把这套完整跑通,中间踩了一堆坑(401、令牌
保姆级教程:
目录
📌 本文所有域名、密钥、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 只在手机上)。
记住这张图:后面所有「连不上 / 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 证书。
- 域名解析:
happyserver.example.com→ 服务器公网 IP。 - NPM 新建 Proxy Host:
- Domain Names:
happyserver.example.com - Forward Hostname/IP:容器 IP 或
127.0.0.1,端口13005 - ✅ 勾选 Websockets Support(约束⑤,必开!)
- SSL 选项卡:Request a new SSL Certificate + Force SSL
- Domain Names:
- 验证(外网):
curl -I https://happyserver.example.com/
# 期望:HTTP/2 200 ... Welcome to Happy Server!
外网能 200,说明 DNS + HTTPS + 反代 + 容器整条链路通了。
五、手机端配置(关键:官方 App 的服务器入口是隐藏的!)
很多人卡在这一步:Settings 里根本找不到填服务器的地方。因为它藏在 开发者模式 里:
- 打开 Happy App → Settings → 拉到底,连续点「版本号」 进入开发者模式;
- 进入后找 Network 区 → API Endpoint;
- 填入
https://happyserver.example.com,保存。
App 内部逻辑:自定义服务器地址存在
custom-server-url,退出登录也不会被清掉,所以填一次即可。
六、终端配对
Mac 上:
export HAPPY_SERVER_URL=https://happyserver.example.com
happy
终端出二维码 → 手机 Happy 扫码 → 配对成功后,Mac 显示 Remote Mode - Claude Messages,手机即可发消息控制。
配对 / 认证的完整时序如下:
实时盯日志确认:
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;非改不可就当全新服务器,清卷 + 所有端重新建号。
九、故障排查速查图
| 现象 | 先查 | 解决方向 |
|---|---|---|
| 外网 curl 不通 | DNS / 安全组 / 反代 | 解析、放行 443、NPM 配置 |
Welcome 出不来 |
容器健康 | docker logs、/health |
| 全部 401 | 令牌 / 密钥 / 数据卷 | 坑④⑤⑥ |
| 配对卡住 | WebSocket | NPM 开 WS |
| 手机连不上但浏览器能开 | 已知 Issue #501 | App 自托管支持有未修 bug,优先清卷 + 重新建号 |
十、选型对比:Happy 不是唯一解
| 方案 | 有无 remote mode 限制 | 后端要求 | 适合场景 |
|---|---|---|---|
| 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 / 端到端加密
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)