|
| 1 | +--- |
| 2 | +title: "用 Tailscale + Terminus 在 iPad 上远程操控 Claude Code" |
| 3 | +author: xkcoding |
| 4 | +pubDatetime: 2026-02-17T00:00:00.000Z |
| 5 | +slug: remote-claude-code-with-tailscale-terminus |
| 6 | +featured: true |
| 7 | +draft: false |
| 8 | +ogImage: https://cdn.xkcoding.com/blog/2026-02-16-remote-claude-code-tailscale-cover-16x9.webp?x-oss-process=style/tag_compress |
| 9 | +tags: |
| 10 | + - ai |
| 11 | + - claude-code |
| 12 | + - tailscale |
| 13 | +description: "Claude Code 驱动的工作流用得越深,越离不开那台 Mac。上下文、笔记、项目全在上面,出门就接不上。本文对比了 frp、Cloudflare Tunnel、Happy Coder、Tailscale 几种远程方案,最后用 Tailscale 组网加 Terminus 做移动端 SSH,iPad 和手机随时能连回 Mac 用 Claude Code。也记录了搭建 Tailscale 过程中踩的坑:GUI 版锁屏断连、国内 DNS 污染、SSH 读不到 macOS 钥匙串,以及自己的排查思路。" |
| 14 | +--- |
| 15 | + |
| 16 | +## 写在前面 |
| 17 | + |
| 18 | +我的第二大脑是 Obsidian + Claude Code 搭的 [Claudesidian](https://github.com/heyitsnoah/claudesidian) 方案。它在 Obsidian 里预设了一套 PARA 结构的知识库,然后通过 Slash Command 和 MCP 让 Claude Code 来驱动整个工作流:日常写笔记、整理收藏、做 daily review,甚至研究一个新话题,都是在跟 Claude Code 对话中完成的。它不只是个工具,更像一个长期协作伙伴。 |
| 19 | + |
| 20 | +问题是,我不可能随时坐在电脑前。春节假期躺沙发上突然冒出个想法,陪娃逛街时脑子里闪过一个 bug 的解法。偏偏这种碎片化的瞬间,最想随手跟 AI 聊两句。 |
| 21 | + |
| 22 | +折腾了一番,我使用 Tailscale 组网 + Terminus 做移动终端,现在在 iPad 上打开 Terminus 就能直接跟 Mac 上的 Claude Code 对话。 |
| 23 | + |
| 24 | + |
| 25 | + |
| 26 | +## 为什么选 Tailscale |
| 27 | + |
| 28 | +想要从外面连回家里的 Mac,核心问题是组网。常见方案有几种: |
| 29 | + |
| 30 | +- **frp / ngrok**:需要一台有公网 IP 的服务器做中转,不是每个人都有 |
| 31 | +- **Cloudflare Tunnel**:免费,不需要公网 IP,但需要一个托管在 Cloudflare 的域名。SSH 场景下客户端也得装 `cloudflared` 做代理,配置链路比较长(建 Tunnel、配公共主机名、改本地 SSH config、加 Access 策略),更适合暴露 Web 服务 |
| 32 | +- **[Happy Coder](https://happy.engineering/)**:专门为 Claude Code 设计的移动端方案,扫码配对就能用。它通过中继服务器转发加密消息,你的代码和对话会经过第三方服务。如果你只需要在手机上操控 Claude Code,这是最轻量的选择 |
| 33 | +- **Tailscale**:注册一个账号(支持 GitHub 登录),各设备装上客户端,自动组成虚拟局域网,设备之间点对点直连,不经过第三方 |
| 34 | + |
| 35 | +我选 Tailscale,原因很简单:零配置门槛,装上就能用。我平时有在家折腾 homelab,用闲置硬件搭了一套自己的开发和存储环境,这些设备本来就跑在 Tailscale 网络里,iPad 和手机加进来只是顺手的事。 |
| 36 | + |
| 37 | +没了解过 Tailscale 的同学,这里简单说一下它的原理:它基于 WireGuard 协议,在你的设备之间建立点对点的加密隧道。大部分情况下设备之间直连,不经过中转服务器,延迟很低。每台设备会分配一个固定的 `100.x.x.x` 内网 IP,你用这个 IP 就能像在同一个局域网一样互相访问。 |
| 38 | + |
| 39 | + |
| 40 | + |
| 41 | +## 前置准备 |
| 42 | + |
| 43 | +### Mac 保持在线 |
| 44 | + |
| 45 | +整个方案的前提是 Mac 不能休眠断网。接上电源,然后打开「系统设置」→「显示器」→「高级」,开启"当显示器关闭时,防止自动休眠";再到「电池」→「选项」,把"唤醒以供网络访问"设为"始终"。 |
| 46 | + |
| 47 | +### Mac 开启远程登录 |
| 48 | + |
| 49 | +「系统设置」→「通用」→「共享」→ 打开"远程登录"。这一步开启的就是 SSH 服务。 |
| 50 | + |
| 51 | +### 移动端安装 Terminus |
| 52 | + |
| 53 | +[Terminus](https://termius.com/) 是一个跨平台的 SSH 客户端,支持 iOS、iPadOS 和 Android,免费版功能足够用了。 |
| 54 | + |
| 55 | +> [!TIP] |
| 56 | +> iPad 强烈建议配一个键盘外设,体验会好很多。 |
| 57 | +
|
| 58 | +## Mac 端 Tailscale 配置 |
| 59 | + |
| 60 | +### 安装 Tailscale |
| 61 | + |
| 62 | +Tailscale 在 Mac 上有两种安装方式:App Store 的图形界面版本,和 Homebrew 的命令行守护进程版本。 |
| 63 | + |
| 64 | +我一开始装的是 GUI 版,用着没问题,但合上盖子或者锁屏关闭显示器之后,iPad 就连不上了。换成 Homebrew 版就没这个问题,它作为系统守护进程运行,锁屏也不会断。 |
| 65 | + |
| 66 | +```bash |
| 67 | +brew install tailscale |
| 68 | +sudo brew services start tailscale |
| 69 | +sudo tailscale up |
| 70 | +``` |
| 71 | + |
| 72 | +运行 `tailscale up` 后终端会打印一个认证链接,复制到浏览器打开完成登录就行。 |
| 73 | + |
| 74 | +如果一切顺利,到这里就结束了。但我遇到了两个坑。 |
| 75 | + |
| 76 | +### 踩坑 1:GUI 版残留导致命令失效 |
| 77 | + |
| 78 | +如果你之前装过 GUI 版,`/usr/local/bin/tailscale` 可能还指向旧的应用路径,执行时会报 `No such file or directory`。 |
| 79 | + |
| 80 | +```bash |
| 81 | +# 删掉失效的旧链接 |
| 82 | +sudo rm /usr/local/bin/tailscale |
| 83 | + |
| 84 | +# 让 Homebrew 接管 |
| 85 | +brew link --overwrite tailscale |
| 86 | +``` |
| 87 | + |
| 88 | +确认一下新路径: |
| 89 | + |
| 90 | +```bash |
| 91 | +which tailscale |
| 92 | +# Apple Silicon: /opt/homebrew/bin/tailscale |
| 93 | +# Intel Mac: /usr/local/bin/tailscale |
| 94 | +``` |
| 95 | + |
| 96 | +如果看到 `/opt/homebrew/bin/tailscale` 或 `/usr/local/bin/tailscale`,说明链接修复成功。 |
| 97 | + |
| 98 | +### 踩坑 2:DNS 污染导致连不上服务器 |
| 99 | + |
| 100 | +> [!NOTE] |
| 101 | +> 这个问题卡了我最久。国内使用代理工具的用户大概率会遇到,网络环境没问题的可以跳过这段。 |
| 102 | +
|
| 103 | +`tailscale up` 之后一直卡着没反应,手动启动守护进程看日志,发现: |
| 104 | + |
| 105 | +``` |
| 106 | +logtail: dial "log.tailscale.com:443" failed: dial tcp 11.0.0.1:443: i/o timeout |
| 107 | +``` |
| 108 | + |
| 109 | +`log.tailscale.com` 被解析到了 `11.0.0.1`,这是典型的 DNS 污染。解法是让 Tailscale 走代理启动。 |
| 110 | + |
| 111 | +先手动验证代理能不能通: |
| 112 | + |
| 113 | +```bash |
| 114 | +# 杀掉残留进程 |
| 115 | +sudo pkill tailscaled |
| 116 | + |
| 117 | +# 带代理启动守护进程(替换成你自己的代理端口) |
| 118 | +sudo env http_proxy=http://127.0.0.1:10080 \ |
| 119 | + https_proxy=http://127.0.0.1:10080 \ |
| 120 | + all_proxy=socks5://127.0.0.1:10081 \ |
| 121 | + /opt/homebrew/bin/tailscaled |
| 122 | +``` |
| 123 | + |
| 124 | +如果日志正常滚动没报错,开一个新终端窗口执行 `tailscale up` 完成登录。 |
| 125 | + |
| 126 | +验证通过后,把代理配置写进 Homebrew 的服务配置文件里,这样重启也能自动生效: |
| 127 | + |
| 128 | +```bash |
| 129 | +sudo nano /opt/homebrew/Cellar/tailscale/$(tailscale version | head -1)/homebrew.mxcl.tailscale.plist |
| 130 | +``` |
| 131 | + |
| 132 | +在 `<dict>` 内添加: |
| 133 | + |
| 134 | +```xml |
| 135 | +<key>EnvironmentVariables</key> |
| 136 | +<dict> |
| 137 | + <key>http_proxy</key> |
| 138 | + <string>http://127.0.0.1:10080</string> |
| 139 | + <key>https_proxy</key> |
| 140 | + <string>http://127.0.0.1:10080</string> |
| 141 | + <key>all_proxy</key> |
| 142 | + <string>socks5://127.0.0.1:10081</string> |
| 143 | +</dict> |
| 144 | +``` |
| 145 | + |
| 146 | +保存后重启服务: |
| 147 | + |
| 148 | +```bash |
| 149 | +sudo brew services restart tailscale |
| 150 | +``` |
| 151 | + |
| 152 | +运行 `tailscale status`,如果能看到你的设备列表,说明配置生效了。 |
| 153 | + |
| 154 | +### 补充:macOS Screen Time 可能拦截网络 |
| 155 | + |
| 156 | +> [!NOTE] |
| 157 | +> 这个坑我没遇到,但排查过程中发现有人踩过。macOS 的「屏幕使用时间」里「内容与隐私」默认是关闭的,大部分人不会遇到。但如果你开启过网页内容限制(或者家里小孩用过你的电脑),它会在系统底层拦截网络流量,导致 Tailscale 怎么配都连不上。 |
| 158 | +> |
| 159 | +> 如果你排查了代理和 DNS 都没问题,检查一下「系统设置」→「屏幕使用时间」→「内容与隐私」,把「访问网页内容」设为"无限制",或者直接关掉「内容与隐私」的总开关。改完之后 `sudo brew services restart tailscale`,再用 `tailscale status` 确认一下。 |
| 160 | +
|
| 161 | +## 移动端连接 Claude Code |
| 162 | + |
| 163 | +Tailscale 配好之后,iPad 和手机上装好 Tailscale 客户端并登录同一个账号,就能拿到 Mac 的 Tailscale IP(`100.x.x.x`)。 |
| 164 | + |
| 165 | +在 Terminus 里新建一个 Host,地址填 Mac 的 Tailscale IP,用户名填 Mac 的登录用户名,连接方式选 SSH。连上之后就是一个标准的终端环境,直接敲 `claude` 就能启动 Claude Code。 |
| 166 | + |
| 167 | +但第一次连大概率会遇到这个提示: |
| 168 | + |
| 169 | +``` |
| 170 | +run in another terminal: security unlock-keychain |
| 171 | +``` |
| 172 | + |
| 173 | +这是因为 Claude Code 把登录凭证存在 macOS 的钥匙串(Keychain)里。你坐在 Mac 前面用的时候,系统会自动解锁钥匙串;但通过 SSH 远程连接时,macOS 出于安全考虑不会自动解锁,Claude Code 读不到凭证就会报错。 |
| 174 | + |
| 175 | +手动解锁: |
| 176 | + |
| 177 | +```bash |
| 178 | +security unlock-keychain ~/Library/Keychains/login.keychain-db |
| 179 | +``` |
| 180 | + |
| 181 | +输入 Mac 的登录密码就行。 |
| 182 | + |
| 183 | +每次 SSH 连接都手动敲这行命令有点烦,在 `.zshrc` 里加一段自动判断: |
| 184 | + |
| 185 | +```bash |
| 186 | +if [ -n "$SSH_CONNECTION" ]; then |
| 187 | + echo "🔓 unlocking Keychain for Claude Code..." |
| 188 | + security unlock-keychain ~/Library/Keychains/login.keychain-db |
| 189 | +fi |
| 190 | +``` |
| 191 | + |
| 192 | +这样每次通过 SSH 登录时会自动触发解锁,只需要输一次密码。看似每次都要输密码,但实际用下来完全可以接受。 |
| 193 | + |
| 194 | +到这里,整个链路就通了。Mac 锁屏挂着,iPad 或手机随时打开 Terminus 就能接入 Claude Code。 |
| 195 | + |
| 196 | +## 日常使用效果 |
| 197 | + |
| 198 | +用手机备忘录也能记想法,但事后还得手动整理到 Obsidian 里,自己补上下文、打标签、关联已有笔记。Obsidian 虽然有移动端,但我的工作流是 Claude Code 驱动的,笔记的创建、归档、关联都是 Claude 在做,直接用 Obsidian App 反而对不上。 |
| 199 | + |
| 200 | +用 Terminus 连上 Claude Code 之后,移动端也能用完整的工作流。记录灵感、改点代码,都能直接上手。 |
| 201 | + |
| 202 | +**捕捉灵感**:假期窝在沙发上突然想到一个点子,拿起 iPad 跟 Claude 说两句,它知道我的笔记结构,会自动归档到对应目录、补上 frontmatter。陪娃在外面逛也一样,掏出手机打开 Terminus 快速记一笔。手机上打长命令确实不太舒服,但记个灵感够用了。 |
| 203 | + |
| 204 | +**出门也能改代码**:刚发完一篇博客发现导出格式有个小问题,手边没电脑,iPad 上跟 Claude Code 描述一下,它自己找到导出脚本改好。不用看代码,不用等回家。 |
| 205 | + |
| 206 | +**灵感趁热落地**:跟朋友聊天聊到一个好用的工作流,当场让 Claude 起一个新 Skill 的骨架,回家再细调。想法热乎的时候直接落地,比事后回忆靠谱得多。 |
| 207 | + |
| 208 | + |
| 209 | + |
| 210 | +## 最后 |
| 211 | + |
| 212 | +这套方案用起来比预想的简单,搭完之后基本没折腾过。Tailscale 组网的能力也不止于此,有需要的时候可以把更多设备加进来。 |
| 213 | + |
| 214 | +搭建过程踩的坑,基本都是把报错丢给 AI 一轮轮排查解决的。给足上下文,试错成本很低。 |
| 215 | + |
| 216 | +希望这篇文章对你有帮助,有更好的方案欢迎留言讨论。 |
0 commit comments