Featured image of post 我的移动办公 vibe coding

我的移动办公 vibe coding

手机接管电脑上的 Claude Code、Codex,锁屏、断网、切 4G 都不掉,回来还是同一条。tmux 管会话,mosh 管连接,Tailscale 省掉公网 IP,Ubuntu、CentOS、macOS、Windows 都能装。

我的移动办公 vibe coding

现在大部分编码任务我都是丢给 Claude Code 或者 Codex 去做。一句话把需求说清楚,它自己读文件、改代码、跑测试、修报错,中间会停下来问几次,剩下的时间我基本只是等结果。人干的活变成了按几个确认键,加最后看一眼它到底改成了什么样。

这件事改变的不只是效率。以前要干活必须坐在那台机器前面,现在只要能看到那个终端、能按那个 y/n 就行。位置没那么重要了,重要的是那个会话还活着。

手机经 Tailscale 连回电脑,接管 tmux 会话的链路示意

手机上都在干什么

出门前把任务挂上,通常是一句「把 user 模块的参数校验抽出来,测试跑绿了再告诉我」。然后关掉终端,人走开。地铁里掏出手机看一眼,它正停在一个 diff 确认上,点一下回车,继续往下跑。到站的时候事情已经做完了。

晚上躺床上是另一种。睡前让它跑一遍全量测试,或者把一批 lint 报错清掉,手机放枕头边上,困了就不看了。第二天早上翻一下它昨晚做了什么,哪一步没跑通,补一句话让它重来。

出差只带一台平板也是这套。机器留在家里开着,连回去,里面该跑的还在跑,接着刚才那条命令往下说就行。

这三个场景对带宽的要求都不高,但有一条是共同的,人不在机器前面,而那个进程得一直活着。普通 SSH 恰恰做不到这一点,手机锁屏、切一次网、进一次隧道,连接断了,终端里的进程跟着收到 SIGHUP,之前跑的那半小时一起没。

移动办公方案

这套方案不用自己写代码,也不用买什么设备,装几个现成的软件就能跑起来。电脑上四样,tmux、mosh、openssh-server、tailscale。手机上装 Tailscale 的 App,再装一个能连 SSH 的终端 App。

openssh-server 让这台电脑可以被远程登录。SSH 是最老资格的远程终端协议,你能在一个终端里操作另一台机器,靠的都是它,mosh 的第一次认证也得走它,所以这个必须装着。

tmux 是个终端复用器,说白了就是让程序跑在后台,你把窗口关了它也不死。它把 shell 塞进一个服务端进程,你连上去的只是一个客户端,客户端掉了服务端照跑。手机上看到的画面和电脑上那条终端是同一条,不是另开一个。它只管进程存活,网络通不通跟它没关系。

mosh 是 mobile shell 的缩写,定位是 SSH 的移动网络版本。起手还是 SSH,认证完切到 UDP,之后自己维护状态和屏幕内容。断网再连上不是重新协商一整个会话,而是把中间缺的那几帧补回来。手机在 4G 和 WiFi 之间来回切,普通 SSH 基本每次都要重新握手,mosh 通常只是卡一下。代价是服务端要占住 UDP 60000 到 61000,这个后面防火墙得放行,很多人栽在这里。

Tailscale 是一个不需要公网 IP 的虚拟内网,底层跑的是 WireGuard。登录同一个账号的设备之间直接能通,每台机器拿一个 100.64/10 段的地址,手机打开 App 就等于接进了同一个局域网。SSH 只监听在这张虚拟网卡上,公网扫不到 22 端口。以前那套 frp 加端口映射的方案可以收起来了。

用户名、账号和休眠

  • 电脑的登录用户名,以及密码或者一把 SSH 公钥
  • 同一个 Tailscale 账号,手机和电脑必须是一个,个人版免费额度够用到没感觉
  • 关掉自动休眠。这一条比装什么都重要,电脑一睡,前面的配置全白费

Ubuntu 和 Debian

桌面版 Ubuntu 默认不带 SSH 服务,openssh-server 要自己装。

1
2
3
sudo apt update
sudo apt install -y tmux mosh openssh-server
sudo systemctl enable --now ssh

Tailscale 用官方脚本装最省事,它会自己认发行版。

1
2
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up

tailscale up 会打出一个登录链接,复制到浏览器里登录,这台机器就进网了。然后看它的内网地址。

1
tailscale ip -4

出来的 100.x.x.x 记下来,手机连的就是它。开了 ufw 的话把这张虚拟网卡放行,不用去碰公网规则。

1
sudo ufw allow in on tailscale0

CentOS 和 Rocky Linux

CentOS 7 和 8 都已经 EOL,仓库还挂着但不再更新,新机器建议直接上 Rocky Linux 9 或者 AlmaLinux 9。下面的命令在 CentOS 7、CentOS Stream、Rocky、Alma 上都能跑,区别只在 Tailscale 的仓库路径要按系统大版本换。

mosh 不在标准源里,得先开 EPEL。

1
2
3
sudo dnf install -y epel-release
sudo dnf install -y tmux mosh openssh-server curl
sudo systemctl enable --now sshd

CentOS 7 上把 dnf 换成 yum,其余一样。最小化安装一般没带 curl,所以上面顺手一起装了。

Tailscale 走官方 yum 源,URL 里的路径按大版本选。

1
2
3
4
5
# Rocky / Alma / CentOS Stream 9
sudo dnf config-manager --add-repo https://pkgs.tailscale.com/stable/rhel/9/tailscale.repo
sudo dnf install -y tailscale
sudo systemctl enable --now tailscaled
sudo tailscale up

CentOS 7 用 https://pkgs.tailscale.com/stable/centos/7/tailscale.repo,CentOS 8 用 centos/8,只换路径。装完同样是 tailscale ip -4 拿地址。

真正跟 Ubuntu 不一样的是防火墙。firewalld 默认拦进来的包,mosh 的 UDP 段过不去,最省事的做法是把 tailscale0 整张网卡丢进 trusted 区域,从这张网卡进来的流量不再过滤。

1
2
sudo firewall-cmd --zone=trusted --add-interface=tailscale0 --permanent
sudo firewall-cmd --reload

不想动 trusted 区域,就至少把 UDP 60000 到 61000 单独放行,具体到 --add-port=60000-61000/udp。漏了这一步的表现很好认,客户端会打出 Nothing received from server on UDP port 60000,然后悄悄退回普通 SSH,看着像连上了其实没用上 mosh。SELinux 一般不用动,别顺手把它关了。

macOS

Homebrew 装两个包,openssh 不用装,系统自带。

1
brew install tmux mosh

SSH 服务默认是关的。系统设置 → 通用 → 共享 → 远程登录,打开开关,右边把允许访问的用户限定成你自己那个账号,别留「所有用户」。macOS 12 及以前这一步在系统偏好设置 → 共享 → 远程登录。

Tailscale 在 macOS 上有两个完全不同的装法,别搞混。

图形版走官网 dmg 或者 brew install --cask tailscale,装完是菜单栏图标,登录后点菜单就能看到本机 100.x 地址。它自带的命令行不在 PATH 里,要用绝对路径调用 /Applications/Tailscale.app/Contents/MacOS/Tailscale status。我图省事建了个软链过去。

1
sudo ln -sf /Applications/Tailscale.app/Contents/MacOS/Tailscale /usr/local/bin/tailscale

命令行版是 brew install tailscale,装的是 tailscaled,按官方的说法用 sudo brew services start tailscale 起服务,没有菜单栏图标。不想要菜单栏多一个东西就走这个,代价是偶尔要自己看一眼服务状态。

macOS 上最容易卡住的不是防火墙,是 PATH。Homebrew 在 Apple Silicon 上装到 /opt/homebrew,sshd 给非登录 shell 的 PATH 里没有这个目录,认证能过,mosh-server 却起不来。

手机上点 mosh 连接,终端报 mosh-server: not found,就是这个问题。软链到 /usr/local/bin 就行,那个目录在 sshd 默认 PATH 里。

1
sudo ln -sf /opt/homebrew/bin/mosh-server /usr/local/bin/mosh-server

Intel 的 Mac 上 brew 装在 /usr/local,一般不会碰上这一条。

还有休眠。笔记本合盖就睡,软件层拦不住,caffeinate 只能管住「别自动睡」,管不了合盖那一下。

1
caffeinate -dimsu &

真要长时间挂着任务,要么接一台外接显示器让机器进入合盖不睡的状态,要么就把盖子开着。这个没有更干净的办法。

Windows

Windows 上没有原生的 tmux,msys2 那套移植版常年有怪毛病,所以这条路是把 WSL2 开起来,tmux、mosh 和 Claude Code 全塞进那个 Ubuntu 里,Windows 只负责跑 Tailscale 和当宿主机。

装 WSL2 和 Ubuntu

Windows 11 或者 Windows 10 的 2004 以后版本,一条命令就够,它会自己去打开虚拟化和需要的组件,装完重启。

1
wsl --install -d Ubuntu

第一次启动要设 Linux 用户名和密码,这个用户名后面手机登录要用,别顺手打个 root。

在 WSL 里装那三样

进 Ubuntu 之后,跟前面 Ubuntu 那节一模一样。

1
2
3
sudo apt update
sudo apt install -y tmux mosh openssh-server
sudo systemctl enable --now ssh

systemctl 报 System has not been booted with systemd 的话,是这套 WSL 没开 systemd。在 Windows 用户目录建 C:\Users\你的名字\.wslconfig,写上

1
2
[wsl2]
systemd=true

然后 wsl --shutdown 再进去。新装的环境一般已经默认开了,只有从旧系统迁过来的才会碰到。

Claude Code 和 Codex 也装在这个 Ubuntu 里,别装在 Windows 那边。任务跑在 tmux 里,agent 的工作目录、依赖、Node 版本全在同一个环境,省得两边对不上账。

Tailscale 和 WSL 的网

Windows 版 Tailscale 从官网下 msi,或者

1
winget install tailscale.tailscale

装完登录同一个账号,托盘菜单里能看到本机的 100.x 地址。麻烦的地方在后面,WSL2 默认躲在一层 NAT 后面,Windows 自己能访问它,从外面进来的包到不了。

Windows 11 22H2 以后有个干净的解法,在 .wslconfig 里打开镜像网络,WSL 直接共用 Windows 的网卡,Tailscale 那个 100.x 地址在 WSL 里也能用。

1
2
3
[wsl2]
systemd=true
networkingMode=mirrored

wsl --shutdown 重进,在 WSL 里 ip addr 看有没有 tailscale0。看得到就齐了,手机连 100.x 的 22 端口,mosh 的 UDP 段也跟着通。防火墙这一侧补一条规则,省得包被静默丢弃。

1
New-NetFirewallRule -DisplayName "mosh UDP" -Direction Inbound -Protocol UDP -LocalPort 60000-61000 -Action Allow

Windows 10 没有镜像网络这个选项。常见的土办法是用 netsh interface portproxy 把 22 端口转到 WSL 的内网地址,但 portproxy 只转 TCP,mosh 走的 UDP 转不过去,所以 Windows 10 上只能退一步用普通 SSH 加 tmux,会话照样不断,代价是切网要重连。在 WSL 里单独再装一份 Tailscale 也许能绕过去,这条路我没跑通,先不下结论。

防睡眠

Windows 默认也会睡,笔记本合盖更是直接睡。设置 → 系统 → 电源和电池,把「使设备进入睡眠状态」在插电时改成「从不」;合盖的行为在控制面板 → 电源选项 → 选择关闭盖子的功能里,改成「不采取任何操作」。

手机端

Tailscale app 装好,登录同一个账号,开着就行,直连还是走中继它自己判断。

终端 App 挑一个。Android 上用 Termius 或者 JuiceSSH,Termius 的界面好看一些。iOS 上装 Blink Shell,它对 mosh 的支持比 Termius 完整,移动网络下的表现也稳。

新建连接的时候填四个东西,Host 是刚才那个 100.x.x.x,端口 22,用户名是电脑上的账号,认证先拿密码跑通,通了再换密钥。协议选 mosh,别用默认的 SSH,不然前面装的服务端白装。

Claude Code 那个界面在手机上的主要问题是列宽。默认字号下它那几栏信息会折行折得很难看,换成等宽字体,字号压到 10 到 11,横屏使用,看起来就正常了。Termius 里把键盘常驻打开,不然每次点输入框都要弹一遍。

每天的用法

电脑上开会话,命令里的 -A 表示会话已经存在就直接接上,不存在才新建,比每次先 tmux ls 看一眼再决定 attach 要省事。

1
2
3
tmux new -A -s claude1
# 在里面跑 claude 或 codex
# 脱离:先按 Ctrl+b,松开,再按 d

手机上连上之后,列一下再进去。

1
2
tmux ls
tmux attach -t claude1

手机端脱离一样是 Ctrl+b 再按 d,Blink 和 Termius 都支持把 Ctrl 组合键映射到工具栏上,不用外接键盘也按得出来。

用完不收拾的会话会一直挂着占内存,定期清一下。

1
tmux kill-session -t claude1

容易卡住的地方

tmux attach -s xxx 会报 unknown flag -s。-s 是给 new-session 起名字用的,指定 attach 的目标要用 -t。这条错在中文教程里非常常见,照抄过来在 tmux 3.x 上直接报错。

CentOS 上 mosh 连不通,先看 firewalld,再看 EPEL 装没装。表现是客户端打出 Nothing received from server on UDP port 60000 然后退回 SSH,不报错不提示,很容易误判成连上了。

macOS 上 mosh-server not found,就是上面那条 PATH 的问题,软链一下就好。

Windows 上最迷惑的一种情况是 Windows 自己能连、手机连不上。多半是 WSL 还躲在 NAT 后面,.wslconfig 里没开 networkingMode=mirrored,100.x 那个地址只在 Windows 那一层有效。先在 WSL 里 ip addr 看有没有 tailscale0,没有就是这件事。

过一晚回来看会话没了,九成是电脑睡了或者重启了。tmux 会话不跨重启,这个是设计如此,不是配置错。

手机连不上但两边 tailscale status 都显示在线,去看电脑上的监听地址,ss -lntp | grep :22。只监听 127.0.0.1 的话手机永远连不上,得让它监听 0.0.0.0 或者那个 100.x 地址。

安全

SSH 只走 tailnet,22 端口不做任何公网映射,这一条守住就避掉了绝大部分爆破。

tailnet 内部用密码登录问题不大,但手机丢了等于全家丢了,所以还是建议换成密钥,然后关掉密码认证。Tailscale 后台的 ACL 能限定哪个账号连哪台机器,多人共用一个 tailnet 的时候值得配一下,这块我没有细调过,先记在这儿。

最后说几句

会写代码的人用这套,省掉的是「必须守在电脑前」那段时间。任务丢给它,出门,路上批两次确认,回来收结果。写卡住了下楼转一圈,回来它已经把测试跑完,红的地方还列了原因。

不写代码的人其实更能用上。Claude Code、Codex 这类东西不要求你会语法,把要做的事说清楚就行,改一批文案、把几百个文件按规则改名、从几个表格里拼一份汇总,它自己动手,你只在它问「这样改行不行」的时候回一句。配上这套远程连接,手边那台平板就是工作台,不必是那台装着环境的电脑。少了「你得先学会装环境」这道门槛,移动办公才轮到那些本来不写代码的人。

本博客所有内容无特殊标注均为大卷学长原创内容,复制请保留原文出处。
Built with Hugo
Theme Stack designed by Jimmy