☰
NocoBase 2.0 Beta 每天自动更新:从 Git 拉取到 pm2 平滑重启的方案
2026/10/9 6:42:10 网站建设 项目流程

自己跟 NocoBase 2.0 beta 跟了快两个月,对这个版本的迭代频率深有体会。next 分支几乎每天都有新提交,我之前一直手动从 Git 拉代码、安装依赖、构建、再重启,纯手工操作。第一天觉得有仪式感,第二天还能接受,到第五天我就想写脚本了。最后定型的这套流程就是标题里说的"每天自动更新 + 自动编译 + 自动重启",除了解决我自己的手痛问题,也希望能给同样在折腾自建低代码平台的朋友一条可以照抄的路径。

先说一下这套方案到底解决了什么问题:NocoBase 2.0 beta 在 next 分支上推进速度非常快,但版本不稳定,你不跟的话体验不了新功能,你天天跟的话手动成本极高。自动化之后,每天早上定时拉取最新代码,自动跑依赖安装和构建,构建通过就平滑重启服务,构建失败就保留上一个可用版本继续跑,整个过程不需要人盯着。适合正在深度使用 NocoBase 2.0、想跟进 next 分支但不想被重复劳动拖垮的人参考。

1. 为什么选 next 分支做自动化更新

1.1 2.0 beta 的版本状态与迭代节奏

NocoBase 2.0 是架构上比 1.x 变化很大的一个版本,核心思路是无代码/低代码平台,后端基于 Node.js,前端基于 React。2.0 的 beta 阶段特性还没有完全冻结,很多重要改动直接推到 next 分支上,所以你在 GitHub 上看到的默认分支不一定是最新的,真正活跃的开发都在 next 上。

我实际观察下来,next 分支的提交节奏基本上是"工作日必有更新,偶尔一天多批"。有时候是修复一个数据源连接问题,有时候是调整某个区块组件的行为,甚至只是改文档和示例。但对我这种把 NocoBase 当生产工具来用的人,任何一个功能性提交都可能影响我正在构建的应用行为,所以不能不看、不能不更。

另一个客观事实是,2.0 beta 的构建产物跟 1.x 差别很大,客户端需要完整的 Webpack 构建,服务端需要处理数据库 migration。这意味着"更新"这个动作不是简单git pull就行了,它天然包含"拉代码 → 装依赖 → 构建 → 迁移 → 重启"这样一串动作。这一串动作手动做,一次至少十分钟,一天两次就是二十分钟的重复劳动。更重要的是,人执行这种多步骤操作时容易漏掉某一步,比如拉完代码忘了装依赖,或者构建完了忘了重启,服务就停留在半新不旧的状态,排查起来非常头痛。

1.2 手动流程的三宗罪

  • 容易漏步骤:人不是机器,拉完代码可能顺手关终端就去干别的了,回来发现服务还是旧版本,或者依赖没装好直接启动报错。
  • 时间不可控:每次构建客户端要花几分钟,这段时间你只能盯着终端,干不了别的。如果碰巧遇到依赖版本冲突,可能耗掉半小时。
  • 没有可回溯的状态:手动执行完,浏览器刷新发现页面不对劲,你根本说不清是这次更新引入的问题,还是上次没重启的残留问题。

我正式决定写自动化脚本,是在某次更新后服务一直报模块找不到的错,查了半天发现是yarn install没跑。那一刻我就知道,这个流程必须交给定时任务去跑,而且要跑得比手动更稳定、更挑剔。

1.3 自动化方案的边界设定

动手之前先给自己定了几条边界,自动化不等于无脑执行:

  • 更新必须基于 Git 的干净状态,本地不允许有未提交的修改干扰合并。
  • 依赖安装失败或构建失败时,必须中止流程,不能让服务挂着半成品状态。
  • 重启要尽量平滑,用pm2 reload而不是restart,减少服务不可用时间。
  • 每次执行要有日志,哪怕只是追加到一个文件里,出问题时有据可查。
  • 数据库迁移(migration)必须等我确认构建通过后再执行,不能跟构建混在一起跑。
  • 要有一套简单的健康检查,重启后确认端口有响应,否则告警。

这些边界决定了脚本的结构,也决定了后面很多细节处理。下面从环境准备开始讲。

2. 环境准备与 Git 拉取的细节

2.1 基础环境清单

先交代我的服务器环境,这套方案不挑发行版,我用的是 Ubuntu 22.04 LTS,配置如下:

项目配置
操作系统Ubuntu 22.04 LTS
Node.js18.17.1(建议用 nvm 管理)
包管理器yarn 1.22.x
进程管理pm2 5.x
数据库PostgreSQL 14(自建)
代码目录/data/nocobase-next

建议所有服务用普通用户运行,不要用 root 直接跑 NocoBase。我单独建了一个nocobase用户,代码目录权限给到这个用户下,pm2 也用这个用户管理,这样权限边界干净,后面排查问题也简单。

Node.js 版本要特别注意。NocoBase 2.0 beta 对 Node 版本有要求,低于 16 基本跑不起来,我自己在 18 上跑得很稳,20 也可以,但个别依赖有过兼容问题。用 nvm 安装指定版本,并且给nocobase用户配置好.nvm环境,否则 cron 执行脚本时会因为找不到 node 命令而报错。这一点很多第一次做自动化的人会忽略。

2.2 克隆 next 分支的正确姿势

第一次拉取代码,我建议用 SSH 方式而不是 HTTPS。SSH 免密配置好之后,后续定时任务拉取不会因为账号密码输入不进去而中断。克隆命令如下:

git clone -b next --single-branch git@github.com:nocobase/nocobase.git /data/nocobase-next

用-b next直接指定分支,用--single-branch只拉取 next 这一个分支的提交历史。这样有两个好处,一是克隆速度快,因为不用把全部远程分支引用都拉下来;二是后续 fetch 的体积小,整个仓库的元信息更精简。如果你已经用默认方式克隆了完整仓库,也没关系,后续运行git remote set-branches origin next再git fetch --prune也能收敛。

克隆完先手动跑一次完整安装和构建,确认服务能起来再上自动化。这一步的动作是:

cd /data/nocobase-next yarn install yarn build yarn start --port 3000

如果这一步能起来,说明环境和代码都正常,后面脚本只是在重复这一串动作。

2.3 SSH 认证与连接保持的坑

SSH 拉代码平常不觉得有问题,但放到定时任务里就变敏感了。我遇到过的典型错误是"ssh认证失败"或者Permission denied (publickey)。原因通常是:

  1. 生成 SSH key 时没有指定-o,老格式密钥在某些环境不被新版 OpenSSH 接受。
  2. 另一个用户执行脚本,读取不到~/.ssh/id_ed25519,因为密钥权限或 owner 不对。
  3. known_hosts里没有 GitHub 的指纹,首次连接会有一个确认交互,而定时任务环境没有交互能力,直接失败。

解决办法第一条是用 Ed25519 算法重新生成密钥并上传公钥:

ssh-keygen -t ed25519 -a 100 -f ~/.ssh/id_ed25519 -o

第二条是确保nocobase用户主目录下的.ssh目录和文件权限正确:

chmod 700 ~/.ssh chmod 600 ~/.ssh/id_ed25519

第三条是提前手动连接一次,让 GitHub 指纹写入 known_hosts:

ssh -T git@github.com

做完这三件事,SSH 层面基本不会再出幺蛾子。我在服务器上用同一个密钥管理多个仓库,所以还会在~/.ssh/config里加ServerAliveInterval 30、ServerAliveCountMax 3,避免长时间挂起时连接中断,这对 fetch 大仓库时的稳定性有点帮助。

2.4 工作目录与本地状态约定

自动化脚本会修改工作目录,所以从一开始就要约定好:这个目录是"可以从远程强制覆盖"的,本地不允许养着没备份的代码。如果你有需要保留的本地修改,单独放到别的目录,或者提交到自己的 fork 仓库,不要堆在这个目录里。

为了实现这个约定,脚本第一步会做一次git add -A && git stash,把本地未提交的更改暂时收走。这一步很多人不喜欢,觉得是暴力操作,但自动更新场景下,一个藏了几天、忘了内容是什么的 stash,比频繁冲突好处理得多。stash 之后执行git clean清理掉多余文件,然后git reset --hard origin/next,确保本地和远程完全一致。如果不是特别在意本地修改,这一步是最省心的。

3. 自动化脚本:更新-编译-重启

3.1 脚本三段式设计

整个脚本的设计很简单,就是"更新 → 编译 → 重启"三段,中间任何一段失败就中止,并保留上一个可用版本继续运行。我把脚本放在/opt/scripts/nocobase-update.sh,内容结构如下:

#!/usr/bin/env bash set -euo pipefail APP_DIR="/data/nocobase-next" LOG_DIR="/opt/scripts/logs" LOCK_FILE="/opt/scripts/nocobase-update.lock" log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $*" | tee -a "$LOG_DIR/update.log" } mkdir -p "$LOG_DIR" # 防止脚本重入 exec 9>"$LOCK_FILE" if ! flock -n 9; then log "另一个更新任务正在执行,本次跳过" exit 0 fi log "===== 开始更新 =====" # 1. 更新代码 cd "$APP_DIR" git add -A git stash || true git fetch origin next git reset --hard origin/next # 2. 安装依赖 yarn install --frozen-lockfile # 3. 构建 yarn build # 4. 迁移数据库 yarn nocobase migrator up # 5. 重启服务 pm2 reload nocobase-next log "===== 更新完成 ====="

这个脚本有几个设计点要展开讲,下面分段说明。

3.2 更新环节:为什么用 reset 而不是 pull

很多人一上来就写git pull,这在新手教程里看起来没问题,但放到自动化场景就脆弱了。git pull本质是 fetch + merge,如果本地有冲突,merge 会停下来等你处理,而"等"在无人值守环境下就是僵死。所以脚本里我用的是:

git add -A && git stash git fetch origin next git reset --hard origin/next

reset --hard操作前,我已经把本地可能的修改 stash 掉了,所以 reset 是把整个目录恢复到远程 next 分支的最新状态。这等于每次更新都是"基于干净状态重新同步",最大程度避免了 merge 冲突和悬挂副本。

有人会问,那 stash 里的修改不是丢了吗?不会,git stash会生成一个 stash 记录,你随时可以用git stash pop恢复到某个分支上。我在脚本里没有自动恢复,因为自动化更新的场景下,远程代码才是唯一可信源,本地修改需要人手动介入判断。如果你确实有本地配置要保留,比如packages/core/src/database/connection之类的自定义,那就不要把这类文件根进 Git,而是做成单独的管理机制,例如用.env或者环境变量注入。

git fetch拉到更新之后,还可以顺便看下这次更新有没有触碰 NocoBase 的依赖锁文件:

cd "$APP_DIR" git diff HEAD..origin/next --name-only | grep -E "package.json|yarn.lock"

如果输出为空,说明本次没有依赖变更,yarn install理论上可以跳过,能省些时间。但为了稳,我的脚本没有做这个判断,每次都跑 install,因为时间多花一分钟总比启动时缺依赖强。

3.3 编译环节:依赖、构建与失败兜底

编译是脚本里最耗时的部分,也是最容易出意外的地方。

依赖安装我用的是yarn install --frozen-lockfile。这个参数会严格按照 yarn.lock 里的版本号安装,不会擅自升级依赖,保证跟 CI 使用的环境一致。在 next 分支频繁变动的背景下,锁文件经常被维护者更新,所以每次拉取后跑一次 install 是非常必要的。如果你用的是 npm,则对应npm ci。

构建命令yarn build在 NocoBase 2.0 里会做客户端 Webpack 构建、服务端编译、以及其他工作区打包。这个过程可能持续三到十分钟,具体看机器配置。因为脚本开了set -euo pipefail,构建失败会直接退出,pm2 里的旧进程不会受到影响,服务仍然跑在上一个可用的构建产物上。这是整套脚本安全性的核心。

set -e这个选项一定要加上,因为它保证任何一条命令返回非零,脚本就立即中止。没有它的话,如果yarn install静默失败而构建又恰好过了(理论上不太可能,但实践中有过),服务会被重启到脏状态。另外我建议在构建前清理一次之前的历史构建产物,保障磁盘空间。构建产物一般只保留最后一次即可,命令参考:

rm -rf /data/nocobase-next/packages/apps/xx/dist

具体目录名以上级版本为准,实际操作时可以先du -sh看哪个目录涨得最快再清。

3.4 重启环节:pm2 reload 与用过才知道的坑

重启这一步,我选 pm2 而不是 systemd 或 nodemon。理由是 pm2 对 Node 应用的守护、日志轮转、开机自启支持都比较成熟,NocoBase 社区里很多人也用 pm2。

初次启动 NocoBase 时,用pm2 start指向启动脚本:

pm2 start "yarn start" --name nocobase-next --user nocobase pm2 save

后续更新用pm2 reload nocobase-next,而不是restart。两者区别在于 reload 会先启动新进程待命,确认成功后切换流量,再杀掉旧进程,整个过程服务中断时间很短。restart是先杀后起,会有一个明显的断开窗口。对于正在操作 NocoBase 页面的用户,reload 的体验好得多。

这里有一个我实际踩过的坑:pm2 默认相当聪明,但也有过度保护机制。如果应用启动后立刻崩溃,pm2 会认为代码有问题,进入 stable 状态并停止反复重启,这本来是好事,但有时我们更新完代码,应用因为缺失某个环境变量会崩一次,pm2 就不拉起来了,需要我们人工介入。解决方式是给 pm2 应用配置max_restarts和restart_delay参数,让它在面对瞬时崩溃时有更大的容错空间:

pm2 start "yarn start" --name nocobase-next --min-uptime 10000 --max-restarts 5

另一个坑是pm2 save偶发过期。你新增了环境变量或修改了启动参数,以为pm2 save之后重启系统会带上新配置,实际不一定。建议每次更新完应用配置都重新执行pm2 delete nocobase-next && pm2 start ...,让重新配置的进程被保存下来,不要只改不改。

4. 定时任务与进程守护

4.1 crontab 定时编排

脚本写完,剩下就是让系统定时跑起来。crontab 是最简单的方案,一行配置就够:

0 6 * * * /opt/scripts/nocobase-update.sh >> /opt/scripts/logs/cron.log 2>&1

每天早上 6 点执行一次,刚好在我早上开始用系统之前完成一次更新。如果你希望一天多次,可以改成0 */6 * * *,但我不建议太频繁。NocoBase 下一个 beta 版本可能一天有多次提交,但服务器的构建和重启是有成本的,一天一次节奏比较平衡,有紧急改动再手动触发一次就好。

执行用户要跟 pm2 的用户保持一致,否则 pm2 reload 时会报权限问题。另外 cron 的环境变量非常精简,它不会加载你的.bashrc,所以脚本开头要显式加载 nvm 环境,或者通过绝对路径调用 node、yarn。我实际做法是在脚本开头这样处理:

export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" export PATH="/usr/local/bin/:$PATH"

否则你会碰到 cron 里 node 找不到或者 yarn 找不到这类低级但又莫名其妙的问题。

4.2 pm2 开机自启与自动恢复

自动化更新解决了日常跟进问题,但服务器重启后的恢复也同样重要。pm2 提供了startup命令来生成 systemd 服务,让应用随操作系统一起启动:

pm2 startup systemd -u nocobase --hp /home/nocobase pm2 save

第一行会在 systemd 里生成一个pm2-nocobase服务,执行用户是nocobase。第二行把当前 pm2 里运行的进程列表保存下来,下次开机时 pm2 会按保存的清单恢复应用。

要注意的是,如果你在系统开机后手动了修改应用目录,比如切换过 Node 版本,pm2 恢复后可能起不来。所以我的习惯是每次大版本操作后用pm2 restart --update-env刷新一下环境变量,再执行pm2 save。这样无论背后环境怎么变,pm2 恢复时都能正确启动。

4.3 日志体系与健康检查

日志是自动化系统的眼睛。脚本更新日志我按天分开存,覆盖当天所有动作:

  • update.log:脚本的执行轨迹,包括拉取、安装、构建、reload 四个阶段。
  • cron.log:cron 调用脚本的标准输出和错误输出。
  • pm2自带的日志:pm2 logs nocobase-next可以实时看 NocoBase 应用的标准输出和错误。

日志不能只存不管,我用了一个简单的日志清理命令,保留最近 30 天:

find /opt/scripts/logs -name "*.log" -mtime +30 -delete

健康检查我推荐在脚本最后增加一个 http 探测,确保 reload 后端口真的能响应。这个探测可以是简单的curl -f加超时:

for i in {1..20}; do curl -sf http://127.0.0.1:3000/api/health >/dev/null && { log "健康检查通过" exit 0 } sleep 3 done log "健康检查失败,端口未响应" exit 1

如果 NocoBase 没有现成的 /api/health 端点,可以探测首页,或者直接检查 TCP 端口是否在监听:

ss -lnt | grep -q ':3000 '

注意这里要写清楚,探测失败时脚本退出码是非零,cron 会把错误记到 cron.log。但就算探测失败,我不会自动回滚旧版本,因为回滚动作本身也可能引入新的不一致。更稳妥的做法是保留上一个构建产物的副本,并保留一份手动回滚命令,出问题的时候人工判断。很多生产事故都是在自动化回滚时又叠加了新问题,所以对这个环节我比较保守。

5. 常见问题排查与避坑实录

5.1 git pull 被本地修改打断

即便脚本里做了 stash,也总有一些情况是 stash 也没兜住的。最常见的是.env文件被修改后又被 stash,导致构建时才报配置缺失。解决办法是在.gitignore里把本地配置类文件排除掉,不要跟踪它们。脚本可以额外跑一次git status --porcelain,如果更新后还有未跟踪文件干扰构建,就输出警告并终止,避免带着不平衡的状态继续走。

另一个真实场景是有人直接在服务器上改了 NocoBase 的源码文件(比如调试某个 bug),这个修改被 stash 收走之后,他第二天忘了这回事,而自动化脚本 reset 掉了它。这个坑很难从技术上完全避免,只能靠工作习惯:服务器上的代码目录保持纯净,调试代码统一放在本地开发机。

5.2 编译失败但服务还在老版本

我特意设计成编译失败不重启,确保服务继续跑旧版本。但这里有个现实问题:如果 next 分支连续两天都构建失败,你第三天早上去看,服务已经落后非常多了,错过了一大批重要更新。

我的处理方式是每天看一眼update.log,如果连续两次失败,就手动去 GitHub 仓库看一下当天的提交是否处于"半成品"状态。很多 beta 分支的提交本身就是渐进式的,第一个提交可能故意破坏什么,第二个才补上,如果你刚好在第一个提交之后构建,失败是正常的。这时不要慌,等下一次成功后自动恢复即可。

5.3 重启后数据库迁移报错

NocoBase 的数据库迁移机制每次启动时都会检查 schema,如果构建成功但迁移失败,web 页面会提示数据库状态异常。我在脚本里单独加了一步yarn nocobase migrator up,并在构建之后、reload 之前执行。这样 migration 失败时脚本照样中止,服务不重启,数据库停留在旧版本 schema,不会出现"新代码 + 旧 schema"这种更尴尬的组合。

但要注意,NocoBase 2.0 的 migration 命令可能随版本演进发生变化,如果你的版本提示命令不存在,去看一下对应版本的 package.json scripts,把命令替换成最新的。我遇到过几次,都是因为维护者在开发早期频繁调整命令名。

5.4 SSH 认证失败与 known_hosts 问题

定时任务跑的时候报Host key verification failed,十有八九是 known_hosts 的问题。解决办法前面已经写了,这里补充一个细节:服务器用的公钥必须是authsock权限匹配的,如果公钥被放在/home/nocobase/.ssh/authorized_keys这种路径,而执行用户不是 nocobase,也会失败。排查时先手动用脚本里的用户跑一次ssh -T git@github.com,能通就说明 SSH 本身没问题。

5.5 安全提醒:.git 目录不要暴露在公网

最后说一个安全相关的细节。Git 拉取代码后,服务器上会有完整的.git目录和源码,如果 NocoBase 直接对公网开放了静态资源,某些服务器配置可能会让访问者通过 URL 直接浏览到.git/HEAD、.git/config这类文件,造成"git 目录泄露"问题,源码、过往 commit 信息、甚至配置都可能被拖走。这对任何用 Git 部署的项目都是大忌。

解决方案是 Nginx 层拦截对.git和源码目录的访问:

location ~ /\.git/ { deny all; return 404; }

同时在 NocoBase 前端构建产物目录之外,不给源码目录任何静态代理。这点很多人会忽略,尤其是 NocoBase 自带静态服务时,更要在入口处封堵。检查方法很简单,浏览器直接访问一下服务器 IP 的对应路径,看能不能拿到.git/config,能拿到就说明已经泄露了,要立即处理。

最后再分享一点个人体会

整套自动化方案跑了一个多月后,我最大的感受是:真正稳定的不是某个单独的命令,而是"更新-编译-重启"这条链路上的每道保险。脚本里的set -e、锁文件、stash、reload、健康检查,每一个单独看都像是多余操作,但组合在一起,才能保证你在每天早上拿起手机打开页面时,系统已经无声无息地跑在最新版本上。

如果你打算照抄这套脚本,我建议先在自己的服务器上跑两周手动模式,把 NocoBase 的构建命令、迁移命令、目录结构都摸清楚,再改成自动化。自动化不是把时间省掉,而是把时间转移到更值得做的事情上。等你熟悉了这套流程,还可以再扩展一个通知功能,比如把更新结果推到 Telegram 或者飞书机器人,这样即使脚本失败,你也能第一时间知道,而不是等到服务挂了才后知后觉。

这套方案虽然是为 NocoBase 写的,但核心思路换成任意频繁迭代的开源项目都成立:拉代码、装依赖、构建、迁移、平滑重启,五步走完,剩下的交给定时任务。

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

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

立即咨询