From 52913939d06710241172242bd8e67cf7dfbc0adb Mon Sep 17 00:00:00 2001
From: ddCat <2695092045@qq.com>
Date: Fri, 18 Sep 2026 08:50:18 +0800
Subject: [PATCH] =?UTF-8?q?fix(deploy):=20=E7=B3=BB=E7=BB=9F=E6=9B=B4?=
=?UTF-8?q?=E6=96=B0=20-=20=E9=9D=9E=E6=AD=A3=E5=BC=8F=E9=95=9C=E5=83=8F?=
=?UTF-8?q?=E6=A0=87=E7=AD=BE=E5=8F=AF=E5=9C=A8=E7=BA=BF=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=E5=88=B0=E6=AD=A3=E5=BC=8F=E7=89=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
sha-* 等非正式标签不再被字典序判成比 vX.Y.Z 更新;健康检查对非正式目标版本只确认服务就绪;管理后台更新页并排展示当前运行与最新发布版本并标注非正式版本
---
backend/internal/hostupdate/manager.go | 8 ++++-
backend/internal/hostupdate/manager_ops.go | 2 +-
backend/internal/hostupdate/version.go | 20 +++++++++--
backend/internal/hostupdate/version_test.go | 4 +++
docs/content/docs/backend/system-update.mdx | 4 +--
docs/content/docs/progress/pending-test.mdx | 7 ++++
.../admin/settings/system-update-page.tsx | 29 ++++++++++++---
web/src/styles/admin-ui.css | 36 +++++++++++++++++++
8 files changed, 99 insertions(+), 11 deletions(-)
diff --git a/backend/internal/hostupdate/manager.go b/backend/internal/hostupdate/manager.go
index 30f5d027e..ec8bf8d17 100644
--- a/backend/internal/hostupdate/manager.go
+++ b/backend/internal/hostupdate/manager.go
@@ -156,7 +156,11 @@ func (m *Manager) Check(ctx context.Context) (Status, error) {
}
if CompareVersions(current, release.Version) < 0 {
m.state.Operation.Phase = PhaseReady
- m.appendLogLocked(PhaseReady, fmt.Sprintf("发现新版本 %s", release.Version))
+ if IsReleaseVersion(current) {
+ m.appendLogLocked(PhaseReady, fmt.Sprintf("发现新版本 %s", release.Version))
+ } else {
+ m.appendLogLocked(PhaseReady, fmt.Sprintf("当前部署为 %s,可更新到正式版 %s", current, release.Version))
+ }
} else {
m.state.Operation.Phase = PhaseNoUpdate
m.appendLogLocked(PhaseNoUpdate, "当前已是最新版本")
@@ -430,6 +434,8 @@ func (m *Manager) checks(current string, currentErr error) []Check {
if currentErr != nil {
items[1].Status = "failed"
items[1].Detail = currentErr.Error()
+ } else if current != "" && !IsReleaseVersion(current) {
+ items[1].Detail = current + " · 非正式镜像标签,可切换到正式 Release"
}
if m.state.LastBackup != nil {
items[2].Status = "passed"
diff --git a/backend/internal/hostupdate/manager_ops.go b/backend/internal/hostupdate/manager_ops.go
index 4ce73b7b2..38d8958b5 100644
--- a/backend/internal/hostupdate/manager_ops.go
+++ b/backend/internal/hostupdate/manager_ops.go
@@ -518,7 +518,7 @@ func (m *Manager) checkHealthOnce(healthURL, targetVersion string) error {
if payload.Code != 0 {
return errors.New("健康接口业务状态异常")
}
- if payload.Data.Build.Version != "" && CompareVersions(payload.Data.Build.Version, targetVersion) != 0 {
+ if IsReleaseVersion(targetVersion) && payload.Data.Build.Version != "" && CompareVersions(payload.Data.Build.Version, targetVersion) != 0 {
return fmt.Errorf("运行版本仍为 %s,期望 %s", payload.Data.Build.Version, targetVersion)
}
return nil
diff --git a/backend/internal/hostupdate/version.go b/backend/internal/hostupdate/version.go
index 94f369b61..46e0685a8 100644
--- a/backend/internal/hostupdate/version.go
+++ b/backend/internal/hostupdate/version.go
@@ -10,12 +10,28 @@ type versionPart struct {
pre []string
}
+func IsReleaseVersion(raw string) bool {
+ _, ok := parseVersion(raw)
+ return ok
+}
+
func CompareVersions(left, right string) int {
a, aok := parseVersion(left)
b, bok := parseVersion(right)
- if !aok || !bok {
- return strings.Compare(strings.TrimSpace(left), strings.TrimSpace(right))
+ if aok && bok {
+ return compareParsedVersions(a, b)
}
+ // sha-5509b17、latest 等非正式标签不能按字典序和 v1.5.0 比,否则会被当成更新而不让升级。
+ if !aok && bok {
+ return -1
+ }
+ if aok && !bok {
+ return 1
+ }
+ return strings.Compare(strings.TrimSpace(left), strings.TrimSpace(right))
+}
+
+func compareParsedVersions(a, b versionPart) int {
for _, pair := range [][2]int{{a.major, b.major}, {a.minor, b.minor}, {a.patch, b.patch}} {
if pair[0] < pair[1] {
return -1
diff --git a/backend/internal/hostupdate/version_test.go b/backend/internal/hostupdate/version_test.go
index a09eba27a..e175b4728 100644
--- a/backend/internal/hostupdate/version_test.go
+++ b/backend/internal/hostupdate/version_test.go
@@ -12,6 +12,10 @@ func TestCompareVersions(t *testing.T) {
{"v1.2.0-preview.3", "v1.2.0", -1},
{"v1.2.1", "v1.2.0", 1},
{"v1.2.0", "v1.2.0", 0},
+ {"vsha-5509b17", "v1.5.0", -1},
+ {"sha-5509b17", "v1.5.0", -1},
+ {"v1.5.0", "vsha-5509b17", 1},
+ {"v1.2.9", "v1.5.0", -1},
}
for _, test := range tests {
got := CompareVersions(test.left, test.right)
diff --git a/docs/content/docs/backend/system-update.mdx b/docs/content/docs/backend/system-update.mdx
index 889d03aa0..afef118c0 100644
--- a/docs/content/docs/backend/system-update.mdx
+++ b/docs/content/docs/backend/system-update.mdx
@@ -13,8 +13,8 @@ description: Host Updater、Release 检查、数据库备份迁移、健康验
## 安全更新顺序
-1. 从 GitHub Releases 读取包含预览版在内的最高语义版本,并与 `.env` 中固定的 `CANVAS_IMAGE_TAG` 比较。
-2. 下载目标版本的 `docker-compose.deploy.yml`,执行 `docker compose config --quiet`,检查部署文件、磁盘空间和当前版本。
+1. 从 GitHub Releases 读取包含预览版在内的最高语义版本,并与 `.env` 中固定的 `CANVAS_IMAGE_TAG` 比较。`sha-*` 这类非正式镜像标签一律视为低于正式 Release,避免把 `vsha-5509b17` 字典序比较成比 `v1.5.0` 更新。
+2. 下载目标版本的 `docker-compose.deploy.yml`,执行 `docker compose config --quiet`,检查部署文件、磁盘空间和当前版本。当前标签不是 `vX.Y.Z` 时,健康检查只确认服务就绪,不要求构建号和镜像标签逐字相同。
3. 创建 `backup-YYYYMMDD-HHMMSS.zip.partial`,其中包含 PostgreSQL custom-format dump、后端数据目录 tar 和元数据;写入完成后 `fsync`、原子改名,并复核 ZIP 结构和 SHA-256。
4. 拉取目标 backend/web 镜像并要求存在仓库 `sha256` 摘要。任何失败都在停服前中止。
5. 停止 web/backend,等待 Backend 的 SIGTERM drain 完成;PostgreSQL 和 Redis 保持运行。
diff --git a/docs/content/docs/progress/pending-test.mdx b/docs/content/docs/progress/pending-test.mdx
index 9ce79d654..ebf188563 100644
--- a/docs/content/docs/progress/pending-test.mdx
+++ b/docs/content/docs/progress/pending-test.mdx
@@ -5,6 +5,13 @@ description: 当前项目已实现功能的手工验证清单与未确认边界
# 待测试
+## 开发镜像标签可在线更新到正式版(2026-09-18)
+
+`CANVAS_IMAGE_TAG=sha-5509b17` 这类非正式标签不再被字典序判成比 `v1.5.0` 更新。待登录管理后台「系统配置 / 系统更新」:
+
+- [ ] 当前运行显示 `vsha-...`,最新发布显示 `v1.5.0`(或当前正式版),状态是可更新而不是「已是最新」。
+- [ ] 「开始更新」可点;更新完成后 `CANVAS_IMAGE_TAG` 变成正式版本号,页面两边版本一致。
+
## 创作参数弹窗表面色(2026-09-17)
画幅 / 清晰度 / 时长弹窗改用工作台 `--user-surface-raised`,选中块用 `--user-control-pressed`,不再画 1px 描边。待登录后打开视频生成设置:
diff --git a/web/src/pages/admin/settings/system-update-page.tsx b/web/src/pages/admin/settings/system-update-page.tsx
index 7d8a7a96d..96f7210ba 100644
--- a/web/src/pages/admin/settings/system-update-page.tsx
+++ b/web/src/pages/admin/settings/system-update-page.tsx
@@ -178,7 +178,7 @@ export default function SystemUpdatePage() {
layout="stacked"
icon={
+ {isReleaseVersion(status.currentVersion) + ? `可以从 ${status.currentVersion} 更新到 ${status.latestRelease.version}。` + : `当前镜像标签不是正式版本号,所以会显示成 ${status.currentVersion}。点开始更新后会切换到 ${status.latestRelease.version}。`} +
+ ) : null}