feat(hermes-agent): support version upgrades (#14079)

This commit is contained in:
CityFun
2026-10-14 11:28:32 +08:00
committed by GitHub
parent 12bfe6a1bd
commit 3b89d83554
17 changed files with 155 additions and 12 deletions
+4 -4
View File
@@ -652,7 +652,7 @@ func checkAgentUpgradable(install model.AppInstall) bool {
if len(ignores) > 0 {
continue
}
if common.IsCrossVersion(install.Version, item.Version) && !install.App.CrossVersionUpdate {
if isCrossAppVersion(install.App.Key, install.Version, item.Version) && !install.App.CrossVersionUpdate {
continue
}
versions = append(versions, item.Version)
@@ -660,12 +660,12 @@ func checkAgentUpgradable(install model.AppInstall) bool {
if len(versions) == 0 {
return false
}
versions = common.GetSortedVersions(versions)
versions = sortAppVersions(install.App.Key, versions)
lastVersion := versions[0]
if common.IsCrossVersion(install.Version, lastVersion) {
if isCrossAppVersion(install.App.Key, install.Version, lastVersion) {
return install.App.CrossVersionUpdate
}
return common.CompareVersion(lastVersion, install.Version)
return isNewerAppVersion(install.App.Key, lastVersion, install.Version)
}
type openclawConfig struct {
+3 -3
View File
@@ -694,10 +694,10 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d
if len(ignores) > 0 {
continue
}
if common.IsCrossVersion(install.Version, detail.Version) && !app.CrossVersionUpdate {
if isCrossAppVersion(app.Key, install.Version, detail.Version) && !app.CrossVersionUpdate {
continue
}
canUpgrade := common.CompareVersion(detail.Version, install.Version)
canUpgrade := isNewerAppVersion(app.Key, detail.Version, install.Version)
if app.Key == vllmAppKeyForUpgrade {
currentImage := loadVllmImageFromEnv(install.Env)
canUpgrade = isVllmUpgradeCandidate(install.Version, detail.Version, currentImage)
@@ -738,7 +738,7 @@ func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]d
}
}
sort.Slice(versions, func(i, j int) bool {
return common.CompareVersion(versions[i].Version, versions[j].Version)
return appVersionSortBefore(app.Key, versions[i].Version, versions[j].Version)
})
return versions, nil
}
+5
View File
@@ -109,6 +109,11 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
if !isAIGatewayUpgradeVersionAllowed(install.App.Key, install.Version, detail.Version) {
return buserr.New("ErrAIGatewayDatabaseTypeMismatch")
}
if isHermesApp(install.App.Key) &&
(!isNewerAppVersion(install.App.Key, detail.Version, install.Version) ||
(isCrossAppVersion(install.App.Key, install.Version, detail.Version) && !install.App.CrossVersionUpdate)) {
return buserr.New("ErrHermesUpgradeVersion")
}
if err = checkVllmVersionAccess(install.App.Key, detail.Version); err != nil {
return err
}
+5 -5
View File
@@ -1578,7 +1578,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
if len(ignores) > 0 {
continue
}
if common.IsCrossVersion(installed.Version, appDetail.Version) && !installed.App.CrossVersionUpdate {
if isCrossAppVersion(installed.App.Key, installed.Version, appDetail.Version) && !installed.App.CrossVersionUpdate {
continue
}
versions = append(versions, appDetail.Version)
@@ -1593,7 +1593,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
continue
}
versions = common.GetSortedVersions(versions)
versions = sortAppVersions(installed.App.Key, versions)
lastVersion := versions[0]
if installed.App.Key == constant.AppMysql || installed.App.Key == constant.AppMysqlCluster {
@@ -1608,10 +1608,10 @@ func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpda
}
}
if common.IsCrossVersion(installed.Version, lastVersion) {
if isCrossAppVersion(installed.App.Key, installed.Version, lastVersion) {
installDTO.CanUpdate = installed.App.CrossVersionUpdate
} else {
installDTO.CanUpdate = common.CompareVersion(lastVersion, installed.Version)
installDTO.CanUpdate = isNewerAppVersion(installed.App.Key, lastVersion, installed.Version)
}
if updated {
@@ -2286,7 +2286,7 @@ func getAppVersions(key string, details []model.AppDetail) []string {
sortAIGatewayVersions(versionsRaw)
versions = versionsRaw
} else {
versions = common.GetSortedVersions(versionsRaw)
versions = sortAppVersions(key, versionsRaw)
}
if hasLatest {
versions = append([]string{latestVersion}, versions...)
+126
View File
@@ -0,0 +1,126 @@
package service
import (
"regexp"
"sort"
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/utils/common"
)
// Release names at https://github.com/NousResearch/hermes-agent/releases pair
// these historical calendar tags with their semantic versions. This includes
// every calendar version published in the 1Panel app store. Do not rewrite
// stored versions: backup metadata, restore and rollback retain the original tag.
var hermesHistoricalVersions = map[string]string{
"2026.3.12": "0.2.0",
"2026.3.17": "0.3.0",
"2026.3.23": "0.4.0",
"2026.3.28": "0.5.0",
"2026.3.30": "0.6.0",
"2026.4.3": "0.7.0",
"2026.4.8": "0.8.0",
"2026.4.13": "0.9.0",
"2026.4.16": "0.10.0",
"2026.4.23": "0.11.0",
"2026.4.30": "0.12.0",
"2026.5.7": "0.13.0",
"2026.5.16": "0.14.0",
"2026.5.28": "0.15.0",
"2026.5.29": "0.15.1",
"2026.5.29.2": "0.15.2",
"2026.6.5": "0.16.0",
"2026.6.19": "0.17.0",
"2026.7.1": "0.18.0",
"2026.7.7": "0.18.1",
"2026.7.7.2": "0.18.2",
"2026.7.20": "0.19.0",
"2026.7.30": "0.19.1",
"2026.8.3": "0.20.0",
"2026.8.13": "0.20.1",
"2026.8.16": "0.20.2",
"2026.8.16.2": "0.20.3",
"2026.8.18": "0.20.4",
"2026.8.19": "0.20.5",
"2026.8.27": "0.20.6",
"2026.8.31": "0.21.0",
"2026.9.7": "0.21.1",
"2026.9.11": "0.21.2",
"2026.9.14": "0.21.3",
"2026.9.21": "0.21.4",
"2026.9.24": "0.21.5",
}
var hermesReleaseVersion = regexp.MustCompile(`^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$`)
func isHermesApp(key string) bool {
return key == constant.AppHermesAgent || key == "local"+constant.AppHermesAgent
}
func hermesComparableVersion(version string) (string, bool) {
version = strings.TrimPrefix(version, "v")
if mapped, ok := hermesHistoricalVersions[version]; ok {
return mapped, true
}
parts := hermesReleaseVersion.FindStringSubmatch(version)
if len(parts) == 0 {
return "", false
}
for i, part := range parts[1:] {
n, err := strconv.Atoi(part)
if err != nil || (i == 0 && n >= 2000) {
// An unknown calendar tag must not become a huge semantic major.
return "", false
}
}
return version, true
}
func isNewerAppVersion(key, target, current string) bool {
if !isHermesApp(key) {
return common.CompareVersion(target, current)
}
targetVersion, targetOK := hermesComparableVersion(target)
currentVersion, currentOK := hermesComparableVersion(current)
return targetOK && currentOK && common.CompareVersion(targetVersion, currentVersion)
}
func isCrossAppVersion(key, current, target string) bool {
if !isHermesApp(key) {
return common.IsCrossVersion(current, target)
}
currentVersion, currentOK := hermesComparableVersion(current)
targetVersion, targetOK := hermesComparableVersion(target)
return currentOK && targetOK && common.IsCrossVersion(currentVersion, targetVersion)
}
func appVersionSortBefore(key, left, right string) bool {
if !isHermesApp(key) {
return common.CompareVersion(left, right)
}
lv, lok := hermesComparableVersion(left)
rv, rok := hermesComparableVersion(right)
if lok != rok {
return lok
}
if !lok {
return common.CompareVersion(left, right)
}
if lv != rv {
return common.CompareVersion(lv, rv)
}
// Prefer the official semantic spelling when both aliases are available.
return strings.TrimPrefix(left, "v") == lv && strings.TrimPrefix(right, "v") != rv
}
func sortAppVersions(key string, versions []string) []string {
if !isHermesApp(key) {
return common.GetSortedVersions(versions)
}
sort.SliceStable(versions, func(i, j int) bool {
return appVersionSortBefore(key, versions[i], versions[j])
})
return versions
}
+1
View File
@@ -142,6 +142,7 @@ ErrAppVersionUnavailable: 'This application version has been removed from the re
ErrVllmGB10ProOnly: 'This vLLM version is available only in 1Panel Professional Edition.'
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway can only upgrade within the same database type (SQLite or PostgreSQL).'
ErrAIGatewayRecoverDatabaseTypeMismatch: 'The AI Gateway backup and target application must use the same database type (SQLite or PostgreSQL).'
ErrHermesUpgradeVersion: 'Hermes Agent can only upgrade to a newer recognized release allowed by its cross-version upgrade policy. Restore an older version from a backup instead.'
ErrAppWarn: 'App status abnormal; check logs'
ErrAppParamKey: 'Invalid app parameter: {{ .name }}'
ErrAppUpgrade: 'App upgrade failed: {{ .name }} {{ .err }}'
+1
View File
@@ -142,6 +142,7 @@ ErrAppVersionUnavailable: 'Esta versión de la aplicación se ha eliminado del s
ErrVllmGB10ProOnly: 'Esta versión de vLLM solo está disponible en 1Panel Professional Edition.'
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway solo puede actualizarse dentro del mismo tipo de base de datos (SQLite o PostgreSQL).'
ErrAIGatewayRecoverDatabaseTypeMismatch: 'La copia de seguridad de AI Gateway y la aplicación de destino deben usar el mismo tipo de base de datos (SQLite o PostgreSQL).'
ErrHermesUpgradeVersion: 'Hermes Agent solo puede actualizarse a una versión reconocida más reciente permitida por la configuración de actualización entre versiones principales. Para volver a una versión anterior, restaure una copia de seguridad.'
ErrAppWarn: 'Estado anómalo, revise el log'
ErrAppParamKey: 'El campo de parámetro {{ .name }} es anómalo'
ErrAppUpgrade: 'La actualización de la aplicación {{ .name }} falló {{ .err }}'
+1
View File
@@ -142,6 +142,7 @@ ErrAppVersionUnavailable: 'این نسخه برنامه از سرویس راه
ErrVllmGB10ProOnly: 'این نسخه vLLM فقط در نسخه حرفه‌ای 1Panel در دسترس است.'
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway فقط در همان نوع پایگاه داده (SQLite یا PostgreSQL) قابل ارتقا است.'
ErrAIGatewayRecoverDatabaseTypeMismatch: 'نسخه پشتیبان AI Gateway و برنامه مقصد باید از یک نوع پایگاه داده (SQLite یا PostgreSQL) استفاده کنند.'
ErrHermesUpgradeVersion: 'Hermes Agent فقط می‌تواند به نسخه جدیدتر و شناخته‌شده‌ای ارتقا یابد که تنظیمات ارتقا بین نسخه‌های اصلی اجازه می‌دهد. برای بازگشت به نسخه قدیمی، نسخه پشتیبان را بازیابی کنید.'
ErrAppWarn: 'وضعیت برنامه غیرعادی است؛ لاگ‌ها را بررسی کنید'
ErrAppParamKey: 'پارامتر برنامه نامعتبر است: {{ .name }}'
ErrAppUpgrade: 'ارتقاء برنامه ناموفق بود: {{ .name }} {{ .err }}'
+1
View File
@@ -142,6 +142,7 @@ ErrAppVersionUnavailable: '現在のアプリケーションバージョンは
ErrVllmGB10ProOnly: 'この vLLM バージョンは 1Panel プロフェッショナル版でのみ利用できます。'
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway は同じデータベース種別(SQLite または PostgreSQL)間でのみアップグレードできます。'
ErrAIGatewayRecoverDatabaseTypeMismatch: 'AI Gateway のバックアップと復元先アプリのデータベース種別(SQLite または PostgreSQL)は一致している必要があります。'
ErrHermesUpgradeVersion: 'Hermes Agent は、認識可能でメジャーバージョン間のアップグレード設定に適合する新しいリリースにのみアップグレードできます。以前のバージョンに戻すには、バックアップから復元してください。'
ErrAppWarn: '異常な状態です。ログを確認してください'
ErrAppParamKey: 'パラメータ {{ .name }} フィールドが異常です'
ErrAppUpgrade: 'アプリケーション {{ .name }} のアップグレードに失敗しました {{ .err }}'
+1
View File
@@ -142,6 +142,7 @@ ErrAppVersionUnavailable: '현재 애플리케이션 버전이 원격 서비스
ErrVllmGB10ProOnly: '이 vLLM 버전은 1Panel 프로페셔널 에디션에서만 사용할 수 있습니다.'
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway는 동일한 데이터베이스 유형(SQLite 또는 PostgreSQL) 내에서만 업그레이드할 수 있습니다.'
ErrAIGatewayRecoverDatabaseTypeMismatch: 'AI Gateway 백업과 대상 애플리케이션의 데이터베이스 유형(SQLite 또는 PostgreSQL)이 같아야 합니다.'
ErrHermesUpgradeVersion: 'Hermes Agent는 인식 가능하고 주 버전 간 업그레이드 설정을 충족하는 최신 릴리스로만 업그레이드할 수 있습니다. 이전 버전으로 되돌리려면 백업을 복원하세요.'
ErrAppWarn: '비정상적인 상태입니다. 로그를 확인해 주세요.'
ErrAppParamKey: '매개변수 {{ .name }} 필드가 비정상입니다.'
ErrAppUpgrade: '애플리케이션 {{ .name }} 업그레이드에 실패했습니다 {{ .err }}'
+1
View File
@@ -132,6 +132,7 @@ ErrAppVersionUnavailable: 'ເວີຊັນແອັບນີ້ຖືກລ
ErrVllmGB10ProOnly: 'ເວີຊັນ vLLM ນີ້ມີໃຫ້ໃຊ້ສະເພາະໃນ 1Panel Professional Edition ເທົ່ານັ້ນ.'
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway ສາມາດອັບເກຣດໄດ້ພາຍໃນປະເພດຖານຂໍ້ມູນດຽວກັນເທົ່ານັ້ນ (SQLite ຫຼື PostgreSQL).'
ErrAIGatewayRecoverDatabaseTypeMismatch: 'ຂໍ້ມູນສຳຮອງ AI Gateway ແລະ ແອັບເປົ້າໝາຍຕ້ອງໃຊ້ປະເພດຖານຂໍ້ມູນດຽວກັນ (SQLite ຫຼື PostgreSQL).'
ErrHermesUpgradeVersion: 'Hermes Agent ສາມາດອັບເກຣດໄປຫາລຸ້ນໃໝ່ກວ່າທີ່ຮູ້ຈັກ ແລະ ອະນຸຍາດໂດຍການຕັ້ງຄ່າອັບເກຣດຂ້າມລຸ້ນຫຼັກເທົ່ານັ້ນ. ຖ້າຕ້ອງການກັບໄປລຸ້ນເກົ່າ ໃຫ້ກູ້ຄືນຈາກຂໍ້ມູນສຳຮອງ.'
ErrAppWarn: 'ສະຖານະແອັບຜິດປົກກະຕິ; ກະລຸນາກວດສອບບັນທຶກ'
ErrAppParamKey: 'ພາຣາມິເຕີຂອງແອັບບໍ່ຖືກຕ້ອງ: {{ .name }}'
ErrAppUpgrade: 'ອັບເກຣດແອັບລົ້ມເຫຼວ: {{ .name }} {{ .err }}'
+1
View File
@@ -142,6 +142,7 @@ ErrAppVersionUnavailable: 'Versi aplikasi ini telah dialih keluar daripada perkh
ErrVllmGB10ProOnly: 'Versi vLLM ini hanya tersedia dalam 1Panel Edisi Profesional.'
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway hanya boleh dinaik taraf dalam jenis pangkalan data yang sama (SQLite atau PostgreSQL).'
ErrAIGatewayRecoverDatabaseTypeMismatch: 'Sandaran AI Gateway dan aplikasi sasaran mesti menggunakan jenis pangkalan data yang sama (SQLite atau PostgreSQL).'
ErrHermesUpgradeVersion: 'Hermes Agent hanya boleh dinaik taraf kepada keluaran lebih baharu yang dikenal pasti dan dibenarkan oleh tetapan naik taraf antara versi utama. Untuk kembali kepada versi lama, pulihkan sandaran.'
ErrAppWarn: 'Status tidak normal, sila semak log'
ErrAppParamKey: 'Parameter {{ .name }} medan tidak normal'
ErrAppUpgrade: 'Peningkatan {{ .name }} aplikasi gagal {{ .err }}'
+1
View File
@@ -142,6 +142,7 @@ ErrAppVersionUnavailable: 'Esta versão do aplicativo foi removida do serviço r
ErrVllmGB10ProOnly: 'Esta versão do vLLM está disponível apenas no 1Panel Professional Edition.'
ErrAIGatewayDatabaseTypeMismatch: 'O AI Gateway só pode ser atualizado dentro do mesmo tipo de banco de dados (SQLite ou PostgreSQL).'
ErrAIGatewayRecoverDatabaseTypeMismatch: 'O backup do AI Gateway e o aplicativo de destino devem usar o mesmo tipo de banco de dados (SQLite ou PostgreSQL).'
ErrHermesUpgradeVersion: 'O Hermes Agent só pode ser atualizado para uma versão reconhecida mais recente permitida pela configuração de atualização entre versões principais. Para voltar a uma versão anterior, restaure um backup.'
ErrAppWarn: 'Status anormal, verifique o log'
ErrAppParamKey: 'O campo de parâmetro {{ .name }} está anormal'
ErrAppUpgrade: 'Falha na atualização do aplicativo {{ .name }} {{ .err }}'
+1
View File
@@ -142,6 +142,7 @@ ErrAppVersionUnavailable: 'Эта версия приложения удален
ErrVllmGB10ProOnly: 'Эта версия vLLM доступна только в профессиональной редакции 1Panel.'
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway можно обновлять только в пределах одного типа базы данных (SQLite или PostgreSQL).'
ErrAIGatewayRecoverDatabaseTypeMismatch: 'Резервная копия AI Gateway и целевое приложение должны использовать один и тот же тип базы данных (SQLite или PostgreSQL).'
ErrHermesUpgradeVersion: 'Hermes Agent можно обновить только до более нового распознанного выпуска, разрешённого настройкой обновления между основными версиями. Для возврата к старой версии восстановите резервную копию.'
ErrAppWarn: 'Ненормальное состояние, проверьте журнал'
ErrAppParamKey: 'Поле параметра {{ .name }} ненормально'
ErrAppUpgrade: 'Обновление приложения {{ .name }} не удалось {{ .err }}'
+1
View File
@@ -142,6 +142,7 @@ ErrAppVersionUnavailable: 'Bu uygulama sürümü uzak hizmetten kaldırıldı. L
ErrVllmGB10ProOnly: 'Bu vLLM sürümü yalnızca 1Panel Professional Edition sürümünde kullanılabilir.'
ErrAIGatewayDatabaseTypeMismatch: 'AI Gateway yalnızca aynı veritabanı türü (SQLite veya PostgreSQL) içinde yükseltilebilir.'
ErrAIGatewayRecoverDatabaseTypeMismatch: 'AI Gateway yedeği ve hedef uygulama aynı veritabanı türünü (SQLite veya PostgreSQL) kullanmalıdır.'
ErrHermesUpgradeVersion: 'Hermes Agent yalnızca ana sürümler arası yükseltme ayarının izin verdiği, tanınan daha yeni bir sürüme yükseltilebilir. Eski bir sürüme dönmek için yedekten geri yükleyin.'
ErrAppWarn: 'Anormal durum, lütfen günlüğü kontrol edin'
ErrAppParamKey: 'Parametre {{ .name }} alanı anormal'
ErrAppUpgrade: 'Uygulama {{ .name }} yükseltmesi başarısız {{ .err }}'
+1
View File
@@ -142,6 +142,7 @@ ErrAppVersionUnavailable: '目前應用程式版本已從遠端服務下架,
ErrVllmGB10ProOnly: '此 vLLM 版本僅支援 1Panel 專業版'
ErrAIGatewayDatabaseTypeMismatch: 'AI 閘道只能在相同資料庫類型(SQLite 或 PostgreSQL)之間升級。'
ErrAIGatewayRecoverDatabaseTypeMismatch: 'AI 閘道備份與目標應用程式的資料庫類型(SQLite 或 PostgreSQL)必須一致,無法跨資料庫類型還原。'
ErrHermesUpgradeVersion: 'Hermes Agent 只能升級至可辨識且符合跨版本升級設定的較新版本。如需還原舊版本,請使用備份還原。'
ErrAppWarn: '狀態異常,請檢視日誌'
ErrAppParamKey: '參數{{ .name }} 欄位異常'
ErrAppUpgrade: '應用程式{{ .name }} 升級失敗{{ .err }}'
+1
View File
@@ -142,6 +142,7 @@ ErrAppVersionUnavailable: "当前应用版本已从远程服务下架,请选
ErrVllmGB10ProOnly: "该 vLLM 版本仅支持 1Panel 专业版"
ErrAIGatewayDatabaseTypeMismatch: 'AI 网关只能在相同数据库类型(SQLite 或 PostgreSQL)之间升级。'
ErrAIGatewayRecoverDatabaseTypeMismatch: 'AI 网关备份与目标应用的数据库类型(SQLite 或 PostgreSQL)必须一致,无法跨数据库类型恢复。'
ErrHermesUpgradeVersion: 'Hermes Agent 只能升级到可识别且符合跨版本升级设置的更新版本。如需恢复旧版本,请使用备份恢复。'
ErrAppWarn: "状态异常,请查看日志"
ErrAppParamKey: "参数 {{ .name }} 字段异常"
ErrAppUpgrade: "应用 {{ .name }} 升级失败 {{ .err }}"