Skip to main content
Zhimalab
中文
This article is not yet available in English. View the Chinese version

用 AI 把服务器搬到新机器:一次 HTTPS、Go 服务、数据的逐字节复刻实录

2026-09-04 · 13 min

新买了一台服务器(Ubuntu 24.04,新域名指向它),要把旧服务器上的一套服务原样“搬”过去。我没有一行行手动敲,而是让 AI(Claude Code)在旧机上勘查、在新机上复刻、最后逐字节对照验证。三样服务全部搬完只卡了一次壳——而那一次还不是 AI 能解决的。这篇把我踩过的坑和验证方法完整记下来。

迁移的本质:勘查 → 复刻 → 对照验证

所谓“用 AI 迁移服务器”,不是让 AI 凭空生成配置,而是让 AI 成为那台“看得见旧机、也够得着新机”的操作员。整件事可以拆成三步,每一步都有一句要领:

  1. 勘查:先读现状,再动手——别凭记忆、别猜。
  2. 复刻:逐字节搬运,连“你以为用不到的文件”一起搬。
  3. 验证拿新机跟旧机比,而不是拿新机跟“你期望的样子”比。

这次的旧机叫 mm(托管 zhimalab.tech),新机叫 oo(托管 zhimayuandi.com),两台都是 Ubuntu 24.04 x86_64,SSH 别名都配好了。要搬的三样东西:站点的 HTTPS 证书与 nginx 结构、一个 8080 端口的 Go 博客服务、以及后端的反馈(Bug)数据。

第一样:HTTPS / nginx —— “本地通”不等于“公网通”

旧机早已用 certbot 配好 HTTPS。新机当时是纯 HTTP:一个 server { listen 80 default_server; } 把所有请求都接了。目标是把证书和“旧机同款”的 nginx 结构搬过去。

勘查,拿到了三张关键“底牌”

AI 干活的第一步是把我肉眼看不到的事实摆上桌:

systemctl cat blog-cli.service        # 某个服务的单元文件长什么样
nginx -v && ls /etc/nginx/sites-enabled/
certbot --version
nslookup zhimayuandi.com              # 域名到底指没指向新机

旧机的 HTTPS 结构是这样的三段式,直接照抄:

  • www.zhimalab.tech 的 443 块 → 301 跳到 apex 主域;
  • 主域内容块监听 443(证书 + include options-ssl-nginx.conf + dhparam);
  • 一个 80 端口的块 → 命中域名就 301 到 HTTPS,其余一律 404

于是新机如法炮制:先用 certbot 的 nginx 插件签证书(只签不改配置):

certbot certonly --nginx -d zhimayuandi.com -d www.zhimayuandi.com
# 证书落在 /etc/letsencrypt/live/zhimayuandi.com/
# 自动续期定时任务也已建好

然后把 nginx 站点配置改写成与旧机一致的三段式,nginx -t 通过后 reload。到这里一切顺利。

卡点:443 从公网死活连不上,本地却全通

证书、配置、跳转都弄好了,本地(在新机内)用 curl 访问 HTTPS:

curl -sk -H "Host: zhimayuandi.com" https://127.0.0.1/   # → 200,一切正常

可一旦从公网访问 https://zhimayuandi.com,就超时;80 端口却正常跳转。这说明:

服务器内部能通 ≠ 公网能通。 中间隔着一道“云边界”——腾讯云轻量服务器的防火墙(在控制台,不在服务器里)。

ss -tlnp 明明显示 nginx 在 0.0.0.0:443 监听,ufw 也关着,主机上唯一的 iptables 规则(YJ-FIREWALL)只拒了两个特定 IP。也就是说问题不在“主机内”,而在“云边缘”。这道防火墙 SSH 里改不了,只能人肉去腾讯云控制台给 443 加一条放行

这是全程唯一一次必须真人介入的环节。排障结论很干脆:先分清“云边界”和“主机内”两层——回环自测过了、监听也在、主机防火墙没拦,那八成就是云安全组/防火墙没放行端口。放行后 HTTPS 立刻通了。

第二样:Go 博客服务 —— “单文件”其实是个“三件套”

用户说 8080 上跑着“应该是 go 写的单文件”。确实,ss -tlnp 看到的是一个 16MB 的 Go 二进制 blog-cli(品牌名 GeekBlog)。但只搬二进制一定白忙——勘查一下它的单元文件就明白了:

systemctl cat blog-cli.service
# ExecStart=/root/.local/bin/blog-cli --db ./blog.db serve
# WorkingDirectory=/root/.local/bin

WorkingDirectory=/root/.local/bin 决定了 --db ./blog.db 这个相对路径实际落在哪,也意味着服务真正依赖的东西是:

/root/.local/bin/blog-cli     # 16MB Go 可执行文件
/root/.local/bin/web/         # 前端:index.html / post.html / static/
/root/.local/bin/blog.db      # SQLite,存了 50 篇帖子

所谓“单文件服务”,运行期其实要吃二进制 + 静态资源 + 数据库三件套。漏一个,服务能起但页面不对;漏数据库,等于搬了个空壳。

复刻做法是跨机器流式打包,不落中间文件:

ssh mm "cd /root/.local/bin && tar -czf - blog-cli web blog.db" \
  | ssh oo "mkdir -p /root/.local/bin && tar -xzf - -C /root/.local/bin"

再把同一个 systemd 单元写过去、enable --now,服务就在新机跑起来了。

第三样:数据 —— 按文件实时读的服务,复制文件即完成

反馈(Bug)服务是新机上“特意空着”的那块。数据其实简单:Bug 服务把每条反馈存成一个 markdown 文件,放在 /var/lib/bug/bugs/*.md。旧机有 11 条,新机目录是空的——这正是用户说的“旧站有内容、新站没内容”。

照搬同款做法,把 11 个 .md 用 tar 管道复制过去。因为服务按文件实时读、没有中心索引,复制完不用重启,列表页立刻多出 11 条。

真正的收获:四个“假 Bug”与一套验证法

这次迁移最值钱的部分不是命令,而是判断“到底算不算 Bug”的方法。一路碰到四个看似异常、实则全是假象的坎:

假 Bug 1:HEAD 请求返回 404

新机的博客首页用 curl -I(HEAD 请求)探测返回 404,当时差点当成问题去查。拿旧机一比:旧机也 404。这是 blog-cli 对 / 只响应 GET、HEAD 走默认 404 的固有行为。

要领:是不是 Bug,以源机的行为为准,别以你的预期为准。 一个行为只要源机同样存在,就不是这次迁移引入的回归。

假 Bug 2:列表页“路由假象”

Bug 服务的 8601 端口直连 /bugs/ 返回 404,但对外网址 https://…/bugs/ 明明有内容。顺着 nginx 一看就明白:nginx 把 /bugs/ 前缀剥掉后反代到了 8601 的根路径 /。真正的列表页在 8601 是 //bugs/ 只是对外地址。

要领:“对外的路径”和“后端的路由”不是一回事。 排查时别盯着对外 URL,要看代理链剥前缀之后到底命中了谁。

假 Bug 3:新旧数据 md5 对不上

验证 /api/posts 时,新机旧机下载回来的响应算 md5,竟然不一致。吓得我逐字节 diff……结果用 Python 按 UTF-8 解析后,逐帖 JSON 完全一致。差异来自中间那层:PowerShell 把 curl 输出按当前代码页当文本解码,编码搅乱了字节。

要领:校验一致性要用原始字节。curl -o file 落盘再 diff/md5sum,别让终端、PowerShell、编码夹在数据中间。

假 Bug 4:把“内容空”当成“服务坏”

新机 /bugs/ 一开始页面上没有任何条目,第一反应是反代坏了。其实是数据目录里 0 个文件——服务是好的,只是没数据。区分“页面 200 但空列表”和“页面 404/500”,排障方向完全不同。

复盘:一份可复用的“AI 迁移服务”清单

  1. 勘查先行,事实优先systemctl cat <svc>ss -tlnpmd5sum <bin>ls 数据目录、nslookup 域名。让 AI 先把客观事实摆出来,再谈怎么做。
  2. 复刻 = 单元 + 可执行 + 运行期依赖:盯紧 WorkingDirectory 与相对路径,把静态资源、DB、配置文件一并搬,别只搬二进制。
  3. 传输走 tar + ssh 管道:不落中间文件、保留权限,tar -czf - 打过去 tar -xzf - 接住;搬完用 md5sum / ls -l 对大小和哈希。
  4. 验证三层,每层都跟源机比:先记录源机行为 → 新机回环自测 → 公网外部测;内容类直接 curl -o 落盘做字节级 diff,别用终端文本比。
  5. “本地通 ≠ 公网通”:回环 200、监听在、主机防火墙没拦,那就是云边界(安全组/防火墙),去控制台放行,SSH 改不了。
  6. 疑似 Bug 先对照源机:源机同样表现 = 固有行为,别白查;迁移是否成功,标准是“和源机一致”,不是“符合预期”。
  7. 边做边沉淀:旧配置先备份到 /root/nginx-backups/,把部署拓扑写成 memory/文档,下次续期、排障才有据可查。

一点尾声

整场迁移,AI 包办了勘查、改写配置、签发证书、跨机搬运、逐字节校验;唯一需要真人的,是去云控制台给 443 点一下放行。这大概是当下“AI 运维”最舒服的切分方式:AI 干服务器内所有能通过 SSH 触达的活,人只做云控制台那道 AI 够不着的门。等哪天控制台也能被 AI 操作,迁移服务器就真的可以全程无人值守了。