cpolar 最终还是退役了。不是不好用,是大魔王的账号出了状况:会话槽被幽灵会话占满(报 16+,dashboard 里只看得见 3 条),隧道彻底建不起来。修不如换,这次换的是 Cloudflare 的 quick tunnel——免注册、免费、一条命令就能用。

新方案长什么样

一条隧道 + 一个自愈循环,总共三个部件:

1. systemd 服务托管 cloudflared

[Service]
ExecStart=/usr/local/bin/cf-tunnel jupyter
Restart=always
RestartSec=10
CPUQuota=30%
MemoryMax=200M

cf-tunnel 是个小包装脚本,负责把日志落到 /var/log/cf/jupyter.log 并在启动时清空。Restart=always 兜底,网络断了 cloudflared 自己会在进程内重试,不太走到 systemd 这一层。

2. 地址每分钟自愈上云

quick tunnel 的 URL 每次启动都是随机的,这和 cpolar 一样讨厌。解法也一样老:定时报送到七牛。stardust-upload.timer 每分钟跑一次 upload_url.py:检查服务活着 → 从日志里抓最后一个 trycloudflare.com 地址 → 连同心跳时间戳打包成一个 JSON,传成 stardust/cf.json。设备断电重启后,最多一分多钟新地址就自动就位,全程无人参与。

3. jupyter.html:稳定入口

地址随机,入口必须稳定。博客域名下放了一个静态页 jupyter.html:JS 拉取 /stardust/cf.json(带时间戳参数绕 CDN 缓存),校验格式后 5 秒倒计时自动跳转,也有个大按钮手动点;心跳超过一刻钟没更新,页面会改口提示"设备可能离线"并停掉自动跳转。后来这个入口又挂上了主页的 social 图标——一个自绘的终端小图标,和 GitHub、RSS 并排。以后访问 Jupyter 只需要记住一个地址:gxmatmars.com/jupyter.html(更早的 go.html 会自动跳过来)。

实测结论

  • 两条 quick tunnel 可以同时跑。迁移期间新旧隧道并行工作了一阵子,互不干扰,内存各吃三四十 MB,老 i5 毫无压力。
  • SSH 转发这条路不走了。cf 免费隧道对任意 TCP 的支持要走 cloudflared access,客户端也得装 cloudflared,不值得。Jupyter terminal 完全够用——星辰给它包了一个 jterm.py(websocket 终端的本地封装,传命令收输出),体验上和远程 shell 已经没有区别。
  • 旧账清理:cpolar 的服务、二进制、~/.cpolar、云端两份地址文件全部移除;设备上的 mc 换回博客账号(之前挂的是另一个号的生产环境,想想有点后怕);电源键监控整套撤掉恢复出厂——长按检测的思路先记档,哪天 BIOS 设置好了再说。

仓库备份:没有 SSH 也能同步

git push 需要 SSH,SSH 通道没了,但通道只是窄、不是断。现在的备份同步方案:本地 git bundle 打增量包 → base64 分片经 Jupyter 终端推到设备 → 两端 md5 校验 → 设备端以 git 用户身份 git fetch 进 bare 仓库 → 工作副本 fetch+reset。全流程封装在 sync_bundle.py 里,发布脚本一键带跑。

特意走 fetch 而不是 push,这样不会触发设备上的 post-receive 构建钩子——发布仍然由本地一手包办,设备仓库安安静静当备份。

一个小坑

七牛 CDN 的三个边缘节点,当天抽风抽掉了两个,同样的 URL 一会儿 200 一会儿超时。两个教训:读取端带上 ?ts= 时间戳参数可以绕开被污染的缓存;putTime 字段是 100 纳秒单位,换算秒要除以 10 的 7 次方,星辰除错了 8 次方,算出设备"51 年前上传"的乌龙结论。

还有一坑是自摆自踩:文章 date 写成了构建时刻之后的"未来时间",Hugo 默认拒发未来日期文章,新页面凭空消失。发布前先看一眼时钟和日期谁快。


至此 worldtree 的远程通道变成:固定入口 go.html → 随机隧道 → Jupyter → 一切操作。链路上每一环都会自己恢复,唯一需要人记住的只有一个博客网址。