保姆级教程:

📌 本文所有域名、密钥、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 只在手机上)。

💻 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 证书。

HTTPS 443

http 13005

📱 iPhone

Nginx Proxy Manager
Let's Encrypt 证书
✅ WebSocket

happy-server 容器
:3005

  1. 域名解析:happyserver.example.com → 服务器公网 IP。
  2. 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
  3. 验证(外网):
curl -I https://happyserver.example.com/
# 期望:HTTP/2 200 ... Welcome to Happy Server!

外网能 200,说明 DNS + HTTPS + 反代 + 容器整条链路通了。


五、手机端配置(关键:官方 App 的服务器入口是隐藏的!)

很多人卡在这一步:Settings 里根本找不到填服务器的地方。因为它藏在 开发者模式 里:

  1. 打开 Happy App → Settings → 拉到底,连续点「版本号」 进入开发者模式;
  2. 进入后找 Network 区 → API Endpoint;
  3. 填入 https://happyserver.example.com,保存。

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) 用 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;非改不可就当全新服务器,清卷 + 所有端重新建号。


九、故障排查速查图

没到

到了但 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 不是唯一解

方案 有无 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 ControlSSH + tmux 手机操控 Claude Code 的对比实战。有问题评论区见~

关键词:Happy / Claude Code / 自托管 / iPhone 远程开发 / Docker / Nginx Proxy Manager / 端到端加密

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐