package service import ( "encoding/json" "fmt" "io/ioutil" "net/http" "os" "time" "github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/utils/cmd" "github.com/1Panel-dev/1Panel/backend/utils/files" ) type latestVersion struct { Version string `json:"version"` UpdateTime string `json:"update_time"` } type UpgradeService struct{} type IUpgradeService interface { Upgrade(version string) error SearchUpgrade() (*dto.UpgradeInfo, error) } func NewIUpgradeService() IUpgradeService { return &UpgradeService{} } func (u *UpgradeService) SearchUpgrade() (*dto.UpgradeInfo, error) { res, err := http.Get(global.CONF.System.AppOss + "/releases/latest.json") if err != nil { return nil, err } resByte, err := ioutil.ReadAll(res.Body) if err != nil { return nil, err } var latest latestVersion if err := json.Unmarshal(resByte, &latest); err != nil { return nil, err } setting, err := settingRepo.Get(settingRepo.WithByKey("SystemVersion")) if err != nil { return nil, err } if latest.Version != setting.Value { notes, err := http.Get(global.CONF.System.AppOss + fmt.Sprintf("/releases/%s/release_notes.md", latest.Version)) if err != nil { return nil, err } noteBytes, err := ioutil.ReadAll(notes.Body) if err != nil { return nil, err } return &dto.UpgradeInfo{ NewVersion: latest.Version, CreatedAt: latest.UpdateTime, ReleaseNote: string(noteBytes), }, nil } return nil, nil } func (u *UpgradeService) Upgrade(version string) error { global.LOG.Info("start to upgrade now...") fileOp := files.NewFileOp() timeStr := time.Now().Format("20060102150405") filePath := fmt.Sprintf("%s/%s.tar.gz", constant.TmpDir, timeStr) rootDir := constant.TmpDir + "/" + timeStr originalDir := fmt.Sprintf("%s/%s/original", constant.TmpDir, timeStr) downloadPath := fmt.Sprintf("%s/releases/%s/%s.tar.gz", global.CONF.System.AppOss, version, version) setting, err := settingRepo.Get(settingRepo.WithByKey("SystemVersion")) if err != nil { return err } u.changeStatus(constant.StatusWaiting, nil) go func() { if err := os.MkdirAll(originalDir, os.ModePerm); err != nil { u.changeStatus(setting.Value, err) return } if err := fileOp.DownloadFile(downloadPath, filePath); err != nil { u.changeStatus(setting.Value, fmt.Errorf("download file failed, err: %v", err)) return } global.LOG.Info("download file from oss successful!") defer func() { _ = os.Remove(filePath) }() if err := fileOp.Decompress(filePath, rootDir, files.TarGz); err != nil { u.changeStatus(setting.Value, fmt.Errorf("decompress file failed, err: %v", err)) return } if err := u.handleBackup(fileOp, originalDir); err != nil { u.changeStatus(setting.Value, fmt.Errorf("handle backup original file failed, err: %v", err)) return } global.LOG.Info("backup original data successful, now start to upgrade!") if err := cpBinary(rootDir+"/1panel", "/usr/local/bin/1panel"); err != nil { u.handleRollback(fileOp, originalDir, 1) u.changeStatus(setting.Value, fmt.Errorf("upgrade 1panel failed, err: %v", err)) return } if err := cpBinary(rootDir+"/1pctl", "/usr/local/bin/1pctl"); err != nil { u.handleRollback(fileOp, originalDir, 2) u.changeStatus(setting.Value, fmt.Errorf("upgrade 1pctl failed, err: %v", err)) return } if err := cpBinary(rootDir+"/1panel.service", "/etc/systemd/system/1panel.service"); err != nil { u.handleRollback(fileOp, originalDir, 3) u.changeStatus(setting.Value, fmt.Errorf("upgrade 1panel.service failed, err: %v", err)) return } global.LOG.Info("upgrade successful!") u.changeStatus(version, nil) _, _ = cmd.Exec("systemctl restart 1panel.service") }() return nil } func (u *UpgradeService) handleBackup(fileOp files.FileOp, originalDir string) error { if err := fileOp.Copy("/usr/local/bin/1panel", originalDir); err != nil { return err } if err := fileOp.Copy("/usr/local/bin/1pctl", originalDir); err != nil { return err } if err := fileOp.Copy("/etc/systemd/system/1panel.service", originalDir); err != nil { return err } dbPath := global.CONF.System.DbPath + "/" + global.CONF.System.DbFile if err := fileOp.Copy(dbPath, originalDir); err != nil { return err } return nil } func (u *UpgradeService) handleRollback(fileOp files.FileOp, originalDir string, errStep int) { dbPath := global.CONF.System.DbPath + "/1Panel.db" if err := cpBinary(originalDir+"/1Panel.db", dbPath); err != nil { global.LOG.Errorf("rollback 1panel failed, err: %v", err) } if err := cpBinary(originalDir+"/1panel", "/usr/local/bin/1panel"); err != nil { global.LOG.Errorf("rollback 1pctl failed, err: %v", err) } if errStep == 1 { return } if err := cpBinary(originalDir+"/1pctl", "/usr/local/bin/1pctl"); err != nil { global.LOG.Errorf("rollback 1panel failed, err: %v", err) } if errStep == 2 { return } if err := cpBinary(originalDir+"/1panel.service", "/etc/systemd/system/1panel.service"); err != nil { global.LOG.Errorf("rollback 1panel failed, err: %v", err) } } func (u *UpgradeService) changeStatus(status string, err error) { if err != nil { global.LOG.Error(err.Error()) } if err := settingRepo.Update("SystemVersion", status); err != nil { global.LOG.Errorf("update system version failed, err: %v", err) } }