diff --git a/docs/en-US/deployment.md b/docs/en-US/deployment.md index f926033d..0e9d82cc 100644 --- a/docs/en-US/deployment.md +++ b/docs/en-US/deployment.md @@ -99,7 +99,7 @@ mirror are all just this one setting; empty means the official repository. An in from a Release archive (no `.git`) can be connected from the same page (no configuration needed - the default official repository applies): the preview lists the files the target would replace and the operator content that is kept, replaced files are backed up under -`.update-backup//overwritten/`, and confirming turns the directory into a normal +`.update-backup//overwritten/`, and confirming turns the directory into a normal installation with one-click updates. `upgrade.sh` still works and is now a thin shell over that implementation, with two paths decided by @@ -136,7 +136,7 @@ quick upgrades without compatibility risk; in production still back up first. Py The one-click update leaves its own rollback point: `./cyberstrike-ai -update-rollback`, or the button on the page, goes back to the commit before that update and puts `cyberstrike-ai.prev` back. It runs only while HEAD is still the commit that update wrote, so work done afterwards is not discarded. -Content that was put aside also survives under `.update-backup//`. +Content that was put aside also survives under `.update-backup//`. Beyond that, roll back together: diff --git a/docs/en-US/developer-guide.md b/docs/en-US/developer-guide.md index b365e8f1..f503e9ce 100644 --- a/docs/en-US/developer-guide.md +++ b/docs/en-US/developer-guide.md @@ -86,7 +86,7 @@ An installation that started as an unpacked Release archive has no `.git`; it ca the same page (the default official repository works without any configuration): the preview fetches in a throwaway repository and first lists **the local files the target would replace** and **the operator content that will be kept**; on confirm the directory becomes a git work tree (`git init`, origin added, the target branch checked -out), every replaced file is kept under `.update-backup//overwritten/`, operator content +out), every replaced file is kept under `.update-backup//overwritten/`, operator content is stashed and restored as usual, and the binary is rebuilt. From then on it is a normal installation with one-click update and rollback - but note there is no git history before the connection, so the connection itself has no earlier commit to roll back to; the first real update @@ -119,11 +119,15 @@ else -> 400). Other failures (`merge_failed`, `build_failed`, `swap_failed`) car compiler's own output; a failed build leaves the previous binary in place while the source has already moved and the state file is written, so it stays rollback-able. +Check, update, adoption, rollback and restart share an operating-system file lock in the install +directory (`.update-lock`). Wait for the other operation when `update_busy` is reported. The system +releases the lock when its process exits; do not delete the lock file to bypass it. + ### How operator content survives `roles/ skills/ tools/ agents/ knowledge_base/ data/ log/ venv/ config.yaml .env` belong to the **operator** (`update.Protected` is the judge). Before the merge, only those of those paths the -update actually writes to are copied into `.update-backup//` and removed from the tree - +update actually writes to are copied into `.update-backup//` and removed from the tree - your extra directories that upstream never touched are not disturbed - and they are put back afterwards, which is what "update the code, keep my work" means. The result names every file in `keptContent` instead of quietly dropping it: where both you and upstream changed the same file, the @@ -141,6 +145,11 @@ not the one the record pairs with that commit (`binary_changed`), **HEAD has mov (`moved_since_update` - a rollback undoes the update, not the work done after it), or source is modified locally (`local_source_edits`). +Rollback writes `.update-rollback-pending.json` before changing source or binaries. Run rollback +again after an interruption to finish recovery; the console reports `rollbackPending` and pauses new +updates. Keep the recovery record, backup directory and `.prev` file. If recorded binary hashes do not +match the files on disk, recovery refuses to overwrite them. + ### The two restart cases The process only stands down (graceful `Shutdown`, then exit 0) when the request explicitly says diff --git a/docs/zh-CN/deployment.md b/docs/zh-CN/deployment.md index 6203bd96..1cd0c344 100644 --- a/docs/zh-CN/deployment.md +++ b/docs/zh-CN/deployment.md @@ -196,7 +196,7 @@ SQLite 热备份时最好先停止服务,或至少复制 `*.db`、`*.db-wal` 更新源是 `config.yaml` `update` 段里的**一个仓库地址**(`repo`),也可以直接在控制台「一键更新」页填写保存: 想跟随官方仓库、自己的二开、别人的二开或镜像,改的都是这一处;不写就是官方仓库。**用 Release 包解压安装、 目录里没有 git 的**,在同一页点「预览并接入」(不配置也能用默认的官方仓库):预览先列出会被目标版本替换的 -文件与会保留的运维者内容,被替换的都会备份在 `.update-backup/<时间戳>/overwritten/`,确认后目录成为可一键 +文件与会保留的运维者内容,被替换的都会备份在 `.update-backup/<唯一备份目录>/overwritten/`,确认后目录成为可一键 更新的正常安装。 `upgrade.sh` 仍然可用,它现在只是这条实现的薄壳,并按安装形态分两条路: @@ -227,7 +227,7 @@ Python 依赖方面:`venv/` 由 `run.sh` 自动创建和管理,保留它就 一键更新自带回滚点:`./cyberstrike-ai -update-rollback` 或页面上的回滚按钮,会撤到那次更新前的提交并把 `cyberstrike-ai.prev` 换回原位;它只在"HEAD 仍是那次更新写下的提交"时才执行,之后的工作不会被顺手抹掉。 -被暂存过的内容也留在 `.update-backup/<时间戳>/` 里。 +被暂存过的内容也留在 `.update-backup/<唯一备份目录>/` 里。 除此之外,回滚时同时恢复: diff --git a/docs/zh-CN/developer-guide.md b/docs/zh-CN/developer-guide.md index 5a3e393e..cb46a58d 100644 --- a/docs/zh-CN/developer-guide.md +++ b/docs/zh-CN/developer-guide.md @@ -86,7 +86,7 @@ update: 一开始用 Release 包解压安装、目录里没有 `.git` 的,可在同一页执行「预览并接入」(不配置也能用默认的官方仓库): 预览在临时仓库里 fetch,先列出**会被目标版本替换的本机文件**与**会保留的运维者内容**;确认后目录接入 成为 git 工作树(`git init` + 添加 origin + 落地目标分支),被替换的文件全部留底在 -`.update-backup/<时间戳>/overwritten/`,运维者内容照旧先暂存再放回,随后重编译二进制。 +`.update-backup/<唯一备份目录>/overwritten/`,运维者内容照旧先暂存再放回,随后重编译二进制。 接入后它就是正常安装,一键更新与回滚都可用了;注意接入前没有 git 历史,因此「接入」本身没有可回滚的 上一提交,第一个回滚点由接入后的第一次更新写下。 @@ -114,11 +114,14 @@ update: `no_binary` → 409,其余 → 400)。其他失败(`merge_failed`、`build_failed`、`swap_failed`)带着 git/go 的原始输出; 编译失败时二进制保持原版本,而源码已移动、状态文件已写好,因此仍然可以回滚。 +检查、更新、接入、回滚和重启共用安装目录的系统文件锁(`.update-lock`)。遇到 +`update_busy` 时等待另一操作完成;进程退出后锁由系统释放,不要删除锁文件来绕过互斥。 + ### 运维者的内容如何被保留 `roles/ skills/ tools/ agents/ knowledge_base/ data/ log/ venv/ config.yaml .env` 是**运维者的内容**, 判定在 `update.Protected`。合并前,只有本次更新**确实会写到**的那些路径被复制到 -`.update-backup/<时间戳>/` 并从工作树移开(你多出来的、上游没动的目录连碰都不碰),合并后再原样放回, +`.update-backup/<唯一备份目录>/` 并从工作树移开(你多出来的、上游没动的目录连碰都不碰),合并后再原样放回, 于是"更新代码、留下我的工作"成立。结果里的 `keptContent` 逐个点名被保留的文件——绝不静默丢弃: 当上游与你在同一个文件上都有改动,落地的是你的版本,而这件事写在结果里。 **回滚遵循同一条规则**:`reset --hard` 会把受保护文件上现存的本地改动先暂存、重置后放回(更新前改的 @@ -134,6 +137,10 @@ update: **HEAD 自那次更新之后又动过**(`moved_since_update`:回滚只该撤到更新前,不该顺手抹掉之后的工作)、 以及存在本地源码改动(`local_source_edits`)。 +回滚开始前会写入 `.update-rollback-pending.json`。中断后再次执行回滚会继续恢复;页面的 +`rollbackPending` 提示会暂停新更新。保留恢复记录、备份目录和 `.prev`,不要手动删除它们。 +如果记录中的二进制哈希无法与现场对应,会拒绝覆盖并保留现场。 + ### 重启的两种情形 进程只有在请求显式带 `restart: true`、且启动时装配了重启钩子时才会优雅退出(`Shutdown` 后退出码 0)。