DooTask 更新流程实战:`sudo ./cmd update` 的前置检查、本地改动决策与失败处理
DooTask 更新流程实战:sudo ./cmd update 的前置检查、本地改动决策与失败处理
本篇基于 DooTask 仓库内置的更新操作技能文档(.claude/skills/dootask-update/SKILL.md),系统讲解一套"刚性"的服务端更新流程:如何通过 ./cmd update 一条命令完成拉代码、Composer 依赖更新、数据库迁移与服务重启,以及如何正确执行前置检查、在"本地有改动"这一关键岔路做出安全决策、并妥善处理各类失败。读完后,你可以对已部署的 DooTask 实例执行一次不丢数据、不丢本地改动的安全更新。
核心原则:脚本负责执行,流程负责把关
DooTask 把整套更新逻辑封装在根目录的管理脚本 cmd 的 handle_update 函数中,一条命令即可串联完整链路:检测本地改动 → git fetch → 必要时自动备份数据库 → git pull/git reset → composer install → php artisan migrate → 重启 php/nginx 容器 → 写入 UPDATE_TIME。
更新流程文档将其定位为"刚性技能",核心原则是:
- 任何一步失败立即停止,不自动重试、不自动跳过;
- 前置检查通过 → 向用户确认一次 → 执行 → 报告结果,不多不少;
- 尤其不要替用户决定强制更新——
--force会丢弃本地改动,这个决定只能由人拍板。
所谓"刚性",意味着违反字面规则就等于违反流程精神:不要擅自加步骤、不要绕过 sudo、不要省略确认环节。脚本本身能力很强(见 cmd),但技能文档的职责是更新前把关、选对参数、处理本地改动这一关键岔路、执行前确认。
前置检查:五项全部通过才能继续
执行 sudo ./cmd update 之前,需要依次确认以下五项(源码中大部分检查脚本自身也会做,提前确认可以让报错更早、定位更准):
| # | 检查项 | 判定方法 | 未通过的后果 |
|---|---|---|---|
| 1 | 已安装 | 项目根存在 vendor/autoload.php |
脚本直接报错 请先执行安装命令(cmd 中 if <a href="https://link.gitcode.com/i/bdbea88dd75ac8d754bb6200d56ded0b" target="_blank"> ! -f "${WORK_DIR}/vendor/autoload.php" ]),此时应改走 [dootask-install 安装流程 |
| 2 | 工作目录 | 必须在项目根目录(存在 cmd、docker-compose.yml、.env.docker) | 脚本以 pwd 作为 WORK_DIR,目录不对会导致所有相对路径与容器探测失败 |
| 3 | 当前分支 / 目标分支 | 默认更新当前分支;需要换分支时用 --branch <分支> |
用户没有明确要换分支时,应确认是否就更新当前分支(本仓库主分支为 pro) |
| 4 | 本地改动 | git status 查看是否有未提交改动 |
这是下文详述的"关键岔路",必须先有决策再执行 |
| 5 | sudo | update 入口先执行 check_sudo,非 root 直接退出 |
必须以 sudo ./cmd update 方式运行(cmd) |
检查通过后,向用户汇报检查结果并确认一次,再实际执行。另外注意:除 electron 和 permission 外,./cmd 的所有子命令都会先执行 check_docker(要求 Docker 已安装、docker-compose/docker compose 为 v2+,见 cmd)和 env_init(从 .env.docker 初始化 .env),因此宿主机必须具备可用的 Docker 环境。
关键岔路:本地有改动时停下,交用户决定
从源码结构看,远程更新模式下脚本会用 git diff --quiet 与 git diff --cached --quiet 双重检测工作区与暂存区是否干净(cmd)。一旦发现本地改动且未带 --force,脚本会交互式询问:
[警告] 检测到本地修改,是否强制更新?[Y/n]
这里的"强制更新"在源码中的真实含义是:
git checkout -f <分支> # 切分支时强制切换(--force 场景)
git reset --hard origin/<分支> # 丢弃所有本地改动,对齐远程
即 --force = 丢弃全部本地改动,属于不可逆操作。因此更新流程文档规定:
- 发现本地有改动 → 停下,把改动清单报告给用户,让用户决定:先提交/暂存改动,还是明确接受强制更新;
- 不要替用户选
--force;只有用户明确说"丢掉改动强制更新"时,才带--force执行; - 一个容易踩的坑:该交互提示在直接回车(空输入)时默认取
Y(cmd 中[[ -z ${confirm_force} ]] && confirm_force="Y")。也就是说如果执行者不留意直接回车,就会进入强制更新路径——这正是要"先停下、向用户报告改动、由用户决定"的原因。
正确姿势示例:检测到 git status 有改动后,先向用户展示改动文件列表,说明"--force 将执行 git reset --hard origin/<分支>,所有未提交的本地修改会永久丢失,请先提交或暂存这些改动,或明确确认强制更新",得到明确答复前不执行。
参数选择:--branch、--force、--local
./cmd update 支持三个参数(与 cmd 脚本帮助信息 一致),选择依据如下表:
| 参数 | 作用 | 何时用 |
|---|---|---|
--branch <分支> |
切到指定分支再更新 | 用户要换分支(如切 dev/pro) |
--force |
强制更新:git checkout -f + git reset --hard |
危险:仅用户明确接受"丢弃本地改动"后 |
--local |
本地更新模式:只备份库 + migrate + 重启,不拉远程代码 |
代码已就位(如手动改过/CI 拉过),只需迁移+重启 |
结合 cmd 的分支处理逻辑,各参数的行为细节可以进一步展开:
--branch <分支>:脚本先检查本地是否已有该分支的远程跟踪配置,缺失时自动补 git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*';若本地无该分支则先 git fetch origin <分支>:<分支> 拉取;最后执行 git checkout <分支>(--force 时为 git checkout -f <分支>)。
--force:跳过 git pull,直接对目标分支执行 git reset --hard origin/<分支>(cmd)。注意它只在"本地有改动且用户已确认"或"切换的分支存在冲突"的场景下使用。
--local:完全跳过 git 相关步骤(不 fetch、不 pull),只做数据库备份 + php artisan migrate + 重启服务(cmd)。适合代码已经就位、只需要补数据库迁移和重启的场景,例如开发者刚在服务器上手动改完代码。
数据库:迁移变动自动备份,重操作前可额外加备
更新对数据的最大风险来自数据库迁移。脚本内置了两道保护,均无需手动干预:
- 远程模式下的自动备份:拉取前,脚本用
git diff --name-only HEAD..origin/<分支>检测database/目录在本地与远程之间是否有变动;只要命中(即远程有新迁移文件),就先调用mysql_snapshot backup备份数据库再继续(cmd)。备份实现是对mariadb容器执行mysqldump后 gzip,落盘到docker/mysql/backup/<库名>_<时间戳>.sql.gz(cmd)。 - 本地模式下的无条件备份:
--local模式进入前固定先执行一次数据库备份(cmd),因为本地模式无法预知代码里有没有新迁移。
在满足以下任一情况时,建议执行前额外提醒用户:本次可能是大版本升级,或用户对数据特别在意。此时可先手动加一次备份:
./cmd mysql backup
备份产物同样落在 docker/mysql/backup/。若更新后发现问题,可用交互式还原命令选择某个备份文件恢复(恢复后脚本会自动再跑一次 migrate 对齐结构):
./cmd mysql recovery
执行与成功后的报告
确认(含本地改动决策)后,按用户选择执行对应命令:
sudo ./cmd update
# 切分支: sudo ./cmd update --branch pro
# 强制(丢改动,用户确认后): sudo ./cmd update --force
# 本地模式: sudo ./cmd update --local
从源码看,迁移完成后的收尾链路是:docker-compose stop php nginx → rm -f php nginx → up -d --remove-orphans 重新拉起全部服务;若 up 失败则兜底执行 down 后再 up -d(cmd)。随后 wait_php_healthy 会轮询 php 容器健康状态(最多约 90 秒),必要时再次兜底拉起 nginx(cmd),最后通过 env_set UPDATE_TIME $(date +%s) 把更新时间戳写入 .env,输出"更新完成"。
执行成功后,向用户报告三项结果:
- 更新到的分支(当前分支或
--branch指定的分支); - 是否做了库备份/迁移(是否命中
database/变动触发的自动备份、备份文件名,migrate是否执行); - 服务是否重启完成(php/nginx 容器是否 healthy/running)。
失败处理:立即停止,原样报告
更新流程文档对失败处理只有一条总原则——任何步骤失败立即停止,原样报告错误;不自动重试、不自动跳过、不要因为 git pull 失败就自己改成 --force。常见失败与对应处理如下(提示文案均出自 cmd 的中英文对照表):
| 报错 | 原因 | 处理 |
|---|---|---|
请先执行安装命令 |
未执行过 ./cmd install,vendor/autoload.php 不存在 |
停止更新,引导用户改走 dootask-install 安装流程 |
代码拉取失败,可能存在冲突,请使用 --force 参数 |
git pull --ff-only 失败,本地与远程出现分叉 |
报告给用户,由用户决定是先处理冲突,还是明确接受 --force 丢改动(cmd) |
获取远程更新失败 |
git fetch --all 失败 |
原样报告,通常是网络或远端仓库问题 |
重启服务失败 |
容器重启异常 | 脚本自身会先尝试 down 后重起;若仍失败,报告交用户(cmd) |
请使用 sudo 运行此脚本 |
未以 root 运行 | 改用 sudo ./cmd update |
禁止项与 Red Flags
更新流程文档用一张对照表固化了"错误做法 → 正确做法",这是整个流程的底线约束:
| 错误做法 | 正确做法 |
|---|---|
检测到本地改动就自动 --force |
停下,报告改动,交用户决定 |
git pull 失败就自动改用 --force |
报告冲突,交用户 |
| 不加 sudo | sudo ./cmd update |
| 未装就更新 | 先走 dootask-install |
| 失败后自动重试/跳过 | 立即停止 |
与之配套的是"Red Flags"清单——执行更新时,一旦出现以下念头应立即停下:
- "有点本地改动,强制更新一下就好了" → 不,
--force会丢改动,必须用户拍板; - "拉取冲突了,我 reset 一下" → 不,交用户决定;
- "已经装过了吧,直接更新" → 不,先确认
vendor/autoload.php存在。
小结
DooTask 的更新流程把"重活"交给 cmd 脚本(git、composer、migrate、容器重启、自动备份一应俱全),把"判断"交给更新操作者:五项前置检查、一次执行前确认、本地改动的用户决策、失败即停。掌握这套流程后,你可以:
- 对生产实例执行安全更新:
sudo ./cmd update(含自动备份迁移); - 按需切分支:
sudo ./cmd update --branch <分支>; - 代码已就位时只补迁移和重启:
sudo ./cmd update --local; - 重大更新前用
./cmd mysql backup加一道备份、出问题时用./cmd mysql recovery回滚。
相关资源:更新技能文档 SKILL.md、安装流程 dootask-install、数据备份流程 dootask-backup、管理脚本 cmd、编排文件 docker-compose.yml。