fix(deploy): 随部署仓库下发正式更新清单

This commit is contained in:
2026-09-27 11:16:41 +08:00
parent 195e2e5a98
commit a00ee10b37
4 changed files with 72 additions and 1 deletions
+2
View File
@@ -183,6 +183,8 @@ update.sh 脚本用于自动更新 EasyAI 应用,包含以下功能:
更新执行器 `easyai-updater` 由 Watchtower 单独守护,默认每 24 小时检查并更新自身。它的任务状态保存在宿主机 `data/update-state` 中,自身更新或意外重启后会继续恢复未完成任务;Watchtower 不再接管三个核心服务,因此页面任务结果不会与后台静默替换相互冲突。 更新执行器 `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` 应用以下缺省值: 存量部署无需手工编辑 `.env`。更新脚本会从 `scripts/update-env.d/010-managed-system-update.env` 应用以下缺省值:
```dotenv ```dotenv
+2 -1
View File
@@ -338,10 +338,11 @@ services:
- UPDATE_HEALTH_TIMEOUT_MS=${UPDATE_HEALTH_TIMEOUT_MS:-300000} - UPDATE_HEALTH_TIMEOUT_MS=${UPDATE_HEALTH_TIMEOUT_MS:-300000}
- DOCKER_CONFIG_FILE=/run/secrets/docker-config.json - DOCKER_CONFIG_FILE=/run/secrets/docker-config.json
- UPDATE_STATE_DIR=/var/lib/easyai-updater - UPDATE_STATE_DIR=/var/lib/easyai-updater
- UPDATE_MANIFEST_FILE=/var/lib/easyai-updater/release-manifest.json - UPDATE_MANIFEST_FILE=/etc/easyai-updater/release-manifest.json
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- ${DOCKER_CONFIG_FILE:-/root/.docker/config.json}:/run/secrets/docker-config.json:ro - ${DOCKER_CONFIG_FILE:-/root/.docker/config.json}:/run/secrets/docker-config.json:ro
- ./system-update/release-manifest.json:/etc/easyai-updater/release-manifest.json:ro
- ./data/update-state:/var/lib/easyai-updater - ./data/update-state:/var/lib/easyai-updater
tmpfs: tmpfs:
- /tmp:mode=1777 - /tmp:mode=1777
+31
View File
@@ -17,6 +17,7 @@ cp \
"$REPO_ROOT/easyai-proxy.conf.sample" \ "$REPO_ROOT/easyai-proxy.conf.sample" \
"$TMP_DIR/" "$TMP_DIR/"
mkdir -p "$TMP_DIR/scripts" mkdir -p "$TMP_DIR/scripts"
mkdir -p "$TMP_DIR/system-update"
cp \ cp \
"$REPO_ROOT/scripts/init-security-env.sh" \ "$REPO_ROOT/scripts/init-security-env.sh" \
"$REPO_ROOT/scripts/init-security-origin.sh" \ "$REPO_ROOT/scripts/init-security-origin.sh" \
@@ -24,6 +25,7 @@ cp \
"$REPO_ROOT/scripts/init-server-http-bind-ip.sh" \ "$REPO_ROOT/scripts/init-server-http-bind-ip.sh" \
"$REPO_ROOT/scripts/check-redis-persistence.sh" \ "$REPO_ROOT/scripts/check-redis-persistence.sh" \
"$TMP_DIR/scripts/" "$TMP_DIR/scripts/"
cp "$REPO_ROOT/system-update/release-manifest.json" "$TMP_DIR/system-update/"
cd "$TMP_DIR" cd "$TMP_DIR"
@@ -166,6 +168,7 @@ mounts = {(item.get("source"), item.get("target")) for item in updater.get("volu
assert ("/var/run/docker.sock", "/var/run/docker.sock") in mounts, mounts assert ("/var/run/docker.sock", "/var/run/docker.sock") in mounts, mounts
assert any(target == "/var/lib/easyai-updater" for _, target in mounts), mounts assert any(target == "/var/lib/easyai-updater" for _, target in mounts), mounts
assert any(target == "/run/secrets/docker-config.json" for _, target in mounts), mounts assert any(target == "/run/secrets/docker-config.json" for _, target in mounts), mounts
assert any(target == "/etc/easyai-updater/release-manifest.json" for _, target in mounts), mounts
server_env = server.get("environment") or {} server_env = server.get("environment") or {}
updater_env = updater.get("environment") or {} updater_env = updater.get("environment") or {}
@@ -176,6 +179,7 @@ assert server_env.get("SYSTEM_UPDATE_MODE") == "managed", server_env.get("SYSTEM
assert str(server_env.get("EASYAI_DEPLOYMENT_SCHEMA_VERSION")) == "2" assert str(server_env.get("EASYAI_DEPLOYMENT_SCHEMA_VERSION")) == "2"
assert str(updater_env.get("UPDATE_HEALTH_TIMEOUT_MS")) == "300000" assert str(updater_env.get("UPDATE_HEALTH_TIMEOUT_MS")) == "300000"
assert updater_env.get("DOCKER_CONFIG_FILE") == "/run/secrets/docker-config.json" assert updater_env.get("DOCKER_CONFIG_FILE") == "/run/secrets/docker-config.json"
assert updater_env.get("UPDATE_MANIFEST_FILE") == "/etc/easyai-updater/release-manifest.json"
assert updater.get("labels", {}).get("com.centurylinklabs.watchtower.enable") == "true" assert updater.get("labels", {}).get("com.centurylinklabs.watchtower.enable") == "true"
assert watchtower.get("environment", {}).get("WATCHTOWER_POLL_INTERVAL") == "86400" assert watchtower.get("environment", {}).get("WATCHTOWER_POLL_INTERVAL") == "86400"
assert watchtower.get("environment", {}).get("WATCHTOWER_CLEANUP") == "true" assert watchtower.get("environment", {}).get("WATCHTOWER_CLEANUP") == "true"
@@ -185,6 +189,33 @@ for service in ("easyai-server", "ws-gateway", "easyai-web"):
labels = services[service].get("labels") or {} labels = services[service].get("labels") or {}
assert labels.get("com.centurylinklabs.watchtower.enable") == "false", (service, labels) assert labels.get("com.centurylinklabs.watchtower.enable") == "false", (service, labels)
' '
python3 - <<'PY'
import json
import re
from pathlib import Path
manifest = json.loads(Path("system-update/release-manifest.json").read_text())
assert manifest["schemaVersion"] == 1
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["minimumDeploymentSchemaVersion"] == 2
assert int(manifest["requiredFreeBytes"]) > 0
components = {item["key"]: item for item in manifest["components"]}
assert set(components) == {"server-main", "ws-gateway", "web"}
expected_images = {
"server-main": "registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server",
"ws-gateway": "registry.cn-shanghai.aliyuncs.com/easyaigc/wsgateway",
"web": "registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai",
}
for key, image in expected_images.items():
assert components[key]["image"] == image
assert re.fullmatch(r"sha256:[a-f0-9]{64}", components[key]["digest"])
assert int(components[key]["imageSizeBytes"]) > 0
PY
} }
assert_legacy_update_contract() { assert_legacy_update_contract() {
+37
View File
@@ -0,0 +1,37 @@
{
"schemaVersion": 1,
"channel": "stable",
"releaseId": "managed-update-20260927",
"productVersion": "3.18.0.20260927",
"platform": "linux/amd64",
"publishedAt": "2026-09-27T11:15:00+08:00",
"managedUpdateAllowed": true,
"scheduledUpdateAllowed": true,
"rollbackSafe": true,
"minimumUpdaterVersion": "1.0.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"
},
{
"key": "ws-gateway",
"image": "registry.cn-shanghai.aliyuncs.com/easyaigc/wsgateway",
"digest": "sha256:661eb2dc5ca6e458e00b8c2e2f068c2a1773694a3017982ecb1aa42de677468e",
"imageSizeBytes": 1982899440,
"buildId": "db43ef28"
},
{
"key": "web",
"image": "registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai",
"digest": "sha256:bc82b10e08886904b2ef4a5c2fe7727b1dac9114c7493b4f2acc0b1a8ef5e29c",
"imageSizeBytes": 1038564864,
"buildId": "188b0ee9"
}
]
}