170 lines
5.7 KiB
Go
170 lines
5.7 KiB
Go
package admin
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"runtime"
|
|
|
|
"github.com/gogf/gf/v2/errors/gcode"
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
|
|
v1 "github.com/qianfree/team-api/api/admin/v1"
|
|
"github.com/qianfree/team-api/internal/consts"
|
|
"github.com/qianfree/team-api/internal/logic/update"
|
|
)
|
|
|
|
// CheckUpdate checks GitHub Releases for a newer version
|
|
func (s *sAdmin) UpdateCheck(ctx context.Context, req *v1.UpdateCheckReq) (*v1.UpdateCheckRes, error) {
|
|
result, err := update.CheckForUpdate(ctx, req.Force)
|
|
if err != nil {
|
|
return nil, gerror.NewCode(gcode.New(500, err.Error(), nil))
|
|
}
|
|
|
|
return &v1.UpdateCheckRes{
|
|
CurrentVersion: result.CurrentVersion,
|
|
LatestVersion: result.LatestVersion,
|
|
HasUpdate: result.HasUpdate,
|
|
ReleaseNotes: result.ReleaseNotes,
|
|
ReleaseURL: result.ReleaseURL,
|
|
PublishedAt: result.PublishedAt,
|
|
CheckedAt: result.CheckedAt,
|
|
DeploymentMode: result.DeploymentMode,
|
|
}, nil
|
|
}
|
|
|
|
// GetUpdateStatus returns the current update status
|
|
func (s *sAdmin) UpdateStatus(ctx context.Context, req *v1.UpdateStatusReq) (*v1.UpdateStatusRes, error) {
|
|
mgr := update.GetManager()
|
|
status := mgr.GetStatus()
|
|
|
|
res := &v1.UpdateStatusRes{
|
|
CurrentVersion: status.CurrentVersion,
|
|
DeploymentMode: status.DeploymentMode,
|
|
Updating: status.Updating,
|
|
RollbackAvailable: status.RollbackAvailable,
|
|
BackupVersion: status.BackupVersion,
|
|
}
|
|
|
|
if status.LastCheck != nil {
|
|
res.LastCheck = &v1.UpdateCheckRes{
|
|
CurrentVersion: status.LastCheck.CurrentVersion,
|
|
LatestVersion: status.LastCheck.LatestVersion,
|
|
HasUpdate: status.LastCheck.HasUpdate,
|
|
ReleaseNotes: status.LastCheck.ReleaseNotes,
|
|
ReleaseURL: status.LastCheck.ReleaseURL,
|
|
PublishedAt: status.LastCheck.PublishedAt,
|
|
CheckedAt: status.LastCheck.CheckedAt,
|
|
DeploymentMode: status.LastCheck.DeploymentMode,
|
|
}
|
|
}
|
|
|
|
// Also check for cached check result
|
|
if res.LastCheck == nil {
|
|
cached := mgr.GetCheckResult()
|
|
if cached != nil {
|
|
res.LastCheck = &v1.UpdateCheckRes{
|
|
CurrentVersion: cached.CurrentVersion,
|
|
LatestVersion: cached.LatestVersion,
|
|
HasUpdate: cached.HasUpdate,
|
|
ReleaseNotes: cached.ReleaseNotes,
|
|
ReleaseURL: cached.ReleaseURL,
|
|
PublishedAt: cached.PublishedAt,
|
|
CheckedAt: cached.CheckedAt,
|
|
DeploymentMode: cached.DeploymentMode,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check rollback availability from disk
|
|
if !res.RollbackAvailable {
|
|
info, err := update.GetRollbackInfo()
|
|
if err == nil && info != nil {
|
|
res.RollbackAvailable = true
|
|
res.BackupVersion = info.BackupVersion
|
|
}
|
|
}
|
|
|
|
if status.Progress != nil {
|
|
res.UpdateProgress = &v1.UpdateProgress{
|
|
Phase: status.Progress.Phase,
|
|
Message: status.Progress.Message,
|
|
Percentage: status.Progress.Percentage,
|
|
Error: status.Progress.Error,
|
|
}
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// ExecuteUpdate triggers the system update
|
|
func (s *sAdmin) UpdateExecute(ctx context.Context, req *v1.UpdateExecuteReq) (*v1.UpdateExecuteRes, error) {
|
|
// Docker mode check
|
|
if update.IsDocker() {
|
|
return nil, gerror.NewCode(gcode.New(consts.CodeUpdateNotSupported, consts.MsgUpdateNotSupported, nil))
|
|
}
|
|
|
|
// Windows 不支持在线更新:无 exec 换壳语义,且运行中二进制无法被重命名
|
|
if runtime.GOOS == "windows" {
|
|
return nil, gerror.NewCode(gcode.New(consts.CodeUpdateNotSupported, "Windows 平台不支持在线更新,请手动更新(下载最新版本替换当前程序)", nil))
|
|
}
|
|
|
|
// Check if already updating
|
|
if update.GetManager().IsUpdating() {
|
|
return nil, gerror.NewCode(gcode.New(consts.CodeUpdateAlreadyRunning, consts.MsgUpdateAlreadyRunning, nil))
|
|
}
|
|
|
|
// Get check result to find download URL
|
|
cached := update.GetManager().GetCheckResult()
|
|
// 缓存里有更新但没匹配到平台安装包时强制重查,而不是直接报「没有可用的更新包」:
|
|
// 典型场景是发行版刚发布、CI 尚未上传资产窗口期的检查结果被缓存,资产几分钟后就位
|
|
if cached == nil || update.HasUpdateButNoAsset(cached) {
|
|
// Force a check first
|
|
result, err := update.CheckForUpdate(ctx, true)
|
|
if err != nil {
|
|
return nil, gerror.New("检查更新失败: " + err.Error())
|
|
}
|
|
cached = result
|
|
}
|
|
|
|
if !cached.HasUpdate {
|
|
return nil, gerror.NewCode(gcode.New(consts.CodeUpdateNotAvailable, consts.MsgUpdateNotAvailable, nil))
|
|
}
|
|
|
|
if cached.LatestVersion != req.Version {
|
|
return nil, gerror.New(fmt.Sprintf("请求的版本 %s 与最新版本 %s 不匹配", req.Version, cached.LatestVersion))
|
|
}
|
|
|
|
if cached.DownloadURL == "" {
|
|
return nil, gerror.NewCode(gcode.New(consts.CodeUpdateNotSupported, "当前平台没有可用的更新包", nil))
|
|
}
|
|
|
|
// Execute update
|
|
if err := update.ExecuteUpdate(ctx, req.Version, cached.DownloadURL, cached.ChecksumURL, cached.AssetSize); err != nil {
|
|
return nil, gerror.NewCode(gcode.New(consts.CodeUpdateDownloadFailed, err.Error(), nil))
|
|
}
|
|
|
|
return &v1.UpdateExecuteRes{
|
|
Message: "系统更新已启动",
|
|
}, nil
|
|
}
|
|
|
|
// RollbackUpdate rolls back to the previous version
|
|
func (s *sAdmin) UpdateRollback(ctx context.Context, req *v1.UpdateRollbackReq) (*v1.UpdateRollbackRes, error) {
|
|
if update.IsDocker() {
|
|
return nil, gerror.NewCode(gcode.New(consts.CodeUpdateNotSupported, consts.MsgUpdateNotSupported, nil))
|
|
}
|
|
|
|
// Windows 不支持在线更新(与 UpdateExecute 保持一致)
|
|
if runtime.GOOS == "windows" {
|
|
return nil, gerror.NewCode(gcode.New(consts.CodeUpdateNotSupported, "Windows 平台不支持在线更新,请手动更新(下载最新版本替换当前程序)", nil))
|
|
}
|
|
|
|
if err := update.Rollback(ctx); err != nil {
|
|
return nil, gerror.NewCode(gcode.New(consts.CodeUpdateRollbackFailed, consts.MsgUpdateRollbackFailed+": "+err.Error(), nil))
|
|
}
|
|
|
|
return &v1.UpdateRollbackRes{
|
|
Message: "回滚已启动,系统将重启",
|
|
}, nil
|
|
}
|