一天时间,把乱七八糟的订阅和服务器收拾干净

精简 AI 订阅、迁移到 1Password、域名统一到 Cloudflare、加固 VPS,再让云端 AI Agent 安全地接管服务器。

我手上的东西越攒越多:好几个 AI 订阅、一台 VPS、两个域名,还有学校里一堆要维护的机器。东西一多就容易忘:哪个订阅什么时候扣钱、密码存在哪、服务器上跑着什么,全靠脑子记。

今天花了一天,把这些从头整理了一遍。这篇记录一下最后的方案。

整理思路:分三层

动手之前先想清楚要管什么,我把它分成三层:

  1. 资产清单:我到底有什么?每样东西花多少钱、什么时候续费、拿来干嘛
  2. 凭据管理:密码、SSH key、API token 放在哪
  3. 访问与运维:人和 AI Agent 怎么安全地操作服务器

原则只有一条:秘密只放在密码管理器里,配置文件里只写”指向秘密的指针”,清单里只写”秘密在哪”。

第一层:资产清单和订阅精简

我建了一个私有 Git 仓库 infra,作为所有资产的唯一信息源:

infra/
├── inventory.yaml     # 订阅、服务器、域名:费用、续费日、用途
├── servers/hk.md      # 服务器档案:系统、用户、SSH 配置、变更记录
├── domains/README.md  # 域名:注册商、DNS、到期日
├── cloudflare.md      # Zone、Tunnel、Access 应用、Token
├── agents.md          # 各个 AI 工具的分工和权限边界
├── TODO.md
└── scripts/           # 配置脚本(不含任何密钥)

inventory.yaml 里每一项大概长这样:

- name: Claude Pro
  cost: 20
  cycle: monthly
  status: active
  role: 本地 agent(代码、运维、文档)
  credentials: 1Password → Personal → Claude

列出来之后才发现,好几个 AI 订阅的功能是重叠的。最后定下的分工是:

工具角色费用
Claude本地 agent:写代码、运维、整理文档$20/月
Memoh云 agent:管理服务器和 Cloudflare$5/月
Meta Muse生活助手免费

其他订阅到期不再续费,每月固定支出从大约 78降到了78 降到了 28。

另外给 AI 工具划了边界:生活助手不接触任何服务器信息;云 agent 只拿单独的、可以随时撤销的凭据;学校内网完全手动管理,不接入任何云 agent。

第二层:迁移到 1Password

之前密码都存在 Google 密码管理器里。迁移过程很简单:从 Google 导出 CSV,在 1Password 网页版里导入,然后立刻删掉那个 CSV,因为里面全是明文。

然后按用途建了三个保管库(Vault):

  • Personal:日常网站账号
  • Infra:服务器 SSH key、root 密码、Cloudflare token
  • School:学校的东西,和个人的完全隔离

SSH Agent:私钥不落盘

1Password 最让我惊喜的是 SSH Agent。SSH 私钥直接在 1Password 里生成和保存,硬盘上没有私钥文件。每次 ssh 时 1Password 弹出 Touch ID,按一下就完成签名。

# 在 1Password 里生成 key,只把公钥导出到本地
op item create --category=ssh --title="hk" --vault=Infra --ssh-generate-key=ed25519
op read "op://Infra/hk/public key" > ~/.ssh/hk.pub

默认情况下 Agent 只提供 Personal 里的 key,需要在 ~/.config/1Password/ssh/agent.toml 里加上其他保管库:

[[ssh-keys]]
vault = "Personal"

[[ssh-keys]]
vault = "Infra"

~/.ssh/config 里有个小技巧:IdentityFile 指向的是公钥。私钥不在硬盘上,写公钥是为了告诉 Agent “只用这一把”,否则 Agent 会把所有 key 挨个试一遍。

Host hk
    HostName <服务器 IP>
    User deploy
    IdentityAgent ~/.1password/agent.sock
    IdentityFile ~/.ssh/hk.pub
    IdentitiesOnly yes

Secret Reference:配置里只写引用

1Password CLI 支持 op://保管库/条目/字段 这种引用写法。脚本和配置文件里只写引用,运行时再读取真实值:

op read "op://Infra/hk deploy/password"

这样脚本就能放心提交到 Git,里面没有任何秘密。后面所有的配置脚本都是这么写的。

第三层:域名统一到 Cloudflare

两个域名注册在阿里云,DNS 全部改成 Cloudflare 的 nameserver。以后的小站打算放到 Cloudflare Pages / Workers 上,统一在一个后台管理。

服务器加固

新买的 VPS 是 Debian 13,做了这几件事:

1. 只允许 key 登录,禁用 root

建一个普通用户 deploy 负责日常运维,加入 sudo 组。加固配置没有直接改 /etc/ssh/sshd_config,而是单独放在 sshd_config.d/ 下:

# /etc/ssh/sshd_config.d/00-hardening.conf
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no

sshd 对同一个配置项取第一次出现的值,而 sshd_config.d/*.conf 在主配置文件的开头就被 Include 了,00- 开头的文件又最先加载,所以它会优先生效。自己的修改集中在一个文件里,一目了然,也不怕主配置被系统或商家的工具覆盖。

2. sudo 需要密码,但不用一直输

deploy 的 sudo 密码由 1Password 随机生成。这样就算 SSH key 被盗用,也拿不到 root 权限。为了不那么烦,延长了免密时间:

# /etc/sudoers.d/10-deploy-timeout
Defaults:deploy timestamp_timeout=60

需要时 Cmd+Shift+Space 唤出 1Password 搜索框,复制密码,和用 Spotlight 启动应用的手感一样。

让云端 AI Agent 安全地接管服务器

这是今天最有意思的部分。Memoh 是一个云托管的 agent,本质上是一台完整的 Linux 虚拟机。问题是:它没有固定的出口 IP,没法用 IP 白名单来限制它。

我对比了 Tailscale 和 Cloudflare Tunnel,最后选了后者:

  • 我的 Mac 不需要常驻任何进程,cloudflared 只在 SSH 时临时启动
  • 可以对 Agent 和我自己分别设置访问策略,单独撤销
  • 和域名、Pages 都在 Cloudflare 一个后台

整体结构是这样的:

Memoh 虚拟机 ──Service Token──→ Cloudflare Access ──→ Tunnel ──→ VPS(memoh 用户)
我的 Mac    ──邮箱登录────────→ Cloudflare Access ──→ Tunnel ──→ VPS(deploy 用户)

服务器端:Tunnel

服务器上运行 cloudflared,主动连接 Cloudflare,把 ssh.example.com 转发到本机的 localhost:22。Tunnel 的 token 放在只有 root 能读的文件里,不出现在命令行和 systemd 配置中:

# /etc/systemd/system/cloudflared.service
[Service]
EnvironmentFile=/etc/cloudflared/token.env
ExecStart=/usr/local/bin/cloudflared --no-autoupdate tunnel run
Restart=on-failure

访问控制:Cloudflare Access

给 ssh.example.com 建一个 Access 应用,配两条策略:

  • Allow:我自己的邮箱,用邮件验证码登录
  • Service Auth:Memoh 专用的 Service Token

Agent 端:两道门

Memoh 要登录服务器,需要同时通过两道门:

  1. Cloudflare Access:出示 Service Token
  2. sshd:出示它自己的 SSH key

SSH key 是在 Memoh 虚拟机里生成的,私钥从来没有离开过那台机器,我只拿到了公钥。服务器上给它建了一个独立的 memoh 用户,不给 sudo,公钥前面加上 restrict:

restrict,pty ssh-ed25519 AAAA... memoh@hk

restrict 会禁用端口转发、Agent 转发、X11 转发,这把 key 只能开一个普通终端执行命令,没法把服务器当成跳板。

Memoh 那边的 ~/.ssh/config:

Host hk
    HostName ssh.example.com
    User memoh
    IdentityFile ~/.ssh/hk_memoh
    StrictHostKeyChecking yes
    ProxyCommand sh -c '. ~/.config/cf-access/hk.env && exec cloudflared access ssh --hostname %h --service-token-id "$CF_ACCESS_CLIENT_ID" --service-token-secret "$CF_ACCESS_CLIENT_SECRET"'

服务器的主机公钥也提前写进了 Memoh 的 known_hosts,第一次连接就能验证对方的身份。

从服务器日志能看到,Memoh 的连接来源是 ::1,也就是确实经过 Tunnel 进来的,没有走公网:

Accepted publickey for memoh from ::1 port 40948 ssh2: ED25519 SHA256:...

想收回权限时,在 Cloudflare 后台删掉 Service Token,或者删掉服务器上那一行公钥,任意一个都行,几秒钟的事,不影响其他任何东西。以后它需要重启某个服务了,再通过 sudoers 白名单只开放具体的几条命令。

总结

一天下来,最大的收获不是某个工具,而是几条原则:

  • 一份清单:所有东西记在一个地方,人和 agent 都读它
  • 秘密集中:只在 1Password 里,其他地方只写引用
  • 最小权限:每个使用方一把独立的凭据,只给需要的权限
  • 随时可撤销:出了问题能在几秒钟内收回,而不影响其他东西

接下来还想做的:等 Tunnel 稳定用一段时间,把公网的 22 端口彻底关掉;给 VPS 配上快照和备份;把博客迁到 Cloudflare Pages 上。