☰
NocoBase 2.0自动更新实践:定时拉取与构建部署脚本
2026/10/15 7:05:07 网站建设 项目流程

NocoBase 2.0 beta 的 next 分支更新有多频繁,只有亲手拉过的人才有发言权。我最初是每天早上手动git pull,然后装依赖、编译、重启服务,一套流程走下来少说二三十分钟,要是赶上依赖大改或者构建报错,一上午就搭进去了。后来实在受不了这种重复劳动,干脆把"Git 拉取 next 分支 + 自动编译 + 自动重启"整个流程做成了每天凌晨定时执行的任务。这篇文章就把我这套折腾下来的完整方案、脚本设计思路、以及跑了一段时间踩到的坑,一次性说清楚,给同样想尝鲜 NocoBase 2.0 又不想被重复劳动拖垮的朋友一个可直接抄作业的参考。

1. 这轮折腾的真正动机:2.0 beta 的迭代节奏比想象中快

1.1 每天的"拉-装-编-启"循环有多低效

NocoBase 2.0 的 beta 阶段,代码变动非常活跃,尤其是 next 分支基本就是开发主干,几乎每天都有新的 commit。表面上看起来只是改了几行代码,但这些改动往往牵一发而动全身。

我实际遇到的情况是,头天晚上还跑得好好的环境,第二天一早就发现某个插件接口变了、某个依赖版本升级了、甚至连数据库表结构都有调整。如果我不及时跟进最新代码,等到想用某个新功能的时候,光排查"为什么我跟官方文档的行为对不上"就能耗掉大半天。

更浪费时间的其实是那个机械式循环:

  • 登录服务器
  • git pull,然后祈祷不要冲突
  • yarn install,等依赖解析
  • 执行构建,等着打包
  • 重启服务,祈祷能一次起来
  • 打开浏览器验证一下

这套流程本身没有什么技术难度,但架不住天天重复。我算了一笔账,就算每次只花二十分钟,一个月下来就是十个小时。十个小时用来写写自己的插件、研究一下 NocoBase 的源码结构,不知道划算了多少倍。这就是我决定做自动化的根本原因。

1.2 自动化到底是替我省时间,还是给我找事

在动手之前我想先把这个账算清楚。自动化这套东西,你是需要写脚本、配定时任务、处理各种意外情况的,这本身也有成本。

我的判断标准很简单:如果这套流程我预计要跑超过两个月,那自动化就值得。因为一次性把脚本写好的成本,摊到两个月甚至更长时间里,比每天手动操作划算得多。而且自动化的价值不只是省时间,更重要的是"确定性"。

手动操作的确定性有多差呢?我举个例子,有时候我上午拉完代码,下午官方又推了新的提交,那我的环境就不是最新的了。但如果定时任务定在凌晨执行,那么我每天早晨面对的环境,永远是截至当天凌晨的最新代码。这对于想跟踪 2.0 beta 进展的人来说,价值非常大。

另外还有一个容易被忽略的点:手动操作容易漏步骤。比如有时候我git pull完就忘了重新构建,或者构建完忘了重启,结果跑到一个半新不旧的状态,出了问题都没法确认是哪一段导致的。自动化脚本不存在这个问题,因为每一步都是强制执行的,日志也清清楚楚。

所以我的结论是:对于 "天天都要做、流程固定、越做越烦" 的操作,自动化是稳赚的。接下来我先把手工流程的每个关键点摸清楚,确认了这条链路的脾气,再写脚本不迟。

2. 手动流程走一遍:先确认每个环节的脾气

2.1 环境准备与依赖选型

在写任何自动化脚本之前,我建议你先完整地手动跑通一遍流程。这一步的意义不在于"你会不会手动操作",而在于让你确认每个命令在你的机器环境下表现是否正常。

我搭建这台测试服务器的时候,环境是这样准备的:

  • 操作系统使用的 Ubuntu Server,安装好基础的 build 工具链
  • Node.js 版本需要严格满足项目要求,我用的版本管理器来切换,避免系统版本污染
  • 包管理器我选择的是 Yarn,因为 NocoBase 的官方文档和仓库脚本对 Yarn 的支持最完整
  • 数据库用的 PostgreSQL,因为从长远看 2.0 的很多特性在 PostgreSQL 上支持得最好

这些前置条件里面,最容易出问题的是 Node.js 版本。我遇到过某些依赖在特定 Node 版本下编译报错的情况,后来统一用一个长期维护版本,问题少了很多。数据库初始化这里我也不展开了,但提醒一句:如果你和我一样是在已有环境上重新拉代码,数据库迁移这块要特别注意,后面的坑位专门讲。

2.2 拉取 next 分支的正确姿势

拉取代码这一步,最忌讳的就是无脑git pull。原因很简单——next 分支是开发分支,它可能在你本地落后很多的情况下强制推送过历史,这时候普通 pull 就会产生一堆莫名其妙的冲突。

我用的方案是这样的:

# 先切到 next 分支,确保本地没有游离的 HEAD git checkout next # 拉取远程的最新状态 git fetch origin # 用 reset 方式把本地分支强制对齐到远程 git reset --hard origin/next

这套组合命令对本地代码的改动是"毁灭性"的。如果你在本地改过东西,会被直接覆盖掉。所以我在执行自动化之前,专门做了两条规定:

  • 这台服务器只是用来跑 NocoBase 测试环境的,不在里面做任何代码开发
  • 本地如果有临时改动,一律先 commit 到单独的功能分支再合并,否则 reset 之后哭都来不及

提示:git reset --hard不是银弹。如果你的本地仓库存在比较重要的未推送提交,请务必提前做好备份或者避免使用这个方案。

2.3 编译与启动命令的实际表现

NocoBase 这种前后端一体的项目,编译流程比普通的前端项目复杂一些。它需要同时处理服务端代码和客户端构建。我这边实际执行的命令大致如下:

# 安装依赖(遇到 lock 文件变更会自动解析) yarn install # 完整构建 yarn build

构建的耗时取决于服务器性能。我现在这台机器上,全套下来大概在 10 到 15 分钟之间波动。这个时间其实不算长,但对于定时任务来说,我必须在脚本里设置足够的超时时间,否则构建还没跑完就被系统杀掉,那就白跑了。

启动服务这块,我记得官方文档里有 dev 模式和生产模式的区分。为了模拟真实环境,我选择了生产模式启动,也就是构建完成后用yarn start --quickstart这类方式直接拉起来。这一步特别费内存,曾经我因为同时开着旧服务和新构建,导致服务器内存告急直接卡死,所以在后面的自动化脚本里特意设计了先停旧服务再构建的顺序。

手动跑通一遍之后,我就对这条链路的花费心里有数了。接下来要做的,就是把这一整串动作交给一段脚本。

3. 把手工流程转成一个可维护的自动化脚本

3.1 脚本的整体结构与执行顺序

设计脚本的时候,我给自己订了几条原则:

  • 每一步都要有独立的日志输出,方便失败后定位
  • 任何一步失败,整个流程必须中止,不能带伤继续
  • 脚本要能够重复执行,即使某次跑到一半被中断,下次执行也能正常恢复

基于这几条原则,我最终的脚本结构分成四个阶段:

  1. Git 更新阶段:拉取最新代码,确认是否有新提交
  2. 依赖与构建阶段:安装依赖并完成编译
  3. 服务重启阶段:停止旧服务,启动新服务
  4. 健康检查阶段:验证新服务是否真正可用

这里的顺序设计是有讲究的。我没有把"停止旧服务"放在最前面,而是放在构建完成之后,就是为了减少服务不可用的时间窗口。虽然定时任务通常会在凌晨执行,外界访问量本来就少,但能少断一会儿是一会儿。

3.2 Git 层的处理:绝不硬拉,先检查再更新

我的更新脚本是这样写的:

#!/usr/bin/env bash PROJECT_DIR="/path/to/nocobase" LOG_FILE="/var/log/nocobase-auto-update.log" BRANCH="next" # 记录日志的函数 log() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" } log "=== 开始自动更新流程 ===" cd "$PROJECT_DIR" || exit 1 # 检查是否有更新的提交 git fetch origin "$BRANCH" LOCAL_REV=$(git rev-parse HEAD) REMOTE_REV=$(git rev-parse "origin/$BRANCH") if [ "$LOCAL_REV" = "$REMOTE_REV" ]; then log "没有新的提交,跳过本次更新" exit 0 fi # 有更新,执行强制对齐 git reset --hard "origin/$BRANCH" log "代码已更新: $LOCAL_REV -> $REMOTE_REV"

"没有新提交就直接退出"这个设计非常关键。我见过不少人写 crontab 的时候,不管有没有更新都执行一遍yarn install和yarn build,结果浪费大量 CPU 和内存。有了这个判断逻辑,大多数凌晨的执行都会在几秒钟内快速结束,只有真正有新提交的时候才走完整流水线。

3.3 构建层的处理:锁文件、缓存与超时守护

构建阶段是整个流程中最脆弱的环节,依赖安装可能失败、编译可能报错、服务器内存可能不够。我做三个防护措施:

第一,依赖安装要区分情况。如果yarn.lock文件有变化,说明依赖树发生了变动,这时候我直接走完整安装;如果没有变化,可以用yarn install --frozen-lockfile这种模式,速度更快也更稳定。

第二,构建命令需要设置超时保护。我是用timeout命令包住整个构建过程,比如:

timeout 3600 yarn build if [ $? -ne 0 ]; then log "构建失败或超时,流程终止" exit 1 fi

这里的 3600 秒是我根据机器性能留出的余量。如果你的机器比较弱,建议把这个值调大,否则构建到一半被中断,下次还要全部重来。

第三,注意服务器的磁盘空间和内存余量。构建过程会生成大量临时文件和产物,如果磁盘满了,任何操作都会失败。我专门写了一个磁盘检查:

DISK_USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//') if [ "$DISK_USAGE" -gt 85 ]; then log "磁盘使用率过高: ${DISK_USAGE}%,终止流程,请人工处理" exit 1 fi

3.4 应用层的处理:优雅下线与拉起

构建完成之后,才是重启的阶段。我一开始是直接杀掉旧进程再启动新的,但发现这样偶尔会留下残留的子进程,导致端口还是被占用,新的服务反而起不来。

后来我改成了优雅停机的思路:

# 如果有旧服务在运行,先发送终止信号等待退出 if [ -f "$PROJECT_DIR/pidfile" ]; then OLD_PID=$(cat "$PROJECT_DIR/pidfile") if kill -0 "$OLD_PID" 2>/dev/null; then kill "$OLD_PID" sleep 10 # 10 秒后如果还活着,强制终止 if kill -0 "$OLD_PID" 2>/dev/null; then kill -9 "$OLD_PID" fi fi fi # 清理端口占用(以防万一) fuser -k 13000/tcp || true

这个脚本一开始给了优雅停止 10 秒的等待时间,正常情况下够 NocoBase 完成收尾了。10 秒后还没退出,才会强制执行强杀。用fuser -k清理端口是最后一道保险,避免"端口被残留进程占用"导致新服务启动失败。

启动部分,我没有直接用yarn start裸奔,而是用进程守护工具来托管。这样即使服务崩溃了也能自动拉起,比裸进程可靠得多。

4. 交给系统去定时:定时器与守护编排

4.1 定时执行方案的选择

脚本写好了,接下来就是解决"每天自动执行"的问题。常见的方案无非是 cron 和 systemd timer,我自己最终选的是 systemd timer,原因后面说。

如果你更习惯用 cron,最简单的写法是这样:

0 4 * * * /usr/local/bin/nocobase-update.sh >> /var/log/nocobase-cron.log 2>&1

每天凌晨 4 点整执行一次。这个时间点是我综合考虑了官方团队通常的提交时间以及服务器负载之后选的,但这只能算是一个经验值,大家可以根据自己的时区和网络情况调整。

cron 的好处是简单、哪里都有、不用学新东西。但它的缺点也很明显——没有天然的依赖管理,服务崩溃了没人管,日志也要自己手动处理。

4.2 systemd 定时器和服务的组合玩法

我最终用的是 systemd 方案,具体分为一个 service 单元和一个 timer 单元。

service 单元负责真正执行脚本:

[Unit] Description=NocoBase Auto Update Service [Service] Type=oneshot ExecStart=/usr/local/bin/nocobase-update.sh StandardOutput=append:/var/log/nocobase-systemd.log StandardError=append:/var/log/nocobase-systemd.log

timer 单元负责触发它:

[Unit] Description=Timer for NocoBase Auto Update [Timer] OnCalendar=*-*-* 04:00:00 Persistent=true [Install] WantedBy=timers.target

这套组合里,Persistent=true是个很有用的参数。它表示如果系统在凌晨 4 点处于关机状态,那么开机之后会立即补跑一次任务,不会出现"机器没开机就错过更新"的情况。

相比纯 cron,systemd 方案的好处是服务状态可查询、日志统一管理、还能用systemctl status随时看执行结果。这对于一个要长期跑的自动化任务来说,省心很多。

4.3 通知机制:失败必须第一时间知道

自动化任务最容易出现的问题是"失败而无人知晓"。我一开始跑了一个多星期,翻日志的时候才发现中间有两次构建失败,但那时候已经不知道该对应哪个代码版本了。

所以我在脚本末尾加了一个简单的通知逻辑:失败的时候直接把错误信息发到常用的通讯工具,比如邮件或者 IM webhook。这样每天早上只要看一眼手机,就知道昨晚的更新是成功还是失败。

if [ $? -ne 0 ]; then curl -s -X POST "https://your-webhook-url" \ -H "Content-Type: application/json" \ -d "{\"text\": \"NocoBase 自动更新失败,请检查日志\"}" exit 1 fi

这不牵扯到复杂的基础设施,一个 webhook 请求就够了。关键是让人能在第一时间介入,而不是等自己发现的时候已经积压了几天的问题。

5. 自动流水线里最容易踩的坑与排查记录

5.1 端口冲突:旧进程没死透,新进程起不来

这是我这套流水线运行以来遇到最多的一个问题。现象是脚本日志里显示构建成功、服务也执行了启动命令,但浏览器访问却打不开。

查了半天,发现问题的根源在于旧进程的残留子进程还在绑定端口。我原来的停止方案是用kill结束主进程,但这个项目启动之后可能会 fork 出若干个子进程,主进程退出了,子进程还占着端口不放。之后新服务尝试监听同一个端口,直接报EADDRINUSE。

最终的解决方案已经在前面出现过了,就是先用kill -0检查进程是否还活着,等 10 秒,然后再用fuser -k强制清理端口。这套三重保险基本把这个问题压下去了。

5.2 依赖变更导致的构建失败

next 分支的开发节奏决定了依赖变动非常频繁。有时候仅仅是一个依赖的小版本升级,就能让整个构建在某个插件上挂掉。

我遇到过的典型报错是某个原生模块需要重新编译,但由于本机的 Node 版本或者工具链不对,编译失败。这种情况,手动处理都很烦躁,更别说是凌晨自动跑了。

我的排查链路是这样的:

  • 先看构建日志,定位是在哪个插件的编译阶段挂掉的
  • 检查是不是因为 Node.js 版本变了,跟官方要求的版本对不上
  • 查看项目仓库近期有没有调整过依赖引入方式,例如从某个老包切换到新包
  • 如果是原生模块的问题,尝试清理掉整个 node_modules 和构建缓存,重新来一遍
rm -rf node_modules rm -rf .cache yarn install yarn build

有没有一劳永逸的办法呢?说实话没有。因为 next 分支本身就包含着实验性变更,构建失败在某种程度上是"正常"的。我能做的是让脚本在这种情况下自动终止,发出告警,而不是带着失败的结果硬启动一个旧版本的服务,造成新旧代码混跑的诡异状态。

5.3 磁盘空间与日志增长的隐患

自动更新跑久了,最隐蔽的问题其实是磁盘。每次构建都会产生新的产物,日志文件也在不断膨胀。我第一次发现这个问题,是某天脚本报"磁盘空间不足,无法写入日志"。

从那以后,我在脚本里加了两条日志清理策略:

  • 超过 7 天的日志文件自动压缩并删除
  • 保留最近 5 次构建产物,旧的构建产物自动清理

这些都是用一行 find 命令就能完成的动作,但没了它们,再稳定的流水线也会被慢吞吞地挤垮。

5.4 数据库迁移的提醒

前面手动流程我提到过数据库的事情,这里展开说。NocoBase 2.0 的 beta 阶段,数据库表结构的变动不算罕见。有些新版本在启动时会自动做迁移,有些则需要在启动前手动执行迁移命令。

我遇到过最尴尬的情况是:代码更新成功、构建成功、服务也起来了,但业务数据查询报错,原因是表结构跟新的代码对不上。

解决方法是固定下来启动前必须额外执行一步初始化或迁移指令。现在我的流水线脚本里只有检测到数据库相关变更时才会执行额外迁移步骤(通过对比变更文件的方式),直接把这个环节也纳入自动化,避免手工漏执行。

注意:数据库迁移操作在自动化场景里要慎之又慎。如果迁移失败,千万不要重试,先把日志保存好,早晨起来人工处理。自动化的边界要清楚,不是所有动作都适合交给机器去反复折腾的。

5.5 网络不稳定:拉取失败的正确处理

Git 拉取依赖外网,网络波动在所难免。我的脚本目前在 fetch 失败时会直接退出并报警。这里有一个容易被忽略的细节:如果git fetch失败,而代码又没有更新,我会保持默认退出,不重试。

有人可能会问,为什么不重试?因为 fetch 失败时大概率是网络不稳定,重试偶尔能成功,但如果是 DNS 或者配置问题,重试多少次都没用。与其在主流程里写一个复杂的重试逻辑,不如把它交给外层的定时机制。反正我们的定时是每天一次,如果某天凌晨失败,第二天凌晨还会再跑。如果真的担心,可以再加一个每小时的轻量检查任务,只做 fetch 判断,不拉代码,这样不至于浪费构建资源,又能提高时效性。

这套自动流水线我跑了一阵子之后,最大的感受就是"省心"。我甚至都不太需要记得它是哪天更新的,每天早上看一眼前一天的运行通知,确认今天的环境状态就好。对于想长期跟踪 NocoBase 2.0 beta 的人来说,与其每天花半小时在机械操作上,不如花一个周末把整个流程自动化掉,然后把精力留给真正值得研究的东西。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询