AI小说工厂(九):部署之路——从Mac到NAS的夜间挂机

  • ~3.63K 字

系列最后一篇讲部署:一个个人工具怎么从”Mac 上跑个进程”进化到”Docker 多架构镜像 + NAS 常驻 + Tailscale 远程访问 + 按配额窗口智能挂机”。这部分的坑全部来自真实世界:交叉编译、国内网络、免费额度的排队、以及 Mac 合盖就断的物理限制。

为什么要部署:Mac 写工具的物理天花板

工具在 Mac 上跑得好好的,为什么非要折腾部署?三个真实痛点:

  1. Mac 合盖就断——写书是 8 小时起步的批量任务,总不能为这个不合盖
  2. Mac 是开发机——调试时重启服务、改配置、切 provider 是常态,跑着的长任务随时被误伤(真实发生过:测试切 mock 时忘了服务端缓存,用户真实项目的任务被牵连)
  3. 家里有台不吃电的常驻设备——极空间 NAS,Docker 环境现成,24 小时在线

开发环境和生产环境分离,个人工具也逃不开这条工程铁律。

Docker 化:从一个 Dockerfile 到多架构发布

容器化的关键决策:

# ARG 全局前置(吃了顺序亏:ARG 必须在 FROM 前才能进构建阶段)
ARG BASE_IMAGE=python:3.10-slim
FROM node:22 AS frontend # 前端构建阶段
...npm ci && npm run build
FROM ${BASE_IMAGE} # 运行阶段
...COPY --from=frontend dist
  • 数据外置AINOVELS_DATA 环境变量派生全部数据目录(projects/config/cache),容器无状态,升级 = pull 新镜像重建容器,数据在卷里纹丝不动
  • 国内网络友好PIP_INDEX_URL 可注入国内源;健康检查用 python urllib(镜像里没有 curl,不想为它 apt)
  • 多架构:Mac 是 arm64、NAS 是 amd64,buildx 交叉构建 + manifest 合并双架构镜像

推送脚本沉淀成一页纸的工程诗(节选):

# 网络策略(自动):
# - 探测到本地代理(Surge 6152 / Clash 7890)→ 推送直连官方源
# - 无代理 → 基础镜像走国内镜像源
# pipefail:本脚本常被 `./docker-push.sh | tee | grep` 调用,
# 没有它管道退出码取的是最后一段的,推送失败也会显示成功
set -e -o pipefail

# 代理探测:宿主机端口可达则启用,统一 host.docker.internal 访问
# (不经 VM 网关 IP,colima 重启换网段也不受影响)
if nc -z 127.0.0.1 6152 2>/dev/null; then
PROXY="http://host.docker.internal:6152"
elif nc -z 127.0.0.1 7890 2>/dev/null; then
PROXY="http://host.docker.internal:7890"
fi

# 基础镜像恒走镜像源:buildkit 的注册表元数据解析不走代理 env
# (缓存冷时直连 docker.io 会被 DNS 污染超时,两轮推送失败实锤);
# 代理仅用于加速推送上传
BASE="${MIRROR}/library/python:3.10-slim"

三条实战注释值千金:

  • pipefail:脚本被管道调用时,没有它退出码是 tee/grep 的(永远成功)——推送失败也显示成功
  • 基础镜像恒走镜像源:buildkit 拉基础镜像的元数据解析不走代理环境变量,缓存冷时直连必被 DNS 污染超时——注释里的”两轮推送失败实锤”是用时间换的
  • host.docker.internal 代替 VM 网关 IP:Docker 跑在 Colima 虚拟机里时,代理在宿主机——走 VM 网关 IP 的话虚拟机重启换网段就断,docker internal 域名一劳永逸

Clash 代理还有个大文件上传的 broken pipe 问题(推镜像层时断流),解法是备一个直连构建器:代理路线推不动时脚本自动切 xbuilder-direct——直连慢(静默期 8 分钟无日志,不是卡死)但稳。

NAS 部署:compose 一把梭

NAS 端的交付物是一份 docker-compose.yml——照着用户 NAS 上已有的 Tailscale 容器的 compose 风格写的(家庭服务器上保持配置风格一致是隐形福利)。数据卷指向 NAS 的存储池路径,restart: always 常驻,8348 端口。

升级闭环就此定型:

# Mac 端发版
./scripts/docker-push.sh # 版本标签自动取 APP_VERSION,双架构

# NAS 端升级(数据不动)
docker compose pull && docker compose up -d

远程访问走 Tailscale——NAS 在异地也能通过虚拟内网安全访问,不暴露公网。远程质检只做只读操作:GET 版本号确认容器跑的是新镜像(/api/version 404 = 旧镜像无疑)、GET 体检报告看写作质量,绝不远程触发任何 LLM 消耗

配额协议:免费额度的智能挂机

部署解决”在哪跑”,配额协议解决”怎么跑得久”。LLM 走的是 Coding Plan 套餐额度——5 小时一个窗口,窗口重置才能继续。夜间挂机的原始做法是定时重试,蠢且浪费。配额协议的做法:

1. 应用暴露 /api/monitor/usage/quota/limit 查询当前窗口用量与重置时间
2. 夜间任务触发配额限制时 → 不是失败退出,而是读取真实重置时间点
3. 计算等待时长,定时器到点自动续跑(而不是每 5 分钟盲试)
4. 全程可人工停止;单集失败记录后继续下一集(一集失败不再中断整夜)

配套的加固都是真实事故喂出来的:

  • CogVideoX 轮询超时 900s → 1800s:免费档夜间排队 7+ 分钟是常态,超时太短会把”排队中”误判为”失败”
  • 成片过期检测:重生成过素材的集(成片比素材旧)自动纳入夜间批次重新合成——不然会出现”图是新的、视频是旧的”的静默错位
  • 每集耗时波动大:任务列表的进度预估改为按集粒度,不做时间承诺

Mac ↔ NAS:设定包工作流

部署后的最后一公里:用户在 Mac 上调试各种设定(骨架/硬规则/文风/知识库组合),调好在 NAS 上长期生产。人肉重复配置太蠢,做了设定包导入导出——JSON 打包参数 + 规则 + 知识库全文,纯前端实现零新端点。安全边界明确:不含 LLM 密钥、不含原著源挂接(密钥留在各自机器,源在 NAS 重新挂一次)。

于是完整的工作流闭环成型:

Mac:调试设定 → 导出设定包
NAS:新建项目 → 导入设定包 → 挂原著源 → 🚀 一键开始 / 🌙 夜间挂机
手机:PWA 打开 → 看进度 → 书架读最新章节

踩坑小结

  • 开发环境和生产环境分离,个人工具也不例外。测试误伤生产任务的教训,比任何理论都直观
  • shell 脚本被管道调用必加 pipefail,不然失败永远显示成功
  • 代理方案要分层:buildkit 元数据不走代理 env(基础镜像恒走镜像源)、上传走代理(快)、代理断流备直连(稳)——一层代理策略打天下是不可能的
  • 免费额度挂机要读真实重置时间,盲重试既浪费又吵
  • 远程操作原则:只读免费随便做,花钱操作必须在场——这条原则让你敢在千里之外动生产环境

系列终章:这个项目教会我的事

九篇写完,回头看整个项目最深的三个认知:

  1. 长篇 AI 写作是系统工程,不是模型测评。产出质量 = 记忆架构 × 质量闭环 × 度量反馈 × 部署形态,模型只是其中一个变量。同一个人用同一个模型,裸聊和管线化的产出差距是断崖的
  2. 个人工具的架构美学是”跟得上变化”。文件即数据库、零构建起点、照抄成熟设计、按痛点重构——每一步都为迭代速度服务,不为简历服务
  3. AI 时代个人开发者的价值在调度和裁决。代码产出可以外包给 AI,但”合并时盯三处”的重叠预判、”度量不是表演”的品味坚持、”我要一爽到底”背后的用户立场——这些判断无一可以外包

工具现在已经在我家 NAS 上安静地跑着,夜间写书白天验收。至于那个科幻同人项目写完是什么样——等它完本了,或许会有第十篇。

赞助喵
非常感谢您的喜欢!
赞助喵
分享这一刻
让朋友们也来瞅瞅!