feat(update): 发布双链路更新进度与线上验收审计
Test deployment public URL (Linux) / test-linux (push) Canceled after 0s
Test start.ps1 (Windows) / test-windows (push) Canceled after 0s

This commit is contained in:
2026-10-02 19:46:39 +08:00
parent 89cc554c04
commit eebc6d1453
52 changed files with 13799 additions and 21 deletions
+7
View File
@@ -268,6 +268,13 @@ UPDATE_HEALTH_TIMEOUT_MS=300000
# 私有镜像仓库登录信息;执行 docker login 后通常位于此处。
DOCKER_CONFIG_FILE=/root/.docker/config.json
SYSTEM_UPDATE_INTERNAL_TOKEN=
WATCHTOWER_IMAGE=registry.cn-shanghai.aliyuncs.com/comfy-ai/watchtower-aliyun:1.22.3
# 更新接口及事件接口仅供容器内网访问,由初始化脚本生成独立令牌。
WATCHTOWER_API_KEY=
WATCHTOWER_HTTP_API_EVENTS_TOKEN=
WATCHTOWER_HTTP_API_PERIODIC_POLLS=true
# 留空时使用正式版本服务;测试环境可指向候选版本服务。
APP_VERSION_CHECK_BASE_URL=
EASYAI_DEPLOYMENT_SCHEMA_VERSION=2
# ========== 16. OSS 配置(可选,PDF 解析图片上传) ==========
+8 -2
View File
@@ -181,13 +181,13 @@ update.sh 脚本用于自动更新 EasyAI 应用,包含以下功能:
- 自动拉取最新镜像并重启服务
- 等待主服务、Web 和 WebSocket 网关恢复可用后才报告成功;拉取失败、启动失败或恢复超时会明确报告失败
新安装默认启用可查询进度的在线更新模式。管理员在页面发起更新后,即使主服务重启导致原请求断开,页面也会根据任务编号继续查询最终结果。存量部署执行新版 `update.sh` / `update.ps1` 时会自动补齐新模式所需的缺失配置;已经显式设置 `SYSTEM_UPDATE_MODE=legacy_watchtower` 的环境仍保留原模式。新模式只管理 `easyai-server`、`easyai-web` 和 `ws-gateway`,数据库、Redis、RabbitMQ 等基础服务仍按原部署流程维护。
新安装默认启用可查询进度的在线更新模式。管理员在页面发起更新后,即使主服务重启导致原请求断开,页面也会根据任务编号继续查询最终结果。存量部署如果没有 `easyai-updater` 容器,更新脚本会保留 Watchtower 模式;已经显式设置 `SYSTEM_UPDATE_MODE=legacy_watchtower` 的环境也保持该模式。只有新安装默认启用 updater。新模式只管理 `easyai-server`、`easyai-web` 和 `ws-gateway`,数据库、Redis、RabbitMQ 等基础服务仍按原部署流程维护。
更新执行器 `easyai-updater` 由 Watchtower 单独守护,默认每 24 小时检查并更新自身。它的任务状态保存在宿主机 `data/update-state` 中,自身更新或意外重启后会继续恢复未完成任务;Watchtower 不再接管三个核心服务,因此页面任务结果不会与后台静默替换相互冲突。
受控更新使用仓库内的 `system-update/release-manifest.json` 作为稳定发布清单,Compose 会以只读方式挂载给 updater。发布新正式镜像时必须同步更新清单中的不可变摘要、镜像大小和版本标识;服务器执行 `update.sh` 拉取部署仓库后即可获得清单,不需要手工向运行目录复制文件。
存量部署无需手工编辑 `.env`。更新脚本会从 `scripts/update-env.d/010-managed-system-update.env` 应用以下缺省值:
新安装或已有 updater 的部署无需手工编辑 `.env`。更新脚本会从 `scripts/update-env.d/010-managed-system-update.env` 应用以下缺省值:
```dotenv
SYSTEM_UPDATE_MODE=managed
@@ -202,6 +202,12 @@ EASYAI_DEPLOYMENT_SCHEMA_VERSION=2
`SYSTEM_UPDATE_INTERNAL_TOKEN` 由 `start.sh` / `update.sh` 自动生成并持久化,不需要手工填写。`UPDATE_HEALTH_TIMEOUT_MS` 控制每个核心服务替换后的恢复等待时间,默认 300000 毫秒。私有镜像仓库需要先执行 `docker login`;更新器与仅负责其自更新的 Watchtower 都会只读挂载 `DOCKER_CONFIG_FILE` 指向的 Docker 登录配置。切换后执行一次 `./update.sh`,确认 `easyai-updater` 以及三个核心服务均处于可用状态。
Watchtower 固定使用 `1.22.3`。内网启用更新、事件、历史、状态和健康接口,不映射主机端口。`WATCHTOWER_API_KEY` 与 `WATCHTOWER_HTTP_API_EVENTS_TOKEN` 是两个独立随机令牌,由 Bash 和 PowerShell 安全初始化流程补齐;浏览器只访问后端,不接触这些令牌。managed 模式只扫描 updater 并定期检查自身;Watchtower 模式关闭定期扫描,只在用户或系统明确提交更新时更新核心服务,避免任务与定时扫描无法对应。
两个页面入口共用计时和步骤窗口。updater 保存阶段完成时间到 `data/update-state/jobs`;无 updater 时后端保存任务和单任务锁到 MongoDB。刷新或重新登录后按站点与账号恢复任务查询。请求超时或服务断开不会自动创建另一项更新,旧接口无法读取的阶段会明确提示。后端重启时页面保留进度,提醒在 3–5 分钟后刷新检查版本;只有确认目标版本和服务恢复才显示完成。
正式版本检测默认仍使用原来的 `https://openapi.51easyai.com`。测试验收可在后端设置 `APP_VERSION_CHECK_BASE_URL`,该地址应提供与正式地址相同格式的 `/app/version` 响应;正式发布前必须撤销测试覆盖。候选镜像用版本标签发布,验收后才将相同摘要提升为 `latest`。源码、镜像、环境迁移、三种实际更新验收和回退证据记录在 `system-update/releases/`,其中不得包含令牌或登录凭据。
以后新增必需环境变量时,在 `scripts/update-env.d/` 增加新的编号 `.env` 文件即可,不需要继续把写入逻辑堆进 `update.sh`。迁移文件支持 `KEY=value`(仅在缺失或空值时写入)、`KEY+=item`(确保逗号列表包含该项),以及 `@skip-if KEY=value`(明确保留某种旧模式);所有迁移每次都会执行并保持幂等。迁移失败会在拉取镜像前终止,避免使用半完成配置启动服务。
### 使用步骤
+8 -2
View File
@@ -294,7 +294,7 @@ services:
easyai:
ipv4_address: 172.21.0.12
watchtower:
image: registry.cn-shanghai.aliyuncs.com/comfy-ai/watchtower-aliyun:latest
image: ${WATCHTOWER_IMAGE:-registry.cn-shanghai.aliyuncs.com/comfy-ai/watchtower-aliyun:1.22.3}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ${DOCKER_CONFIG_FILE:-/root/.docker/config.json}:/config.json:ro
@@ -303,6 +303,11 @@ services:
environment:
- WATCHTOWER_POLL_INTERVAL=${SYSTEM_UPDATER_UPDATE_INTERVAL_SECONDS:-86400}
- WATCHTOWER_CLEANUP=${WATCHTOWER_CLEANUP:-false}
- WATCHTOWER_HTTP_API_HOST=0.0.0.0
- WATCHTOWER_HTTP_API_ENDPOINTS=health,update,metrics,events,history
- WATCHTOWER_HTTP_API_TOKEN=${WATCHTOWER_API_KEY}
- WATCHTOWER_HTTP_API_EVENTS_TOKEN=${WATCHTOWER_HTTP_API_EVENTS_TOKEN}
- WATCHTOWER_HTTP_API_PERIODIC_POLLS=${WATCHTOWER_HTTP_API_PERIODIC_POLLS:-false}
restart: always
labels:
- "com.centurylinklabs.watchtower.enable=false"
@@ -347,7 +352,8 @@ services:
tmpfs:
- /tmp:mode=1777
networks:
- easyai
easyai:
ipv4_address: 172.21.0.31
healthcheck:
test: ["CMD-SHELL", "node -e \"require('http').get('http://127.0.0.1:3015/health', (res) => { res.resume(); process.exit(res.statusCode === 200 ? 0 : 1); }).on('error', () => process.exit(1))\""]
interval: 10s
+3 -1
View File
@@ -58,7 +58,9 @@ function Initialize-SecurityEnv {
$definitions = @(
@{ Key = "CONFIG_JWT_SECRET"; Legacy = "this is a very secret secret"; MinLength = 32 },
@{ Key = "WS_AUTH_WS_TICKET_SECRET"; Legacy = ""; MinLength = 32 },
@{ Key = "SYSTEM_UPDATE_INTERNAL_TOKEN"; Legacy = ""; MinLength = 32 }
@{ Key = "SYSTEM_UPDATE_INTERNAL_TOKEN"; Legacy = ""; MinLength = 32 },
@{ Key = "WATCHTOWER_API_KEY"; Legacy = ""; MinLength = 32 },
@{ Key = "WATCHTOWER_HTTP_API_EVENTS_TOKEN"; Legacy = ""; MinLength = 32 }
)
if ($Mode -eq "new") {
$definitions += @(
+2
View File
@@ -130,6 +130,8 @@ init_security_env() {
ensure_security_secret "$file" "CONFIG_JWT_SECRET" "this is a very secret secret"
ensure_security_secret "$file" "WS_AUTH_WS_TICKET_SECRET"
ensure_security_secret "$file" "SYSTEM_UPDATE_INTERNAL_TOKEN"
ensure_security_secret "$file" "WATCHTOWER_API_KEY"
ensure_security_secret "$file" "WATCHTOWER_HTTP_API_EVENTS_TOKEN"
ensure_env_list_item "$file" "WS_AUTH_METHODS" "ws_ticket" "none,bearer"
if [ "$mode" = "new" ]; then
ensure_security_secret "$file" "CONFIG_SECURITY_CONFIG_ENCRYPTION_KEY"
+8 -2
View File
@@ -145,7 +145,12 @@ for service in ("mongo", "rabbitmq", "video-edit", "dozzle", "agent-memory"):
watchtower = services["watchtower"]
command = " ".join(watchtower.get("command") or [])
assert "http-api-update" not in command, command
assert "WATCHTOWER_HTTP_API_TOKEN" not in (watchtower.get("environment") or {}), watchtower.get("environment")
watchtower_env = watchtower.get("environment") or {}
assert watchtower.get("ports") in (None, []), watchtower.get("ports")
assert set(watchtower_env["WATCHTOWER_HTTP_API_ENDPOINTS"].split(",")) == {"health", "update", "metrics", "events", "history"}
assert len(watchtower_env["WATCHTOWER_HTTP_API_TOKEN"]) >= 32
assert len(watchtower_env["WATCHTOWER_HTTP_API_EVENTS_TOKEN"]) >= 32
assert watchtower_env["WATCHTOWER_HTTP_API_TOKEN"] != watchtower_env["WATCHTOWER_HTTP_API_EVENTS_TOKEN"]
watchtower_mounts = {(item.get("source"), item.get("target")) for item in watchtower.get("volumes") or []}
assert any(target == "/config.json" for _, target in watchtower_mounts), watchtower_mounts
assert services["dozzle"]["environment"]["DOZZLE_ENABLE_DOWNLOAD"] == "false", services["dozzle"]["environment"]
@@ -162,6 +167,7 @@ watchtower = services["watchtower"]
assert updater.get("ports") in (None, []), updater.get("ports")
assert updater.get("read_only") is True, updater.get("read_only")
assert updater["networks"]["easyai"]["ipv4_address"] == "172.21.0.31", updater["networks"]
assert set(updater.get("cap_drop") or []) == {"ALL"}, updater.get("cap_drop")
assert "no-new-privileges:true" in (updater.get("security_opt") or []), updater.get("security_opt")
mounts = {(item.get("source"), item.get("target")) for item in updater.get("volumes") or []}
@@ -201,7 +207,7 @@ assert manifest["channel"] == "stable"
assert manifest["platform"] == "linux/amd64"
assert manifest["managedUpdateAllowed"] is True
assert manifest["rollbackSafe"] is True
assert manifest["minimumUpdaterVersion"] == "1.0.0"
assert manifest["minimumUpdaterVersion"] == "1.1.0"
assert manifest["minimumDeploymentSchemaVersion"] == 2
assert int(manifest["requiredFreeBytes"]) > 0
components = {item["key"]: item for item in manifest["components"]}
+7 -1
View File
@@ -24,7 +24,12 @@ grep -qx 'EXISTING_VALUE=keep-me' "$LEGACY_ENV_FILE"
grep -qx 'SYSTEM_UPDATE_MODE=legacy_watchtower' "$LEGACY_ENV_FILE"
grep -qx 'COMPOSE_PROFILES=customer-profile' "$LEGACY_ENV_FILE"
grep -qx 'WATCHTOWER_CLEANUP=' "$LEGACY_ENV_FILE"
[ "$(wc -l < "$LEGACY_ENV_FILE" | tr -d ' ')" = "4" ]
grep -qx 'WATCHTOWER_HTTP_API_PERIODIC_POLLS=false' "$LEGACY_ENV_FILE"
grep -qx 'WATCHTOWER_UPDATE_TARGET=easyai-server easyai-wsgateway easyai-web' "$LEGACY_ENV_FILE"
grep -qx 'WATCHTOWER_IMAGE=registry.cn-shanghai.aliyuncs.com/comfy-ai/watchtower-aliyun:1.22.3' "$LEGACY_ENV_FILE"
cp "$LEGACY_ENV_FILE" "$TMP_DIR/legacy-first-run.env"
bash "$PROJECT_ROOT/scripts/apply-update-env-migrations.sh" "$LEGACY_ENV_FILE" "$PROJECT_ROOT" >/dev/null
cmp "$LEGACY_ENV_FILE" "$TMP_DIR/legacy-first-run.env"
ENV_FILE="$TMP_DIR/.env"
cat > "$ENV_FILE" <<'EOF'
@@ -43,6 +48,7 @@ read_value() {
[ "$(read_value SYSTEM_UPDATE_MODE)" = "managed" ]
[ "$(read_value COMPOSE_PROFILES)" = "customer-profile,managed-update" ]
[ "$(read_value WATCHTOWER_CLEANUP)" = "true" ]
[ "$(read_value WATCHTOWER_HTTP_API_PERIODIC_POLLS)" = "true" ]
[ "$(read_value WATCHTOWER_CORE_UPDATE_ENABLED)" = "false" ]
[ "$(read_value WATCHTOWER_UPDATE_TARGET)" = "easyai-updater" ]
[ "$(read_value SYSTEM_UPDATER_UPDATE_INTERVAL_SECONDS)" = "86400" ]
@@ -0,0 +1,2 @@
# 两种更新模式均启用内网接口;安全令牌由安全初始化脚本生成。
WATCHTOWER_IMAGE=registry.cn-shanghai.aliyuncs.com/comfy-ai/watchtower-aliyun:1.22.3
@@ -0,0 +1,2 @@
@skip-if SYSTEM_UPDATE_MODE=legacy_watchtower
WATCHTOWER_HTTP_API_PERIODIC_POLLS=true
@@ -0,0 +1,3 @@
@skip-if SYSTEM_UPDATE_MODE=managed
WATCHTOWER_HTTP_API_PERIODIC_POLLS=false
WATCHTOWER_UPDATE_TARGET=easyai-server easyai-wsgateway easyai-web
+13 -13
View File
@@ -1,37 +1,37 @@
{
"schemaVersion": 1,
"channel": "stable",
"releaseId": "managed-update-20260927",
"productVersion": "3.18.0.20260927",
"releaseId": "online-update-progress-20261002",
"productVersion": "3.22.0.20261002.2",
"platform": "linux/amd64",
"publishedAt": "2026-09-27T11:15:00+08:00",
"publishedAt": "2026-10-02T19:00:00+08:00",
"managedUpdateAllowed": true,
"scheduledUpdateAllowed": true,
"rollbackSafe": true,
"minimumUpdaterVersion": "1.0.0",
"minimumUpdaterVersion": "1.1.0",
"minimumDeploymentSchemaVersion": 2,
"requiredFreeBytes": 8589934592,
"components": [
{
"key": "server-main",
"image": "registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server",
"digest": "sha256:38cbe3fdb151b54dd729071e1368ad0670710a5b0160aa5fdbc486737bf5b2ef",
"imageSizeBytes": 2723428239,
"buildId": "db43ef28"
"digest": "sha256:6825def1b7bf252eee477b34d3ddc0317e691f0787ea3f300793c4d272a3b491",
"buildId": "e3f12240",
"imageSizeBytes": 2734540568
},
{
"key": "ws-gateway",
"image": "registry.cn-shanghai.aliyuncs.com/easyaigc/wsgateway",
"digest": "sha256:661eb2dc5ca6e458e00b8c2e2f068c2a1773694a3017982ecb1aa42de677468e",
"imageSizeBytes": 1982899440,
"buildId": "db43ef28"
"digest": "sha256:a2b5fb67384c7f0eca7c7a768b5deacb4c0d2195dfbeb6e16ef40d6dc097bf25",
"buildId": "retained-formal-image",
"imageSizeBytes": 1982472178
},
{
"key": "web",
"image": "registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai",
"digest": "sha256:bc82b10e08886904b2ef4a5c2fe7727b1dac9114c7493b4f2acc0b1a8ef5e29c",
"imageSizeBytes": 1038564864,
"buildId": "188b0ee9"
"digest": "sha256:a287db622d13a706725520d3a9864f986b8854630d198fce3373a8e7f1cfe3b0",
"buildId": "56e51ea5",
"imageSizeBytes": 1034496829
}
]
}
@@ -0,0 +1,171 @@
{
"status": "released",
"productVersion": "3.22.0.20261002.2",
"source": {
"backend": "e3f12240c4a186a55ed2bc9a5afcb71902004a55",
"backendBaseline": "179d4dae8c9400a773f7f9d92a397039c39ef572",
"web": "56e51ea55dcc281ade91bd0e6438c5fc6a617c25",
"webBaseline": "340c47f820a039d409ffcc4f464829acc973a171"
},
"images": {
"server-main": "sha256:6825def1b7bf252eee477b34d3ddc0317e691f0787ea3f300793c4d272a3b491",
"watchtower": "sha256:6afd62a208687bb4840b3b8131e1461dee7033989e14e28b6d69082839eaf410",
"updater": "sha256:bbaddb598618ceaaf6696f6c5fe19daf564d983039b70d36a4196cc4aaca385a",
"web": "sha256:a287db622d13a706725520d3a9864f986b8854630d198fce3373a8e7f1cfe3b0",
"ws-gateway": "sha256:a2b5fb67384c7f0eca7c7a768b5deacb4c0d2195dfbeb6e16ef40d6dc097bf25"
},
"watchtowerVersion": "1.22.3",
"acceptance": {
"oldWatchtower": {
"status": "passed",
"beforeVersion": "3.22.0.20261002.1",
"afterVersion": "3.22.0.20261002.2",
"uiElapsedSeconds": 61,
"details": [
"old backend jobApi=false",
"real browser confirmation",
"elapsed32s and restart message screenshot",
"refresh resumes original update",
"core images and health verified"
],
"evidenceDirectory": "online-update-progress-20261002/acceptance"
},
"newWatchtower": {
"status": "passed",
"beforeVersion": "3.22.0.20261002.1",
"afterVersion": "3.22.0.20261002.2",
"uiElapsedSeconds": 43,
"jobId": "wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"details": [
"POST async update accepted and scan started",
"SSE endpoint observed through public browser",
"Mongo job stages persisted",
"history confirms completion after main restart",
"refresh resumes original task",
"core images and health verified"
],
"evidenceDirectory": "online-update-progress-20261002/acceptance"
},
"managedUpdater": {
"status": "passed",
"beforeVersion": "3.22.0.20261002.1",
"afterVersion": "3.22.0.20261002.2",
"uiElapsedSeconds": 53,
"jobId": "154b6781-dd24-434d-94a5-4bf59122c0bb",
"failureRecoveryJobId": "d0f02915-1cbe-4063-94c6-e9d749aaf2d7",
"failureRecoveryUiElapsedSeconds": 154,
"details": [
"all six stages persisted with timestamps",
"real browser confirmation and refresh recovery",
"main service restart queries recover",
"updater restart during intentional HTTP503 health failure resumes same task",
"failed candidate rolled back to A image ids and A version",
"new job then updates real formal B digests and healthy services"
],
"evidenceDirectory": "online-update-progress-20261002/acceptance",
"failureFixtureDigest": "sha256:deddc80bee5a4123dc62d1598a34660ad4f54b8f50f3968fbf4b1844b058e11f"
}
},
"production": {
"host": "101.200.186.210",
"mode": "legacy_watchtower",
"status": "passed",
"beforeVersion": "3.22.0.20260930.3",
"afterVersion": "3.22.0.20261002.2",
"backupPath": "/home/admin/easyai/data/deployment-backups/online-update-progress-20261002",
"iceConfigUnchangedSha256": "1427d181c5d405278e7b9b099379679a91d408108b6737c6cff2ed978f88acc1",
"health": {
"main": "ok",
"web": "ok",
"gateway": "ok",
"watchtower": "ok",
"coreRestarts": 0
},
"interfaces": {
"eventsWithUpdateToken": 401,
"eventsWithIndependentToken": 200,
"history": 200,
"statusBeforeFirstScan": 204,
"livez": 200,
"readyz": 200,
"startupz": 200,
"hostPortPublished": false
},
"publicLoginPage": "passed",
"updaterInstalled": false
},
"testBackup": "/root/easyai/update-backups/online-update-progress-20261002",
"validation": [
{
"check": "frontend real mounted components and full 30s HTTP regression",
"passed": true,
"durationSeconds": 49.42
},
{
"check": "MongoDB 4.4 and real HTTP/SSE task integration plus existing backend tests",
"passed": true,
"durationSeconds": 2.21
},
{
"check": "backend production compile",
"passed": true,
"durationSeconds": 21.02
},
{
"check": "frontend production build",
"passed": true,
"durationSeconds": 441.82
},
{
"check": "Bash and PowerShell security initialization and migration",
"passed": true
},
{
"check": "bash scripts/test-security-env.sh",
"exitCode": 0,
"durationSeconds": 0.54
},
{
"check": "bash scripts/test-update-env-migrations.sh",
"exitCode": 0,
"durationSeconds": 0.41
},
{
"check": "bash scripts/test-deployment-public-url.sh",
"exitCode": 0,
"durationSeconds": 2.03
},
{
"check": "pwsh -NoProfile -File scripts/Test-SecurityEnv.ps1",
"exitCode": 0,
"durationSeconds": 0.57
},
{
"check": "python3 scripts/test-start-ps1-env.py",
"exitCode": 0,
"durationSeconds": 0.02
}
],
"rollback": "Restore recorded immutable core image digests and Compose backup, preserve business volumes and data/update-state; synchronize Watchtower token configuration before restoring its image. Do not re-submit unresolved update jobs.",
"latestPromotion": {
"status": "passed",
"allDigestsMatchCandidate": true
},
"testRestore": {
"status": "passed",
"configFilesUnchanged": true,
"version": "3.22.0.20260930.3",
"businessVolumesAndUpdateStatePreserved": true
},
"runtimeBase": {
"server-main": "registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server@sha256:8493e5a4c82f6564d86925f4ab37061f6e6e3d9a6bb88db5fe5d25d3a7b7835b",
"dependenciesMatchFormalBaseline": true
},
"versionCatalog": {
"defaultAddress": "https://openapi.51easyai.com",
"observedVersion": "3.22.0.20260930.3",
"status": "awaiting_user_scope_confirmation",
"reason": "Version service is on a different production host from the explicitly requested 101.200.186.210"
},
"releasedAt": "2026-10-02T19:45:00+08:00"
}
@@ -0,0 +1,40 @@
# 在线更新发布审计:2026-10-02
发布版本:`3.22.0.20261002.2`;Watchtower:`1.22.3`;updater:`1.1.0`。
完整源码提交、不可变镜像摘要、验证耗时和部署状态见上级 `online-update-progress-20261002.json`。前后端只追加本次更新改造,发布基线分别为 backend `179d4dae`、web `340c47f8`;现有 Workbench 工作区未参与镜像构建。
## 原始问题证据
用户原更新请求于 16:55:28 被 Watchtower 接收,前端在 16:55:58 收到旧接口的 30 秒超时包装失败,实际更新在 16:57:12 完成。旧界面因此误报“更新请求未成功提交”。修复前使用未修改的旧后端请求链路复现 30 秒超时,修复后的真实 HTTP 和组件挂载用例覆盖同一边界。
## 浏览器实际更新验收
| 链路 | 版本变化 | 页面耗时 | 结果 |
| --- | --- | --- | --- |
| 旧 Watchtower,无 updater,旧后端接口 | `.1` → `.2` | 61 秒 | 32 秒时仍计时并提示重启;刷新恢复并核对目标版本 |
| Watchtower 1.22.3,无 updater | `.1` → `.2` | 43 秒 | 异步接收、开始事件、SSE、持久任务及重启后的历史确认 |
| updater 1.1.0 | `.1` → `.2` | 53 秒 | 六个阶段完成时间、主服务重启及刷新后继续查询 |
| updater 健康故障与重启恢复 | `.1` → 故障候选 → `.1` | 154 秒 | 实际 HTTP 503、updater 重启恢复同一任务、回退原镜像并恢复健康 |
每项均从实际浏览器确认按钮开始,后端、前端镜像发生实际替换,最终版本及核心服务健康均核对。`acceptance/` 包含截图、任务、事件时间线和镜像证据。首次故障镜像仅改入口命令,因 updater 保留原启动配置而未触发失败;最终 `fault-fixture/` 通过替换应用启动文件真实返回 503,此镜像未提升为正式标签。
测试站配置文件与备份逐字节一致,已恢复原版本 `3.22.0.20260930.3` 和原镜像;业务数据、`data/update-state` 及新增任务记录保留。测试专用 Compose 使用明确的 updater 与版本检测服务地址,避免动态地址占用业务服务预留地址。
## 生产部署
`101.200.186.210` 已部署正式候选的同一摘要,保留 `legacy_watchtower` 模式且未安装 updater。公网版本接口确认 `.2`,登录页面实际渲染,后端、前端、网关和 Watchtower 健康,核心服务重启次数为 0。事件与更新令牌独立,事件接口使用更新令牌返回 401,正确事件令牌返回 200;Watchtower 不映射主机端口。
原端口、业务卷和 `docker-compose.ice-v1.yml` 保留,ICE 文件 SHA256 为 `1427d181c5d405278e7b9b099379679a91d408108b6737c6cff2ed978f88acc1`。线上没有 Docker 登录配置文件,因此省略该配置的绑定挂载。只合并 Watchtower 配置及核心健康检查;部署脚本与迁移在本仓库正式发布。
生产备份:`/home/admin/easyai/data/deployment-backups/online-update-progress-20261002`,配置和容器完整检查结果只在服务器以权限 600 保存。公开审计省略认证信息。
正式版本检测仍使用 `https://openapi.51easyai.com`,此服务在另一台服务器,当前仍报告旧版本;同步其版本信息正在等待用户确认范围。指定的生产主机已完成部署,未通过测试版本检测地址覆盖正式配置。
## 回退
先恢复备份 `.env` 和 `docker-compose.yml`,保持 ICE 文件、业务卷和更新状态目录;按 JSON 中生产前快照的不可变摘要恢复 main、web、Watchtower(网关摘要未变化)。先同步 Watchtower 与后端使用的令牌,再按原 Compose 配置重建上述服务。不得重复提交结果尚待确认的更新任务。`configure-production.py` 保留了本次配置合并方法,不含令牌实际值。
## 验证范围
Node `22.22.0`、Yarn `1.22.22` 定向验证:真实组件挂载与 30 秒 HTTP 回归 49.42 秒;后端 31 个接口用例和 4 个 MongoDB 4.4 / HTTP / SSE 用例 2.21 秒;后端生产编译 21.02 秒;前端生产构建 441.82 秒;updater 8 个行为用例及构建通过。Bash 与真实 PowerShell 初始化、迁移、部署配置检查通过,逐项耗时见 JSON。未运行全量质量门禁,未用定向通过宣称其他业务功能全部验证。
@@ -0,0 +1,3 @@
FROM registry.cn-shanghai.aliyuncs.com/easyaigc/wsgateway@sha256:a2b5fb67384c7f0eca7c7a768b5deacb4c0d2195dfbeb6e16ef40d6dc097bf25
LABEL easyai.acceptance="intentional gateway health failure for rollback verification 20261002"
COPY pm2.config.js acceptance-health-failure.cjs /app/
@@ -0,0 +1 @@
require('node:http').createServer((q,s)=>{s.statusCode=503;s.end('acceptance-health-failure')}).listen(3012,'0.0.0.0');
@@ -0,0 +1 @@
module.exports={apps:[{name:'acceptance-health-failure',script:'./acceptance-health-failure.cjs'}]};
@@ -0,0 +1,6 @@
/easyai-server sha256:72ec514ed221c424422542f7922f3962b03de969a65e3ffec058fa7cd70e1fea healthy
/easyai-web sha256:4161c653ff66e92e0f24f0b175bb9b3a447f095c0e55b238f480a12ddce2821f healthy
/easyai-wsgateway sha256:821ed6a69be4771c57a7bf67084455a4c0c7e4c9e201b8e7bcb2dedf1fb229f5 healthy
sha256:72ec514ed221c424422542f7922f3962b03de969a65e3ffec058fa7cd70e1fea ["registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server@sha256:6825def1b7bf252eee477b34d3ddc0317e691f0787ea3f300793c4d272a3b491"]
sha256:4161c653ff66e92e0f24f0b175bb9b3a447f095c0e55b238f480a12ddce2821f ["registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai@sha256:a287db622d13a706725520d3a9864f986b8854630d198fce3373a8e7f1cfe3b0"]
sha256:821ed6a69be4771c57a7bf67084455a4c0c7e4c9e201b8e7bcb2dedf1fb229f5 ["registry.cn-shanghai.aliyuncs.com/easyaigc/wsgateway@sha256:a2b5fb67384c7f0eca7c7a768b5deacb4c0d2195dfbeb6e16ef40d6dc097bf25"]
Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

@@ -0,0 +1,4 @@
/easyai-server sha256:d70bb954db8e6dea13ae2ae295bd274df6bcfbdaf690fe2599698d449af86773 2026-10-02T11:22:03.534661056Z healthy
/easyai-web sha256:73689c521bdc5ecb57171c0fc629b6746491769dd2e015b9abb4e0d6091cbc07 2026-10-02T11:22:04.002902303Z healthy
/easyai-wsgateway sha256:821ed6a69be4771c57a7bf67084455a4c0c7e4c9e201b8e7bcb2dedf1fb229f5 2026-09-29T07:22:22.307885848Z healthy
/easyai-watchtower-1 sha256:04f6afca616ae63c48c860250212b7406ba917b712f4b2ebff5d43ef45578b87 2026-10-02T11:19:18.087853796Z healthy
@@ -0,0 +1,138 @@
[
{
"time": "2026-10-02T11:15:34.439Z",
"path": "/api/app/update-jobs",
"status": 200,
"value": {
"status": "success",
"data": [],
"message": "success"
}
},
{
"time": "2026-10-02T11:22:54.730Z",
"path": "/api/app/update-jobs",
"status": 500,
"value": {
"statusCode": 500,
"timestamp": "2026-10-02T11:22:54.644Z",
"path": "/app/update-jobs?active=true&limit=1",
"message": "getaddrinfo EAI_AGAIN easyai-updater",
"traceId": "yGyuK8eBkaXeNsMy",
"appVersion": "3.22.0.20261002.1",
"diagnosticCode": "RequestError"
}
},
{
"time": "2026-10-02T11:23:20.074Z",
"path": "/api/app/update-capabilities",
"status": 200,
"value": {
"status": "success",
"data": {
"mode": "legacy_watchtower",
"jobApi": false,
"executorReachable": false
},
"message": "success"
}
},
{
"time": "2026-10-02T11:23:26.733Z",
"path": "/api/app/update",
"status": 502
},
{
"time": "2026-10-02T11:23:28.856Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:30.935Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:33.033Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:35.153Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:37.254Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:39.392Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:41.490Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:43.622Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:45.733Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:47.824Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:49.905Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:51.995Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:54.094Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:56.194Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:58.414Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:24:20.993Z",
"path": "/api/health/update-ready",
"status": 200,
"value": {
"status": "ok",
"service": "easyai-server-main",
"version": "3.22.0.20261002.2",
"buildId": "3.22.0.20261002.2",
"timestamp": "2026-10-02T11:24:20.920Z",
"uptime": 42.086032874,
"dependencies": {
"mongo": "ok",
"redis": "ok",
"rabbitmq": "ok"
}
}
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

@@ -0,0 +1,36 @@
Update event timeline. Container configuration and authentication details omitted.
time="2026-10-02T11:23:20Z" level=debug msg="Log entry sent immediately (not batching)" fields.level=info legacy_template=true message="Received HTTP API update request" notify=no
time="2026-10-02T11:23:20Z" level=debug msg="Generated notification message" message="Received HTTP API update request | method=GETpath=/v1/update" notify=no
time="2026-10-02T11:23:20Z" level=debug msg="Preparing to send entries" error="<nil>" message="Received HTTP API update request | method=GETpath=/v1/update" notify=no
time="2026-10-02T11:23:20Z" level=info msg="Received HTTP API update request" method=GET path=/v1/update
time="2026-10-02T11:23:20Z" level=debug msg="Handler: trying to acquire lock"
time="2026-10-02T11:23:20Z" level=debug msg="Handler: acquired lock"
time="2026-10-02T11:23:20Z" level=debug msg="Handler: executing update function"
time="2026-10-02T11:23:20Z" level=debug msg="Starting container update check"
time="2026-10-02T11:23:20Z" level=debug msg="Sending notification" message="Received HTTP API update request | method=GETpath=/v1/update" notify=no
time="2026-10-02T11:23:22Z" level=debug msg="Log entry queued for batching" entries_count=1 fields.level=info legacy_template=true message="Found new image" notify=no
time="2026-10-02T11:23:22Z" level=info msg="Found new image" container=easyai-web image="registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai:update-acceptance-20261002" new_id=4161c653ff66
time="2026-10-02T11:23:23Z" level=debug msg="Log entry queued for batching" entries_count=2 fields.level=info legacy_template=true message="Found new image" notify=no
time="2026-10-02T11:23:23Z" level=info msg="Found new image" container=easyai-server image="registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server:update-acceptance-20261002" new_id=72ec514ed221
time="2026-10-02T11:23:24Z" level=debug msg="Stopping container for restart" container=easyai-web image="registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai:update-acceptance-20261002"
time="2026-10-02T11:23:24Z" level=debug msg="Log entry queued for batching" entries_count=3 fields.level=info legacy_template=true message="Stopping container" notify=no
time="2026-10-02T11:23:24Z" level=info msg="Stopping container" container=easyai-web id=877447f1cc84 signal=SIGTERM timeout=30s
time="2026-10-02T11:23:25Z" level=debug msg="Stopping container for restart" container=easyai-server image="registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server:update-acceptance-20261002"
time="2026-10-02T11:23:25Z" level=debug msg="Log entry queued for batching" entries_count=4 fields.level=info legacy_template=true message="Stopping container" notify=no
time="2026-10-02T11:23:25Z" level=info msg="Stopping container" container=easyai-server id=5aadf9dad929 signal=SIGTERM timeout=30s
time="2026-10-02T11:23:27Z" level=debug msg="Starting container after update/restart" container=easyai-server image="registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server:update-acceptance-20261002"
time="2026-10-02T11:23:28Z" level=debug msg="Log entry queued for batching" entries_count=5 fields.level=info legacy_template=true message="Started new container" notify=no
time="2026-10-02T11:23:28Z" level=info msg="Started new container" container=easyai-server id=5aadf9dad929 new_id=f2706ffec607
time="2026-10-02T11:23:28Z" level=debug msg="Started new container" container=easyai-server image="registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server:update-acceptance-20261002" new_id=f2706ffec607
time="2026-10-02T11:23:28Z" level=debug msg="Restarted container" container=easyai-server image="registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server:update-acceptance-20261002"
time="2026-10-02T11:23:28Z" level=debug msg="Starting container after update/restart" container=easyai-web image="registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai:update-acceptance-20261002"
time="2026-10-02T11:23:30Z" level=debug msg="Log entry queued for batching" entries_count=6 fields.level=info legacy_template=true message="Started new container" notify=no
time="2026-10-02T11:23:30Z" level=info msg="Started new container" container=easyai-web id=877447f1cc84 new_id=67052e84930b
time="2026-10-02T11:23:30Z" level=debug msg="Started new container" container=easyai-web image="registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai:update-acceptance-20261002" new_id=67052e84930b
time="2026-10-02T11:23:30Z" level=debug msg="Restarted container" container=easyai-web image="registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai:update-acceptance-20261002"
time="2026-10-02T11:23:30Z" level=debug msg="Generated notification message" message="Found new image: registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai:update-acceptance-20261002 (4161c653ff66)\nFound new image: registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server:update-acceptance-20261002 (72ec514ed221)\nStopped stale container: easyai-web (877447f1cc84)\nStopped stale container: easyai-server (5aadf9dad929)\nStarted new container: easyai-server (f2706ffec607)\nStarted new container: easyai-web (67052e84930b)" notify=no
time="2026-10-02T11:23:30Z" level=debug msg="Preparing to send entries" error="<nil>" message="Found new image: registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai:update-acceptance-20261002 (4161c653ff66)\nFound new image: registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server:update-acceptance-20261002 (72ec514ed221)\nStopped stale container: easyai-web (877447f1cc84)\nStopped stale container: easyai-server (5aadf9dad929)\nStarted new container: easyai-server (f2706ffec607)\nStarted new container: easyai-web (67052e84930b)" notify=no
time="2026-10-02T11:23:30Z" level=info msg="Update session completed" failed=0 notify=no scanned=3 updated=2
time="2026-10-02T11:23:30Z" level=debug msg="Handler: update function completed"
time="2026-10-02T11:23:30Z" level=debug msg="Handler: releasing lock"
time="2026-10-02T11:23:30Z" level=debug msg="Sending notification" message="Found new image: registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai:update-acceptance-20261002 (4161c653ff66)\nFound new image: registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server:update-acceptance-20261002 (72ec514ed221)\nStopped stale container: easyai-web (877447f1cc84)\nStopped stale container: easyai-server (5aadf9dad929)\nStarted new container: easyai-server (f2706ffec607)\nStarted new container: easyai-web (67052e84930b)" notify=no
@@ -0,0 +1,6 @@
/easyai-server sha256:72ec514ed221c424422542f7922f3962b03de969a65e3ffec058fa7cd70e1fea healthy
/easyai-web sha256:4161c653ff66e92e0f24f0b175bb9b3a447f095c0e55b238f480a12ddce2821f healthy
/easyai-wsgateway sha256:821ed6a69be4771c57a7bf67084455a4c0c7e4c9e201b8e7bcb2dedf1fb229f5 healthy
sha256:72ec514ed221c424422542f7922f3962b03de969a65e3ffec058fa7cd70e1fea ["registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server@sha256:6825def1b7bf252eee477b34d3ddc0317e691f0787ea3f300793c4d272a3b491"]
sha256:4161c653ff66e92e0f24f0b175bb9b3a447f095c0e55b238f480a12ddce2821f ["registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai@sha256:a287db622d13a706725520d3a9864f986b8854630d198fce3373a8e7f1cfe3b0"]
sha256:821ed6a69be4771c57a7bf67084455a4c0c7e4c9e201b8e7bcb2dedf1fb229f5 ["registry.cn-shanghai.aliyuncs.com/easyaigc/wsgateway@sha256:a2b5fb67384c7f0eca7c7a768b5deacb4c0d2195dfbeb6e16ef40d6dc097bf25"]
@@ -0,0 +1,252 @@
[
{
"id": "154b6781-dd24-434d-94a5-4bf59122c0bb",
"idempotencyKey": "c00a35c9-6e10-4ab1-9915-5e44f58e2daf",
"action": "update",
"source": "manual",
"status": "succeeded",
"phase": "finished",
"message": "更新完成,所有核心服务均已通过检查",
"createdAt": "2026-10-02T11:37:32.825Z",
"updatedAt": "2026-10-02T11:38:26.397Z",
"previousVersion": "3.22.0.20261002.1",
"components": [
{
"key": "server-main",
"containerName": "easyai-server",
"status": "succeeded",
"targetImageId": "sha256:72ec514ed221c424422542f7922f3962b03de969a65e3ffec058fa7cd70e1fea",
"previousImageId": "sha256:6a809aacd82dd70c23ef1f3873bab3f46c7daa824a5c3b6196bfb6dadb1718af",
"networks": {
"easyai_easyai": {
"networkId": "c4265a68ef2b242f0a7d26e4a059d2d62e8aedf5db7029c27fc3676ea779b170",
"aliases": [
"easyai-server",
"easyai-server"
],
"links": null,
"ipv4Address": "172.21.0.6",
"ipv6Address": ""
},
"easyai_sandbox-network": {
"networkId": "b2daf0ea06fc85a171ed60f0a3fb7a58e7ca56fad15490fb2e589ad05e0ffffc",
"aliases": [
"easyai-server",
"easyai-server"
],
"links": null,
"ipv4Address": "172.20.0.2",
"ipv6Address": ""
}
},
"message": "新版本已通过检查"
},
{
"key": "ws-gateway",
"containerName": "easyai-wsgateway",
"status": "succeeded",
"targetImageId": "sha256:821ed6a69be4771c57a7bf67084455a4c0c7e4c9e201b8e7bcb2dedf1fb229f5"
},
{
"key": "web",
"containerName": "easyai-web",
"status": "succeeded",
"targetImageId": "sha256:4161c653ff66e92e0f24f0b175bb9b3a447f095c0e55b238f480a12ddce2821f",
"previousImageId": "sha256:73689c521bdc5ecb57171c0fc629b6746491769dd2e015b9abb4e0d6091cbc07",
"networks": {
"easyai_easyai": {
"networkId": "c4265a68ef2b242f0a7d26e4a059d2d62e8aedf5db7029c27fc3676ea779b170",
"aliases": [
"easyai-web",
"easyai-web"
],
"links": null,
"ipv4Address": "172.21.0.8",
"ipv6Address": ""
}
},
"message": "新版本已通过检查"
}
],
"executor": "updater",
"steps": [
{
"id": "queued",
"title": "提交请求",
"status": "completed",
"startedAt": "2026-10-02T11:37:32.825Z",
"finishedAt": "2026-10-02T11:37:32.827Z"
},
{
"id": "prechecking",
"title": "检查环境",
"status": "completed",
"startedAt": "2026-10-02T11:37:32.827Z",
"finishedAt": "2026-10-02T11:37:32.829Z"
},
{
"id": "pulling",
"title": "下载更新",
"status": "completed",
"startedAt": "2026-10-02T11:37:32.829Z",
"finishedAt": "2026-10-02T11:37:33.478Z"
},
{
"id": "applying",
"title": "更新服务",
"status": "completed",
"startedAt": "2026-10-02T11:37:33.478Z",
"finishedAt": "2026-10-02T11:38:26.153Z"
},
{
"id": "verifying",
"title": "验证版本",
"status": "completed",
"startedAt": "2026-10-02T11:38:26.153Z",
"finishedAt": "2026-10-02T11:38:26.397Z"
},
{
"id": "finished",
"title": "完成",
"status": "completed",
"finishedAt": "2026-10-02T11:38:26.397Z"
}
],
"startedAt": "2026-10-02T11:37:32.827Z",
"releaseId": "online-update-progress-20261002",
"productVersion": "3.22.0.20261002.2",
"finishedAt": "2026-10-02T11:38:26.397Z"
},
{
"id": "d0f02915-1cbe-4063-94c6-e9d749aaf2d7",
"idempotencyKey": "c05c99af-affd-482b-98e1-eca9dea21c6b",
"action": "update",
"source": "manual",
"status": "rolled_back",
"phase": "finished",
"message": "新版本未能正常启动,已恢复原版本",
"createdAt": "2026-10-02T11:33:41.418Z",
"updatedAt": "2026-10-02T11:36:15.937Z",
"previousVersion": "3.22.0.20261002.1",
"components": [
{
"key": "server-main",
"containerName": "easyai-server",
"status": "rolled_back",
"targetImageId": "sha256:72ec514ed221c424422542f7922f3962b03de969a65e3ffec058fa7cd70e1fea",
"previousImageId": "sha256:6a809aacd82dd70c23ef1f3873bab3f46c7daa824a5c3b6196bfb6dadb1718af",
"networks": {
"easyai_easyai": {
"networkId": "c4265a68ef2b242f0a7d26e4a059d2d62e8aedf5db7029c27fc3676ea779b170",
"aliases": [
"easyai-server",
"easyai-server"
],
"links": null,
"ipv4Address": "172.21.0.6",
"ipv6Address": ""
},
"easyai_sandbox-network": {
"networkId": "b2daf0ea06fc85a171ed60f0a3fb7a58e7ca56fad15490fb2e589ad05e0ffffc",
"aliases": [
"easyai-server",
"easyai-server"
],
"links": null,
"ipv4Address": "172.20.0.2",
"ipv6Address": ""
}
},
"message": "新版本已通过检查"
},
{
"key": "ws-gateway",
"containerName": "easyai-wsgateway",
"status": "rolled_back",
"targetImageId": "sha256:4fb8a96a311686f59ab142dd150f7a2df8662a8b9f19233f9b06cfe548c899d6",
"previousImageId": "sha256:821ed6a69be4771c57a7bf67084455a4c0c7e4c9e201b8e7bcb2dedf1fb229f5",
"networks": {
"easyai_easyai": {
"networkId": "c4265a68ef2b242f0a7d26e4a059d2d62e8aedf5db7029c27fc3676ea779b170",
"aliases": [
"easyai-wsgateway",
"ws-gateway"
],
"links": null,
"ipv4Address": "172.21.0.5",
"ipv6Address": ""
}
}
},
{
"key": "web",
"containerName": "easyai-web",
"status": "rolled_back",
"targetImageId": "sha256:4161c653ff66e92e0f24f0b175bb9b3a447f095c0e55b238f480a12ddce2821f",
"previousImageId": "sha256:73689c521bdc5ecb57171c0fc629b6746491769dd2e015b9abb4e0d6091cbc07",
"networks": {
"easyai_easyai": {
"networkId": "c4265a68ef2b242f0a7d26e4a059d2d62e8aedf5db7029c27fc3676ea779b170",
"aliases": [
"easyai-web",
"easyai-web"
],
"links": null,
"ipv4Address": "172.21.0.8",
"ipv6Address": ""
}
},
"message": "新版本已通过检查"
}
],
"executor": "updater",
"steps": [
{
"id": "queued",
"title": "提交请求",
"status": "completed",
"startedAt": "2026-10-02T11:33:41.421Z",
"finishedAt": "2026-10-02T11:33:41.426Z"
},
{
"id": "prechecking",
"title": "检查环境",
"status": "completed",
"startedAt": "2026-10-02T11:33:41.426Z",
"finishedAt": "2026-10-02T11:33:41.432Z"
},
{
"id": "pulling",
"title": "下载更新",
"status": "completed",
"startedAt": "2026-10-02T11:33:41.432Z",
"finishedAt": "2026-10-02T11:33:43.339Z"
},
{
"id": "applying",
"title": "更新服务",
"status": "completed",
"startedAt": "2026-10-02T11:33:43.339Z",
"finishedAt": "2026-10-02T11:35:11.300Z"
},
{
"id": "verifying",
"title": "验证版本",
"status": "failed",
"startedAt": "2026-10-02T11:35:11.300Z",
"finishedAt": "2026-10-02T11:36:15.936Z"
},
{
"id": "finished",
"title": "完成",
"status": "failed",
"finishedAt": "2026-10-02T11:36:15.936Z"
}
],
"startedAt": "2026-10-02T11:33:41.426Z",
"releaseId": "update-rollback-acceptance-20261002",
"productVersion": "3.22.0.20261002.2",
"reasonCode": "healthcheck_failed",
"finishedAt": "2026-10-02T11:36:15.936Z"
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

@@ -0,0 +1,4 @@
/easyai-server sha256:6a809aacd82dd70c23ef1f3873bab3f46c7daa824a5c3b6196bfb6dadb1718af healthy
/easyai-web sha256:73689c521bdc5ecb57171c0fc629b6746491769dd2e015b9abb4e0d6091cbc07 healthy
/easyai-wsgateway sha256:821ed6a69be4771c57a7bf67084455a4c0c7e4c9e201b8e7bcb2dedf1fb229f5 healthy
{"status":"success","data":{"version":"3.22.0.20261002.1"},"message":"success"}
Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

@@ -0,0 +1,132 @@
{
"id": "d0f02915-1cbe-4063-94c6-e9d749aaf2d7",
"idempotencyKey": "c05c99af-affd-482b-98e1-eca9dea21c6b",
"action": "update",
"source": "manual",
"status": "rolled_back",
"phase": "finished",
"message": "新版本未能正常启动,已恢复原版本",
"createdAt": "2026-10-02T11:33:41.418Z",
"updatedAt": "2026-10-02T11:36:15.937Z",
"previousVersion": "3.22.0.20261002.1",
"components": [
{
"key": "server-main",
"containerName": "easyai-server",
"status": "rolled_back",
"targetImageId": "sha256:72ec514ed221c424422542f7922f3962b03de969a65e3ffec058fa7cd70e1fea",
"previousImageId": "sha256:6a809aacd82dd70c23ef1f3873bab3f46c7daa824a5c3b6196bfb6dadb1718af",
"networks": {
"easyai_easyai": {
"networkId": "c4265a68ef2b242f0a7d26e4a059d2d62e8aedf5db7029c27fc3676ea779b170",
"aliases": [
"easyai-server",
"easyai-server"
],
"links": null,
"ipv4Address": "172.21.0.6",
"ipv6Address": ""
},
"easyai_sandbox-network": {
"networkId": "b2daf0ea06fc85a171ed60f0a3fb7a58e7ca56fad15490fb2e589ad05e0ffffc",
"aliases": [
"easyai-server",
"easyai-server"
],
"links": null,
"ipv4Address": "172.20.0.2",
"ipv6Address": ""
}
},
"message": "新版本已通过检查"
},
{
"key": "ws-gateway",
"containerName": "easyai-wsgateway",
"status": "rolled_back",
"targetImageId": "sha256:4fb8a96a311686f59ab142dd150f7a2df8662a8b9f19233f9b06cfe548c899d6",
"previousImageId": "sha256:821ed6a69be4771c57a7bf67084455a4c0c7e4c9e201b8e7bcb2dedf1fb229f5",
"networks": {
"easyai_easyai": {
"networkId": "c4265a68ef2b242f0a7d26e4a059d2d62e8aedf5db7029c27fc3676ea779b170",
"aliases": [
"easyai-wsgateway",
"ws-gateway"
],
"links": null,
"ipv4Address": "172.21.0.5",
"ipv6Address": ""
}
}
},
{
"key": "web",
"containerName": "easyai-web",
"status": "rolled_back",
"targetImageId": "sha256:4161c653ff66e92e0f24f0b175bb9b3a447f095c0e55b238f480a12ddce2821f",
"previousImageId": "sha256:73689c521bdc5ecb57171c0fc629b6746491769dd2e015b9abb4e0d6091cbc07",
"networks": {
"easyai_easyai": {
"networkId": "c4265a68ef2b242f0a7d26e4a059d2d62e8aedf5db7029c27fc3676ea779b170",
"aliases": [
"easyai-web",
"easyai-web"
],
"links": null,
"ipv4Address": "172.21.0.8",
"ipv6Address": ""
}
},
"message": "新版本已通过检查"
}
],
"executor": "updater",
"steps": [
{
"id": "queued",
"title": "提交请求",
"status": "completed",
"startedAt": "2026-10-02T11:33:41.421Z",
"finishedAt": "2026-10-02T11:33:41.426Z"
},
{
"id": "prechecking",
"title": "检查环境",
"status": "completed",
"startedAt": "2026-10-02T11:33:41.426Z",
"finishedAt": "2026-10-02T11:33:41.432Z"
},
{
"id": "pulling",
"title": "下载更新",
"status": "completed",
"startedAt": "2026-10-02T11:33:41.432Z",
"finishedAt": "2026-10-02T11:33:43.339Z"
},
{
"id": "applying",
"title": "更新服务",
"status": "completed",
"startedAt": "2026-10-02T11:33:43.339Z",
"finishedAt": "2026-10-02T11:35:11.300Z"
},
{
"id": "verifying",
"title": "验证版本",
"status": "failed",
"startedAt": "2026-10-02T11:35:11.300Z",
"finishedAt": "2026-10-02T11:36:15.936Z"
},
{
"id": "finished",
"title": "完成",
"status": "failed",
"finishedAt": "2026-10-02T11:36:15.936Z"
}
],
"startedAt": "2026-10-02T11:33:41.426Z",
"releaseId": "update-rollback-acceptance-20261002",
"productVersion": "3.22.0.20261002.2",
"reasonCode": "healthcheck_failed",
"finishedAt": "2026-10-02T11:36:15.936Z"
}
@@ -0,0 +1,128 @@
{
"id": "d0f02915-1cbe-4063-94c6-e9d749aaf2d7",
"idempotencyKey": "c05c99af-affd-482b-98e1-eca9dea21c6b",
"action": "update",
"source": "manual",
"status": "running",
"phase": "applying",
"message": "正在更新网页服务",
"createdAt": "2026-10-02T11:33:41.418Z",
"updatedAt": "2026-10-02T11:35:05.249Z",
"previousVersion": "3.22.0.20261002.1",
"components": [
{
"key": "server-main",
"containerName": "easyai-server",
"status": "succeeded",
"targetImageId": "sha256:72ec514ed221c424422542f7922f3962b03de969a65e3ffec058fa7cd70e1fea",
"previousImageId": "sha256:6a809aacd82dd70c23ef1f3873bab3f46c7daa824a5c3b6196bfb6dadb1718af",
"backupContainerName": "easyai-update-backup-d0f02915-server-main",
"networks": {
"easyai_easyai": {
"networkId": "c4265a68ef2b242f0a7d26e4a059d2d62e8aedf5db7029c27fc3676ea779b170",
"aliases": [
"easyai-server",
"easyai-server"
],
"links": null,
"ipv4Address": "172.21.0.6",
"ipv6Address": ""
},
"easyai_sandbox-network": {
"networkId": "b2daf0ea06fc85a171ed60f0a3fb7a58e7ca56fad15490fb2e589ad05e0ffffc",
"aliases": [
"easyai-server",
"easyai-server"
],
"links": null,
"ipv4Address": "172.20.0.2",
"ipv6Address": ""
}
},
"message": "新版本已通过检查"
},
{
"key": "ws-gateway",
"containerName": "easyai-wsgateway",
"status": "succeeded",
"targetImageId": "sha256:4fb8a96a311686f59ab142dd150f7a2df8662a8b9f19233f9b06cfe548c899d6",
"previousImageId": "sha256:821ed6a69be4771c57a7bf67084455a4c0c7e4c9e201b8e7bcb2dedf1fb229f5",
"backupContainerName": "easyai-update-backup-d0f02915-ws-gateway",
"networks": {
"easyai_easyai": {
"networkId": "c4265a68ef2b242f0a7d26e4a059d2d62e8aedf5db7029c27fc3676ea779b170",
"aliases": [
"easyai-wsgateway",
"ws-gateway"
],
"links": null,
"ipv4Address": "172.21.0.5",
"ipv6Address": ""
}
}
},
{
"key": "web",
"containerName": "easyai-web",
"status": "verifying",
"targetImageId": "sha256:4161c653ff66e92e0f24f0b175bb9b3a447f095c0e55b238f480a12ddce2821f",
"previousImageId": "sha256:73689c521bdc5ecb57171c0fc629b6746491769dd2e015b9abb4e0d6091cbc07",
"backupContainerName": "easyai-update-backup-d0f02915-web",
"networks": {
"easyai_easyai": {
"networkId": "c4265a68ef2b242f0a7d26e4a059d2d62e8aedf5db7029c27fc3676ea779b170",
"aliases": [
"easyai-web",
"easyai-web"
],
"links": null,
"ipv4Address": "172.21.0.8",
"ipv6Address": ""
}
}
}
],
"executor": "updater",
"steps": [
{
"id": "queued",
"title": "提交请求",
"status": "completed",
"startedAt": "2026-10-02T11:33:41.421Z",
"finishedAt": "2026-10-02T11:33:41.426Z"
},
{
"id": "prechecking",
"title": "检查环境",
"status": "completed",
"startedAt": "2026-10-02T11:33:41.426Z",
"finishedAt": "2026-10-02T11:33:41.432Z"
},
{
"id": "pulling",
"title": "下载更新",
"status": "completed",
"startedAt": "2026-10-02T11:33:41.432Z",
"finishedAt": "2026-10-02T11:33:43.339Z"
},
{
"id": "applying",
"title": "更新服务",
"status": "running",
"startedAt": "2026-10-02T11:33:43.339Z"
},
{
"id": "verifying",
"title": "验证版本",
"status": "pending"
},
{
"id": "finished",
"title": "完成",
"status": "pending"
}
],
"startedAt": "2026-10-02T11:33:41.426Z",
"releaseId": "update-rollback-acceptance-20261002",
"productVersion": "3.22.0.20261002.2"
}
@@ -0,0 +1,6 @@
/easyai-server sha256:72ec514ed221c424422542f7922f3962b03de969a65e3ffec058fa7cd70e1fea healthy
/easyai-web sha256:4161c653ff66e92e0f24f0b175bb9b3a447f095c0e55b238f480a12ddce2821f healthy
/easyai-wsgateway sha256:821ed6a69be4771c57a7bf67084455a4c0c7e4c9e201b8e7bcb2dedf1fb229f5 healthy
sha256:72ec514ed221c424422542f7922f3962b03de969a65e3ffec058fa7cd70e1fea ["registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server@sha256:6825def1b7bf252eee477b34d3ddc0317e691f0787ea3f300793c4d272a3b491"]
sha256:4161c653ff66e92e0f24f0b175bb9b3a447f095c0e55b238f480a12ddce2821f ["registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai@sha256:a287db622d13a706725520d3a9864f986b8854630d198fce3373a8e7f1cfe3b0"]
sha256:821ed6a69be4771c57a7bf67084455a4c0c7e4c9e201b8e7bcb2dedf1fb229f5 ["registry.cn-shanghai.aliyuncs.com/easyaigc/wsgateway@sha256:a2b5fb67384c7f0eca7c7a768b5deacb4c0d2195dfbeb6e16ef40d6dc097bf25"]
Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

@@ -0,0 +1,4 @@
/easyai-server sha256:6a809aacd82dd70c23ef1f3873bab3f46c7daa824a5c3b6196bfb6dadb1718af 2026-10-02T11:25:01.404278562Z healthy
/easyai-web sha256:73689c521bdc5ecb57171c0fc629b6746491769dd2e015b9abb4e0d6091cbc07 2026-10-02T11:25:03.264453447Z healthy
/easyai-wsgateway sha256:821ed6a69be4771c57a7bf67084455a4c0c7e4c9e201b8e7bcb2dedf1fb229f5 2026-09-29T07:22:22.307885848Z healthy
/easyai-watchtower-1 sha256:6ba1e78a0c9ce1ae5389ecf8a78426b82beaf3357c71d52548f7d05fd92eebb7 2026-10-02T11:25:01.394675669Z healthy
@@ -0,0 +1,459 @@
[
{
"time": "2026-10-02T11:15:34.439Z",
"path": "/api/app/update-jobs",
"status": 200,
"value": {
"status": "success",
"data": [],
"message": "success"
}
},
{
"time": "2026-10-02T11:22:54.730Z",
"path": "/api/app/update-jobs",
"status": 500,
"value": {
"statusCode": 500,
"timestamp": "2026-10-02T11:22:54.644Z",
"path": "/app/update-jobs?active=true&limit=1",
"message": "getaddrinfo EAI_AGAIN easyai-updater",
"traceId": "yGyuK8eBkaXeNsMy",
"appVersion": "3.22.0.20261002.1",
"diagnosticCode": "RequestError"
}
},
{
"time": "2026-10-02T11:23:20.074Z",
"path": "/api/app/update-capabilities",
"status": 200,
"value": {
"status": "success",
"data": {
"mode": "legacy_watchtower",
"jobApi": false,
"executorReachable": false
},
"message": "success"
}
},
{
"time": "2026-10-02T11:23:26.733Z",
"path": "/api/app/update",
"status": 502
},
{
"time": "2026-10-02T11:23:28.856Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:30.935Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:33.033Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:35.153Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:37.254Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:39.392Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:41.490Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:43.622Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:45.733Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:47.824Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:49.905Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:51.995Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:54.094Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:56.194Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:23:58.414Z",
"path": "/api/health/update-ready",
"status": 502
},
{
"time": "2026-10-02T11:24:20.993Z",
"path": "/api/health/update-ready",
"status": 200,
"value": {
"status": "ok",
"service": "easyai-server-main",
"version": "3.22.0.20261002.2",
"buildId": "3.22.0.20261002.2",
"timestamp": "2026-10-02T11:24:20.920Z",
"uptime": 42.086032874,
"dependencies": {
"mongo": "ok",
"redis": "ok",
"rabbitmq": "ok"
}
}
},
{
"time": "2026-10-02T11:25:52.477Z",
"path": "/api/app/update-jobs",
"status": 200,
"value": {
"status": "success",
"data": [],
"message": "success"
}
},
{
"time": "2026-10-02T11:25:58.757Z",
"path": "/api/app/update-capabilities",
"status": 200,
"value": {
"status": "success",
"data": {
"mode": "legacy_watchtower",
"executor": "watchtower",
"jobApi": true,
"eventApi": true,
"realtime": true,
"executorReachable": true
},
"message": "success"
}
},
{
"time": "2026-10-02T11:25:58.910Z",
"path": "/api/app/update-jobs",
"status": 201,
"value": {
"status": "success",
"data": {
"id": "wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"idempotencyKey": "9bb11e4f-924a-4fd5-a501-86150a35e147",
"action": "update",
"source": "manual",
"executor": "watchtower",
"status": "queued",
"phase": "queued",
"message": "更新任务已保存,正在提交更新请求",
"previousVersion": "3.22.0.20261002.1",
"productVersion": "3.22.0.20261002.2",
"createdAt": "2026-10-02T11:25:58.856Z",
"updatedAt": "2026-10-02T11:25:58.856Z",
"startedAt": "2026-10-02T11:25:58.856Z",
"steps": [
{
"id": "queued",
"title": "提交请求",
"status": "running",
"startedAt": "2026-10-02T11:25:58.856Z"
},
{
"id": "applying",
"title": "执行更新",
"status": "pending"
},
{
"id": "verifying",
"title": "等待服务恢复",
"status": "pending"
},
{
"id": "finished",
"title": "确认版本",
"status": "pending"
}
],
"components": []
},
"message": "success"
}
},
{
"time": "2026-10-02T11:25:59.020Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a/events",
"status": 200
},
{
"time": "2026-10-02T11:26:01.059Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"status": 200,
"value": {
"status": "success",
"data": {
"id": "wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"idempotencyKey": "9bb11e4f-924a-4fd5-a501-86150a35e147",
"action": "update",
"source": "manual",
"executor": "watchtower",
"status": "running",
"phase": "applying",
"message": "更新服务已开始执行,正在检查、下载并更新服务",
"previousVersion": "3.22.0.20261002.1",
"productVersion": "3.22.0.20261002.2",
"createdAt": "2026-10-02T11:25:58.856Z",
"updatedAt": "2026-10-02T11:25:58.893Z",
"startedAt": "2026-10-02T11:25:58.856Z",
"steps": [
{
"id": "queued",
"title": "提交请求",
"status": "completed",
"startedAt": "2026-10-02T11:25:58.856Z",
"finishedAt": "2026-10-02T11:25:58.893Z"
},
{
"id": "applying",
"title": "执行更新",
"status": "running",
"startedAt": "2026-10-02T11:25:58.893Z"
},
{
"id": "verifying",
"title": "等待服务恢复",
"status": "pending"
},
{
"id": "finished",
"title": "确认版本",
"status": "pending"
}
],
"components": []
},
"message": "success"
}
},
{
"time": "2026-10-02T11:26:01.259Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a/events",
"status": 200
},
{
"time": "2026-10-02T11:26:03.159Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"status": 502
},
{
"time": "2026-10-02T11:26:03.288Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a/events",
"status": 502
},
{
"time": "2026-10-02T11:26:05.239Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"status": 502
},
{
"time": "2026-10-02T11:26:05.338Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a/events",
"status": 502
},
{
"time": "2026-10-02T11:26:07.353Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"status": 502
},
{
"time": "2026-10-02T11:26:07.462Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a/events",
"status": 502
},
{
"time": "2026-10-02T11:26:09.548Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"status": 502
},
{
"time": "2026-10-02T11:26:09.746Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a/events",
"status": 502
},
{
"time": "2026-10-02T11:26:11.619Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"status": 502
},
{
"time": "2026-10-02T11:26:11.842Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a/events",
"status": 502
},
{
"time": "2026-10-02T11:26:13.722Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"status": 502
},
{
"time": "2026-10-02T11:26:13.978Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a/events",
"status": 502
},
{
"time": "2026-10-02T11:26:15.817Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"status": 502
},
{
"time": "2026-10-02T11:26:15.916Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a/events",
"status": 502
},
{
"time": "2026-10-02T11:26:17.930Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"status": 502
},
{
"time": "2026-10-02T11:26:18.136Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a/events",
"status": 502
},
{
"time": "2026-10-02T11:26:20.053Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"status": 502
},
{
"time": "2026-10-02T11:26:20.233Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a/events",
"status": 502
},
{
"time": "2026-10-02T11:26:22.159Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"status": 502
},
{
"time": "2026-10-02T11:26:22.331Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a/events",
"status": 502
},
{
"time": "2026-10-02T11:26:24.287Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"status": 502
},
{
"time": "2026-10-02T11:26:24.428Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a/events",
"status": 502
},
{
"time": "2026-10-02T11:26:26.401Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"status": 502
},
{
"time": "2026-10-02T11:26:26.647Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a/events",
"status": 502
},
{
"time": "2026-10-02T11:26:28.491Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"status": 502
},
{
"time": "2026-10-02T11:26:28.654Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a/events",
"status": 502
},
{
"time": "2026-10-02T11:27:02.209Z",
"path": "/api/app/update-jobs/wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"status": 200,
"value": {
"status": "success",
"data": {
"id": "wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"idempotencyKey": "9bb11e4f-924a-4fd5-a501-86150a35e147",
"action": "update",
"source": "manual",
"executor": "watchtower",
"status": "succeeded",
"phase": "finished",
"message": "更新完成,目标版本和核心服务均已确认可用",
"previousVersion": "3.22.0.20261002.1",
"productVersion": "3.22.0.20261002.2",
"createdAt": "2026-10-02T11:25:58.856Z",
"updatedAt": "2026-10-02T11:26:41.594Z",
"startedAt": "2026-10-02T11:25:58.856Z",
"steps": [
{
"id": "queued",
"title": "提交请求",
"status": "completed",
"startedAt": "2026-10-02T11:25:58.856Z",
"finishedAt": "2026-10-02T11:25:58.893Z"
},
{
"id": "applying",
"title": "执行更新",
"status": "completed",
"startedAt": "2026-10-02T11:25:58.893Z",
"finishedAt": "2026-10-02T11:26:39.560Z"
},
{
"id": "verifying",
"title": "等待服务恢复",
"status": "completed",
"startedAt": "2026-10-02T11:26:39.560Z",
"finishedAt": "2026-10-02T11:26:41.594Z"
},
{
"id": "finished",
"title": "确认版本",
"status": "completed",
"startedAt": "2026-10-02T11:26:41.594Z",
"finishedAt": "2026-10-02T11:26:41.594Z"
}
],
"components": [],
"finishedAt": "2026-10-02T11:26:41.594Z"
},
"message": "success"
}
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

@@ -0,0 +1,47 @@
{
"id": "wt-91612f117537604c18fc41bf214d06a8b0aeaa4956b2c42aad738d657c5f369a",
"idempotencyKey": "9bb11e4f-924a-4fd5-a501-86150a35e147",
"action": "update",
"source": "manual",
"executor": "watchtower",
"status": "succeeded",
"phase": "finished",
"message": "更新完成,目标版本和核心服务均已确认可用",
"previousVersion": "3.22.0.20261002.1",
"productVersion": "3.22.0.20261002.2",
"createdAt": "2026-10-02T11:25:58.856Z",
"updatedAt": "2026-10-02T11:26:41.594Z",
"startedAt": "2026-10-02T11:25:58.856Z",
"steps": [
{
"id": "queued",
"title": "提交请求",
"status": "completed",
"startedAt": "2026-10-02T11:25:58.856Z",
"finishedAt": "2026-10-02T11:25:58.893Z"
},
{
"id": "applying",
"title": "执行更新",
"status": "completed",
"startedAt": "2026-10-02T11:25:58.893Z",
"finishedAt": "2026-10-02T11:26:39.560Z"
},
{
"id": "verifying",
"title": "等待服务恢复",
"status": "completed",
"startedAt": "2026-10-02T11:26:39.560Z",
"finishedAt": "2026-10-02T11:26:41.594Z"
},
{
"id": "finished",
"title": "确认版本",
"status": "completed",
"startedAt": "2026-10-02T11:26:41.594Z",
"finishedAt": "2026-10-02T11:26:41.594Z"
}
],
"components": [],
"finishedAt": "2026-10-02T11:26:41.594Z"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

@@ -0,0 +1,31 @@
Update event timeline. Container configuration and authentication details omitted.
time=2026-10-02T11:25:58Z level=debug msg="Received HTTP API history request" method=GET notify=no path=/v1/history
time=2026-10-02T11:25:58Z level=debug msg="Received HTTP API history request" method=GET notify=no path=/v1/history
time=2026-10-02T11:25:58Z level=debug msg="Received HTTP API history request" method=GET notify=no path=/v1/history
time=2026-10-02T11:25:58Z level=info msg="Received HTTP API update request" method=POST notify=no path=/v1/update
time=2026-10-02T11:25:58Z level=debug msg="Handler: trying to acquire lock" notify=no
time=2026-10-02T11:25:58Z level=debug msg="Handler: acquired lock for full update" notify=no
time=2026-10-02T11:25:58Z level=debug msg="Starting container update check"
time=2026-10-02T11:25:58Z level=debug msg="Received HTTP API history request" method=GET notify=no path=/v1/history
time=2026-10-02T11:25:59Z level=debug msg="Received HTTP API history request" method=GET notify=no path=/v1/history
time=2026-10-02T11:26:00Z level=info msg="Found new image" container=easyai-web image=registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai:update-acceptance-20261002 new_id=4161c653ff66
time=2026-10-02T11:26:00Z level=debug msg="Received HTTP API history request" method=GET notify=no path=/v1/history
time=2026-10-02T11:26:01Z level=info msg="Found new image" container=easyai-server image=registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server:update-acceptance-20261002 new_id=72ec514ed221
time=2026-10-02T11:26:01Z level=debug msg="Stopping container for restart" container=easyai-web image=registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai:update-acceptance-20261002
time=2026-10-02T11:26:01Z level=info msg="Stopping container" container=easyai-web id=41244005ea7a signal=SIGTERM timeout=30000
time=2026-10-02T11:26:01Z level=debug msg="Received HTTP API history request" method=GET notify=no path=/v1/history
time=2026-10-02T11:26:01Z level=debug msg="Stopping container for restart" container=easyai-server image=registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server:update-acceptance-20261002
time=2026-10-02T11:26:01Z level=info msg="Stopping container" container=easyai-server id=cea3cafc9806 signal=SIGTERM timeout=30000
time=2026-10-02T11:26:03Z level=debug msg="Starting container with updated configuration" container=easyai-server image=registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server:update-acceptance-20261002
time=2026-10-02T11:26:03Z level=debug msg="Starting container by ID" container_id=62a2a7eea3d6
time=2026-10-02T11:26:05Z level=debug msg="Container started successfully" container_id=62a2a7eea3d6
time=2026-10-02T11:26:05Z level=info msg="Started new container" container=easyai-server image=registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server:update-acceptance-20261002 new_id=62a2a7eea3d6
time=2026-10-02T11:26:05Z level=debug msg="Restarted container" container=easyai-server image=registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server:update-acceptance-20261002
time=2026-10-02T11:26:06Z level=debug msg="Starting container with updated configuration" container=easyai-web image=registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai:update-acceptance-20261002
time=2026-10-02T11:26:06Z level=debug msg="Starting container by ID" container_id=6b1c321e8352
time=2026-10-02T11:26:07Z level=debug msg="Container started successfully" container_id=6b1c321e8352
time=2026-10-02T11:26:07Z level=info msg="Started new container" container=easyai-web image=registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai:update-acceptance-20261002 new_id=6b1c321e8352
time=2026-10-02T11:26:07Z level=debug msg="Restarted container" container=easyai-web image=registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai:update-acceptance-20261002
time=2026-10-02T11:26:07Z level=info msg="Update session completed" failed=0 notify=no scanned=3 skipped=0 updated=2
time=2026-10-02T11:26:07Z level=debug msg="Handler (async): update function completed" duration=8527.922807 notify=no
time=2026-10-02T11:26:07Z level=debug msg="Handler: releasing lock" notify=no
@@ -0,0 +1,23 @@
import pathlib,re,json,secrets,os,subprocess,hashlib
root=pathlib.Path('/home/admin/easyai');spec={'watchtowerBlock': ' watchtower:\n image: ${WATCHTOWER_IMAGE:-registry.cn-shanghai.aliyuncs.com/comfy-ai/watchtower-aliyun:1.22.3}\n volumes:\n - /var/run/docker.sock:/var/run/docker.sock\n - ${DOCKER_CONFIG_FILE:-/root/.docker/config.json}:/config.json:ro\n # 新安装只负责 easyai-updater 自身;存量环境未配置目标时保持原来的全容器扫描方式。\n command: --debug ${WATCHTOWER_UPDATE_TARGET:-}\n environment:\n - WATCHTOWER_POLL_INTERVAL=${SYSTEM_UPDATER_UPDATE_INTERVAL_SECONDS:-86400}\n - WATCHTOWER_CLEANUP=${WATCHTOWER_CLEANUP:-false}\n - WATCHTOWER_HTTP_API_HOST=0.0.0.0\n - WATCHTOWER_HTTP_API_ENDPOINTS=health,update,metrics,events,history\n - WATCHTOWER_HTTP_API_TOKEN=${WATCHTOWER_API_KEY}\n - WATCHTOWER_HTTP_API_EVENTS_TOKEN=${WATCHTOWER_HTTP_API_EVENTS_TOKEN}\n - WATCHTOWER_HTTP_API_PERIODIC_POLLS=${WATCHTOWER_HTTP_API_PERIODIC_POLLS:-false}\n restart: always\n labels:\n - "com.centurylinklabs.watchtower.enable=false"\n# ports:\n# - "${WATCHTOWER_PORT}:8080"\n read_only: true\n networks:\n easyai:\n ipv4_address: 172.21.0.9\n # 日志大小设置,避免日志文件过大\n logging:\n driver: "json-file"\n options:\n max-size: "100m"\n max-file: "10"\n', 'healthcheckBlocks': {'easyai-server': ' healthcheck:\n test: ["CMD-SHELL", "node -e \\"require(\'http\').get(\'http://127.0.0.1:3001/health/update-ready\', (res) => { res.resume(); process.exit(res.statusCode === 200 ? 0 : 1); }).on(\'error\', () => process.exit(1))\\""]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 60s\n', 'easyai-web': ' healthcheck:\n test: ["CMD-SHELL", "node -e \\"require(\'http\').get(\'http://127.0.0.1:3010/health\', (res) => { res.resume(); process.exit(res.statusCode === 200 ? 0 : 1); }).on(\'error\', () => process.exit(1))\\""]\n interval: 10s\n timeout: 5s\n retries: 12\n start_period: 30s\n # 资源限制,防止内存溢出\n'}}
p=root/'.env';original=p.read_text();rows=original.splitlines();env={line.partition('=')[0]:line.partition('=')[2].strip().strip('"').strip("'") for line in rows if '=' in line and not line.lstrip().startswith('#')}
updates={'SYSTEM_UPDATE_MODE':'legacy_watchtower','SYSTEM_UPDATE_DEPLOYMENT_ID':'newfound-101200186210','WATCHTOWER_IMAGE':'registry.cn-shanghai.aliyuncs.com/comfy-ai/watchtower-aliyun:1.22.3','WATCHTOWER_UPDATE_TARGET':'easyai-server easyai-wsgateway easyai-web','WATCHTOWER_HTTP_API_PERIODIC_POLLS':'false','WATCHTOWER_CLEANUP':'false','WATCHTOWER_CORE_UPDATE_ENABLED':'true','WATCHTOWER_API_URL':'http://watchtower:8080'}
for key in ['WATCHTOWER_API_KEY','WATCHTOWER_HTTP_API_EVENTS_TOKEN']:
updates[key]=env[key] if len(env.get(key,''))>=32 else secrets.token_hex(32)
if updates['WATCHTOWER_API_KEY']==updates['WATCHTOWER_HTTP_API_EVENTS_TOKEN']:updates['WATCHTOWER_HTTP_API_EVENTS_TOKEN']=secrets.token_hex(32)
if not env.get('COMPOSE_FILE'):updates['COMPOSE_FILE']='docker-compose.yml:docker-compose.ice-v1.yml'
added=list(updates)
new=[k+'='+updates.pop(k) if k in updates else line for line in rows for k in [line.partition('=')[0]]];new.extend(k+'='+v for k,v in updates.items());stat=p.stat();p.write_text('\n'.join(new)+'\n');p.chmod(0o600);os.chown(p,stat.st_uid,stat.st_gid)
p=root/'docker-compose.yml';s=p.read_text();stat=p.stat()
def bounds(name):
start=s.index(' '+name+':');match=re.search(r'^ [A-Za-z0-9_-]+:',s[start+3:],re.M);return start,start+3+match.start() if match else len(s)
a,b=bounds('watchtower');wt=spec['watchtowerBlock']
if not pathlib.Path('/root/.docker/config.json').is_file():wt='\n'.join(line for line in wt.splitlines() if '/config.json:ro' not in line)+'\n'
s=s[:a]+wt+s[b:]
for name,health in spec['healthcheckBlocks'].items():
a,b=bounds(name);part=s[a:b]
if ' healthcheck:' not in part:s=s[:b]+health+s[b:]
p.write_text(s);os.chown(p,stat.st_uid,stat.st_gid)
backup=root/'data/deployment-backups/online-update-progress-20261002';(backup/'configuration-change.json').write_text(json.dumps({'envKeys':added,'mode':'legacy_watchtower','composeBeforeSha256':hashlib.sha256((backup/'docker-compose.yml').read_bytes()).hexdigest(),'composeAfterSha256':hashlib.sha256(p.read_bytes()).hexdigest(),'iceSha256':hashlib.sha256((root/'docker-compose.ice-v1.yml').read_bytes()).hexdigest()},indent=2));(backup/'configuration-change.json').chmod(0o600)
x=json.loads(subprocess.check_output(['docker','compose','config','--format','json'],cwd=root));assert 'easyai-updater' not in x['services'];assert not x['services']['watchtower'].get('ports');assert x['services']['easyai-server']['environment']['SYSTEM_UPDATE_MODE']=='legacy_watchtower';assert len(x['services']['watchtower']['environment']['WATCHTOWER_HTTP_API_TOKEN'])>=32
print(json.dumps({'configured':True,'mode':'legacy_watchtower','updaterContainerRequested':False,'watchtowerPublishedPorts':[],'changedEnvKeys':added}))
@@ -0,0 +1,55 @@
services:
easyai-server:
image: ${ACCEPT_SERVER_IMAGE}
labels:
com.centurylinklabs.watchtower.enable: "${ACCEPT_CORE_WATCHTOWER}"
environment:
SYSTEM_UPDATE_MODE: ${ACCEPT_UPDATE_MODE}
SYSTEM_UPDATE_DEPLOYMENT_ID: update-acceptance-20261002
WATCHTOWER_API_URL: http://watchtower:8080
WATCHTOWER_API_KEY: ${ACCEPT_WATCHTOWER_TOKEN}
WATCHTOWER_HTTP_API_EVENTS_TOKEN: ${ACCEPT_EVENTS_TOKEN}
APP_VERSION_CHECK_BASE_URL: http://update-version-catalog:18880
SYSTEM_UPDATE_INTERNAL_TOKEN: ${ACCEPT_UPDATER_TOKEN}
SYSTEM_UPDATE_SERVICE_URL: http://easyai-updater:3015
easyai-web:
image: ${ACCEPT_WEB_IMAGE}
labels:
com.centurylinklabs.watchtower.enable: "${ACCEPT_CORE_WATCHTOWER}"
environment:
NUXT_APP_CDN_URL: ""
NUXT_PUBLIC_BASE_APIURL: /api
NUXT_PUBLIC_BASE_SSRURL: http://easyai-server:3001
ws-gateway:
image: registry.cn-shanghai.aliyuncs.com/easyaigc/wsgateway@sha256:a2b5fb67384c7f0eca7c7a768b5deacb4c0d2195dfbeb6e16ef40d6dc097bf25
labels:
com.centurylinklabs.watchtower.enable: "${ACCEPT_CORE_WATCHTOWER}"
watchtower:
image: ${ACCEPT_WATCHTOWER_IMAGE}
command: ${ACCEPT_WATCHTOWER_COMMAND}
environment:
WATCHTOWER_HTTP_API_TOKEN: ${ACCEPT_WATCHTOWER_TOKEN}
WATCHTOWER_HTTP_API_EVENTS_TOKEN: ${ACCEPT_EVENTS_TOKEN}
WATCHTOWER_HTTP_API_HOST: 0.0.0.0
WATCHTOWER_HTTP_API_ENDPOINTS: health,update,metrics,events,history
WATCHTOWER_HTTP_API_PERIODIC_POLLS: "false"
WATCHTOWER_CLEANUP: "false"
easyai-updater:
image: registry.cn-shanghai.aliyuncs.com/easyaigc/easyai-updater:1.1.0
environment:
SYSTEM_UPDATE_INTERNAL_TOKEN: ${ACCEPT_UPDATER_TOKEN}
UPDATE_MANIFEST_FILE: /var/lib/easyai-updater/release-manifest-20261002.json
UPDATE_HEALTH_TIMEOUT_MS: "60000"
networks:
easyai:
ipv4_address: 172.21.0.31
update-version-catalog:
image: registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server:3.22.0.20261002.2
entrypoint: [node, -e]
command: ["require('node:http').createServer((q,s)=>{s.setHeader('content-type','application/json');s.end(JSON.stringify({status:'success',data:{version:'3.22.0.20261002.2'}}))}).listen(18880,'0.0.0.0')"]
labels:
com.centurylinklabs.watchtower.enable: "false"
restart: unless-stopped
networks:
easyai:
ipv4_address: 172.21.0.30
@@ -0,0 +1,7 @@
/easyai-server sha256:6825def1b7bf252eee477b34d3ddc0317e691f0787ea3f300793c4d272a3b491 running 0 healthy {"3001/tcp":[{"HostIp":"","HostPort":"3001"}]}
/easyai-web sha256:a287db622d13a706725520d3a9864f986b8854630d198fce3373a8e7f1cfe3b0 running 0 healthy {"3010/tcp":[{"HostIp":"","HostPort":"3010"}]}
/easyai-wsgateway sha256:a2b5fb67384c7f0eca7c7a768b5deacb4c0d2195dfbeb6e16ef40d6dc097bf25 running 0 {"3002/tcp":[{"HostIp":"","HostPort":"3002"}]}
/easyai-watchtower-1 sha256:6afd62a208687bb4840b3b8131e1461dee7033989e14e28b6d69082839eaf410 running 0 healthy {}
WARNING: Error parsing config file (/root/.docker/config.json): read /root/.docker/config.json: is a directory
WARNING: Error parsing config file (/root/.docker/config.json): read /root/.docker/config.json: is a directory
{"status":"ok","service":"easyai-server-main","version":"3.22.0.20261002.2","buildId":"3.22.0.20261002.2","timestamp":"2026-10-02T11:42:39.968Z","uptime":35.4169154,"dependencies":{"mongo":"ok","redis":"ok","rabbitmq":"ok"}}
@@ -0,0 +1,7 @@
history 200 {"api_version":"v1","count":0,"entries":[],"timestamp":"2026-10-02T11:43:51Z"}
status 204
events-with-update-token 401
events-with-event-token 200 text/event-stream
gateway-health 200
mode legacy_watchtower deployment newfound-101200186210 independentTokens true
registry.cn-shanghai.aliyuncs.com/comfy-ai/watchtower-aliyun:1.22.3 healthy {}
Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

@@ -0,0 +1,4 @@
{
"url": "https://newfound.top/api/app/version",
"text": "{\"status\":\"success\",\"data\":{\"version\":\"3.22.0.20261002.2\"},\"message\":\"success\"}"
}
@@ -0,0 +1,71 @@
[
{
"container": "easyai-server",
"imageId": "sha256:8493e5a4c82f6564d86925f4ab37061f6e6e3d9a6bb88db5fe5d25d3a7b7835b",
"imageRef": "registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server:latest",
"repoDigests": [
"registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server@sha256:8493e5a4c82f6564d86925f4ab37061f6e6e3d9a6bb88db5fe5d25d3a7b7835b"
],
"startedAt": "2026-10-02T08:57:10.888579571Z",
"restartCount": 0,
"health": null,
"ports": {
"3001/tcp": [
{
"HostIp": "",
"HostPort": "3001"
}
]
}
},
{
"container": "easyai-web",
"imageId": "sha256:de461512c2937f39010454b7aa30f8dfdc3d6bf5521f31722c756715d34d83ee",
"imageRef": "registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai:latest",
"repoDigests": [
"registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai@sha256:de461512c2937f39010454b7aa30f8dfdc3d6bf5521f31722c756715d34d83ee"
],
"startedAt": "2026-10-02T08:57:10.477953596Z",
"restartCount": 0,
"health": null,
"ports": {
"3010/tcp": [
{
"HostIp": "",
"HostPort": "3010"
}
]
}
},
{
"container": "easyai-wsgateway",
"imageId": "sha256:a2b5fb67384c7f0eca7c7a768b5deacb4c0d2195dfbeb6e16ef40d6dc097bf25",
"imageRef": "registry.cn-shanghai.aliyuncs.com/easyaigc/wsgateway:latest",
"repoDigests": [
"registry.cn-shanghai.aliyuncs.com/easyaigc/wsgateway@sha256:a2b5fb67384c7f0eca7c7a768b5deacb4c0d2195dfbeb6e16ef40d6dc097bf25"
],
"startedAt": "2026-09-28T07:22:48.994346059Z",
"restartCount": 0,
"health": null,
"ports": {
"3002/tcp": [
{
"HostIp": "",
"HostPort": "3002"
}
]
}
},
{
"container": "easyai-watchtower-1",
"imageId": "sha256:48700ed275baeedac5b6852b6ba8fbe6196dc8fd73fffdf89a5d19b59df10056",
"imageRef": "registry.cn-shanghai.aliyuncs.com/comfy-ai/watchtower-aliyun:latest",
"repoDigests": [
"registry.cn-shanghai.aliyuncs.com/comfy-ai/watchtower-aliyun@sha256:48700ed275baeedac5b6852b6ba8fbe6196dc8fd73fffdf89a5d19b59df10056"
],
"startedAt": "2026-04-16T01:35:39.204197353Z",
"restartCount": 0,
"health": "healthy",
"ports": {}
}
]
@@ -0,0 +1,3 @@
livez 200 OK
readyz 200 OK
startupz 200 OK
@@ -0,0 +1 @@
{"status":"success","data":{"version":"3.22.0.20260930.3"},"message":"success"}
+13
View File
@@ -191,6 +191,19 @@ if (-not $skipRepoUpdate) {
if (-not (Test-Path ".env")) { Write-Err "未找到 .env,请先执行 start.ps1 完成初始化" }
# 每次更新都执行版本化、幂等的环境配置迁移;已有非空自定义值保持不变。
. (Join-Path $scriptDir "scripts\Initialize-SecurityEnv.ps1")
$environmentPath = Join-Path $scriptDir ".env"
$environmentContent = Get-Content $environmentPath -Raw -Encoding UTF8
if (-not (Get-EnvValue $environmentContent "SYSTEM_UPDATE_MODE")) {
& docker inspect easyai-server *> $null
$hasServer = $LASTEXITCODE -eq 0
& docker inspect easyai-updater *> $null
$hasUpdater = $LASTEXITCODE -eq 0
if ($hasServer -and -not $hasUpdater) {
$environmentContent = Set-EnvValue $environmentContent "SYSTEM_UPDATE_MODE" "legacy_watchtower"
[System.IO.File]::WriteAllText($environmentPath, $environmentContent, [System.Text.UTF8Encoding]::new($false))
}
}
& (Join-Path $scriptDir "scripts\Apply-UpdateEnvMigrations.ps1") `
-EnvPath (Join-Path $scriptDir ".env") `
-ProjectRoot $scriptDir
+6
View File
@@ -142,6 +142,12 @@ fi
# 每次更新都执行版本化、幂等的环境配置迁移。迁移只补缺失/空值或必要的列表项,
# 不覆盖用户已经配置的非空值。后续新增必需配置时只需增加编号迁移文件。
# 已安装但没有 updater 的旧部署保留其更新方式;新安装仍默认 managed。
# shellcheck source=scripts/init-security-env.sh
. ./scripts/init-security-env.sh
if [ -z "$(read_env_value .env SYSTEM_UPDATE_MODE)" ] && docker inspect easyai-server >/dev/null 2>&1 && ! docker inspect easyai-updater >/dev/null 2>&1; then
write_env_value .env SYSTEM_UPDATE_MODE legacy_watchtower
fi
bash ./scripts/apply-update-env-migrations.sh .env "$SCRIPT_DIR"
# 老部署缺少绑定配置时,根据现有公开 API 地址迁移:域名/本机保持回环,IP 直连保持可用。