mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-11-08 10:41:16 +08:00
* feat(systemctl): 实现服务管理器初始化和命令执行 - 新增 systemctl 包,实现对 systemd、openrc 和 sysvinit 三种服务管理器的支持 - 添加服务状态检查、启动、停止、重启和启用/禁用功能 - 实现服务发现和智能服务名处理 - 添加配置文件查看功能 - 优化错误处理和日志记录 * refactor(system): 重构系统服务管理逻辑 - 引入 systemctl 工具包以统一处理系统服务 - 优化服务状态获取、配置文件路径解析等逻辑 - 重构 HostToolService 中的 GetToolStatus 方法 - 更新 DockerService、SettingService 等相关服务的处理方式 - 调整快照创建和恢复过程中的服务处理逻辑 * feat(utils): 添加目录复制功能并优化文件复制逻辑 - 新增 CopyDirs 函数,用于复制整个目录及其内容 - 添加对符号链接的复制支持 - 实现通用的 Copy 函数,根据文件类型自动选择 CopyFile 或 CopyDirs - 在 CopyFile 函数中增加对源文件是目录的检查和错误提示 * refactortoolbox: 重构 Fail2ban 和 Pure-FTPd 的管理逻辑 - 优化了 Fail2ban 和 Pure-FTPd 的启动、停止、重启等操作的实现 - 改进了 Fail2ban 版本信息的获取方法 - 统一了错误处理和日志记录的格式 - 调整了部分导入的包,提高了代码的可维护性 * build: 禁用 CGO 以提高构建性能和兼容性 - 在 Linux 后端构建命令中添加 CGO_ENABLED=0 环境变量 - 此修改可以提高构建速度,并确保生成的二进制文件在没有 C 库依赖的环境中也能运行 * refactor(docker): 重构 Docker 服务的重启和操作逻辑 - 添加 isDockerSnapInstalled 函数来判断 Docker 是否通过 Snap 安装 - 在 OperateDocker 和 restartDocker 函数中增加对 Snap 安装的处理 - 移除未使用的 getDockerRestartCommand 函数 * fix(service): 优化快照恢复后的服务重启逻辑 - 在使用 systemd 管理服务时,增加 daemon-reload 操作以确保服务配置更新 - 重启 1panel 服务,以应用快照恢复的更改 * refactor(server): 支持非 systemd 系统的恢复操作 - 增加 isSystemd 函数判断系统是否为 systemd 类型 - 根据系统类型选择性地恢复服务文件 - 兼容 systemd 和非 systemd 系统的恢复流程 * fix(upgrade): 优化升级过程中的服务重启逻辑 - 移动服务重启逻辑到版本号更新之后,修复因提前重启导致的版本号未更新BUG。 - 在 systemctl 重启之前添加 daemon-reload 命令 --------- Co-authored-by: gcsong023 <gcsong023@users.noreply.github.com>
402 lines
12 KiB
Go
402 lines
12 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"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/common"
|
|
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
|
httpUtil "github.com/1Panel-dev/1Panel/backend/utils/http"
|
|
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
|
)
|
|
|
|
type UpgradeService struct{}
|
|
|
|
type IUpgradeService interface {
|
|
Upgrade(req dto.Upgrade) error
|
|
LoadNotes(req dto.Upgrade) (string, error)
|
|
SearchUpgrade() (*dto.UpgradeInfo, error)
|
|
}
|
|
|
|
func NewIUpgradeService() IUpgradeService {
|
|
return &UpgradeService{}
|
|
}
|
|
|
|
func (u *UpgradeService) SearchUpgrade() (*dto.UpgradeInfo, error) {
|
|
var upgrade dto.UpgradeInfo
|
|
currentVersion, err := settingRepo.Get(settingRepo.WithByKey("SystemVersion"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
DeveloperMode, err := settingRepo.Get(settingRepo.WithByKey("DeveloperMode"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
upgrade.TestVersion, upgrade.NewVersion, upgrade.LatestVersion = u.loadVersionByMode(DeveloperMode.Value, currentVersion.Value)
|
|
var itemVersion string
|
|
if len(upgrade.LatestVersion) != 0 {
|
|
itemVersion = upgrade.LatestVersion
|
|
}
|
|
if len(upgrade.NewVersion) != 0 {
|
|
itemVersion = upgrade.NewVersion
|
|
}
|
|
if (global.CONF.System.Mode == "dev" || DeveloperMode.Value == "enable") && len(upgrade.TestVersion) != 0 {
|
|
itemVersion = upgrade.TestVersion
|
|
}
|
|
if len(itemVersion) == 0 {
|
|
return &upgrade, nil
|
|
}
|
|
mode := global.CONF.System.Mode
|
|
if strings.Contains(itemVersion, "beta") {
|
|
mode = "beta"
|
|
}
|
|
notes, err := u.loadReleaseNotes(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.CONF.System.RepoUrl, mode, itemVersion, itemVersion))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load releases-notes of version %s failed, err: %v", itemVersion, err)
|
|
}
|
|
upgrade.ReleaseNote = notes
|
|
return &upgrade, nil
|
|
}
|
|
|
|
func (u *UpgradeService) LoadNotes(req dto.Upgrade) (string, error) {
|
|
mode := global.CONF.System.Mode
|
|
if strings.Contains(req.Version, "beta") {
|
|
mode = "beta"
|
|
}
|
|
notes, err := u.loadReleaseNotes(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.CONF.System.RepoUrl, mode, req.Version, req.Version))
|
|
if err != nil {
|
|
return "", fmt.Errorf("load releases-notes of version %s failed, err: %v", req.Version, err)
|
|
}
|
|
return notes, nil
|
|
}
|
|
|
|
func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
|
|
global.LOG.Info("start to upgrade now...")
|
|
fileOp := files.NewFileOp()
|
|
timeStr := time.Now().Format(constant.DateTimeSlimLayout)
|
|
rootDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("upgrade/upgrade_%s/downloads", timeStr))
|
|
originalDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("upgrade/upgrade_%s/original", timeStr))
|
|
if err := os.MkdirAll(rootDir, os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
if err := os.MkdirAll(originalDir, os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
itemArch, err := loadArch()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mode := global.CONF.System.Mode
|
|
if strings.Contains(req.Version, "beta") {
|
|
mode = "beta"
|
|
}
|
|
downloadPath := fmt.Sprintf("%s/%s/%s/release", global.CONF.System.RepoUrl, mode, req.Version)
|
|
fileName := fmt.Sprintf("1panel-%s-%s-%s.tar.gz", req.Version, "linux", itemArch)
|
|
serviceHandle, _ := systemctl.DefaultHandler("1panel")
|
|
currentServiceName := serviceHandle.GetServiceName()
|
|
if err := settingRepo.Update("SystemStatus", "Upgrading"); err != nil {
|
|
return fmt.Errorf("update system status failed: %w", err)
|
|
}
|
|
|
|
go func() {
|
|
defer func() {
|
|
if err := settingRepo.Update("SystemStatus", "Free"); err != nil {
|
|
global.LOG.Errorf("Reset system status failed: %v", err)
|
|
}
|
|
}()
|
|
|
|
_ = global.Cron.Stop()
|
|
defer global.Cron.Start()
|
|
|
|
if err := fileOp.DownloadFileWithProxy(
|
|
fmt.Sprintf("%s/%s", downloadPath, fileName),
|
|
path.Join(rootDir, fileName),
|
|
); err != nil {
|
|
global.LOG.Errorf("Failed to download upgrade package: %v", err)
|
|
return
|
|
}
|
|
defer os.RemoveAll(rootDir)
|
|
|
|
if err := handleUnTar(path.Join(rootDir, fileName), rootDir, ""); err != nil {
|
|
global.LOG.Errorf("Failed to extract package: %v", err)
|
|
return
|
|
}
|
|
|
|
tmpDir := path.Join(rootDir, strings.TrimSuffix(fileName, ".tar.gz"))
|
|
|
|
if err := u.handleBackup(fileOp, originalDir); err != nil {
|
|
global.LOG.Errorf("Backup failed: %v", err)
|
|
return
|
|
}
|
|
|
|
binDir := systemctl.BinaryPath
|
|
servicePath, _ := serviceHandle.GetServicePath()
|
|
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo/GeoIP.mmdb")
|
|
|
|
criticalUpdates := []struct {
|
|
src string
|
|
dest string
|
|
step int
|
|
}{
|
|
{path.Join(tmpDir, "1panel"), path.Join(binDir, "1panel"), 1},
|
|
{path.Join(tmpDir, "1pctl"), path.Join(binDir, "1pctl"), 2},
|
|
{path.Join(tmpDir, currentServiceName), servicePath, 3},
|
|
}
|
|
|
|
for _, update := range criticalUpdates {
|
|
if err := common.Copy(update.src, update.dest); err != nil {
|
|
global.LOG.Errorf("Update %s failed: %v", path.Base(update.dest), err)
|
|
u.handleRollback(originalDir, update.step)
|
|
return
|
|
}
|
|
}
|
|
|
|
if _, err := cmd.Execf("sed -i -e 's#BASE_DIR=.*#BASE_DIR=%s#g' /usr/local/bin/1pctl",
|
|
global.CONF.System.BaseDir); err != nil {
|
|
global.LOG.Errorf("Update base directory failed: %v", err)
|
|
u.handleRollback(originalDir, 2)
|
|
return
|
|
}
|
|
|
|
langDir := path.Join(binDir, "lang")
|
|
if err := common.Copy(path.Join(tmpDir, "lang"), langDir); err != nil {
|
|
global.LOG.Errorf("Update language files failed: %v", err)
|
|
}
|
|
if err := common.Copy(path.Join(tmpDir, "GeoIP.mmdb"), geoPath); err != nil {
|
|
global.LOG.Warnf("Update GeoIP database failed: %v", err)
|
|
}
|
|
|
|
global.LOG.Info("upgrade successful!")
|
|
go writeLogs(req.Version)
|
|
checkPointOfWal()
|
|
if err := settingRepo.Update("SystemVersion", req.Version); err != nil {
|
|
global.LOG.Errorf("Update system version failed: %v", err)
|
|
}
|
|
if serviceHandle.ManagerName() == "systemd" {
|
|
_, _ = cmd.Exec("systemctl daemon-reload")
|
|
}
|
|
if err := systemctl.Restart("1panel"); err != nil {
|
|
global.LOG.Errorf("Service restart failed: %v", err)
|
|
return
|
|
}
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
func (u *UpgradeService) handleBackup(fileOp files.FileOp, originalDir string) error {
|
|
global.LOG.Info("Initiating backup procedure...")
|
|
h, _ := systemctl.DefaultHandler("1panel")
|
|
binDir := systemctl.BinaryPath
|
|
servicePath, _ := h.GetServicePath()
|
|
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo/GeoIP.mmdb")
|
|
|
|
backupItems := []struct {
|
|
src string
|
|
dest string
|
|
}{
|
|
{path.Join(binDir, "1panel"), originalDir},
|
|
{path.Join(binDir, "1pctl"), originalDir},
|
|
{servicePath, originalDir},
|
|
{path.Join(binDir, "lang"), originalDir},
|
|
{geoPath, originalDir},
|
|
}
|
|
|
|
for _, item := range backupItems {
|
|
if err := fileOp.Copy(item.src, item.dest); err != nil {
|
|
return fmt.Errorf("backup %s failed: %w", path.Base(item.src), err)
|
|
}
|
|
}
|
|
|
|
if err := handleTar(
|
|
path.Join(global.CONF.System.BaseDir, "1panel/db"),
|
|
originalDir,
|
|
"db.tar.gz",
|
|
"db/1Panel.db-*",
|
|
"",
|
|
); err != nil {
|
|
return fmt.Errorf("database backup failed: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (u *UpgradeService) handleRollback(originalDir string, errStep int) {
|
|
global.LOG.Info("Initiating rollback procedure...")
|
|
h, _ := systemctl.DefaultHandler("1panel")
|
|
binDir := systemctl.BinaryPath
|
|
servicePath, _ := h.GetServicePath()
|
|
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo/GeoIP.mmdb")
|
|
|
|
rollbackSteps := []struct {
|
|
src string
|
|
dest string
|
|
}{
|
|
{path.Join(originalDir, "1panel"), path.Join(binDir, "1panel")},
|
|
{path.Join(originalDir, "1pctl"), path.Join(binDir, "1pctl")},
|
|
{path.Join(originalDir, filepath.Base(servicePath)), servicePath},
|
|
{path.Join(originalDir, "lang"), path.Join(binDir, "lang")},
|
|
{path.Join(originalDir, "GeoIP.mmdb"), geoPath},
|
|
}
|
|
|
|
for _, step := range rollbackSteps[:errStep] {
|
|
if err := common.CopyFile(step.src, step.dest); err != nil {
|
|
global.LOG.Errorf("Rollback %s failed: %v", path.Base(step.src), err)
|
|
}
|
|
}
|
|
|
|
if err := systemctl.Restart("1panel"); err != nil {
|
|
global.LOG.Errorf("Service restart during rollback failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func (u *UpgradeService) loadVersionByMode(developer, currentVersion string) (string, string, string) {
|
|
var current, latest string
|
|
if global.CONF.System.Mode == "dev" {
|
|
betaVersionLatest := u.loadVersion(true, currentVersion, "beta")
|
|
devVersionLatest := u.loadVersion(true, currentVersion, "dev")
|
|
if common.ComparePanelVersion(betaVersionLatest, devVersionLatest) {
|
|
return betaVersionLatest, "", ""
|
|
}
|
|
return devVersionLatest, "", ""
|
|
}
|
|
|
|
betaVersionLatest := ""
|
|
latest = u.loadVersion(true, currentVersion, "stable")
|
|
current = u.loadVersion(false, currentVersion, "stable")
|
|
if developer == "enable" {
|
|
betaVersionLatest = u.loadVersion(true, currentVersion, "beta")
|
|
}
|
|
if current != latest {
|
|
return betaVersionLatest, current, latest
|
|
}
|
|
|
|
versionPart := strings.Split(current, ".")
|
|
if len(versionPart) < 3 {
|
|
return betaVersionLatest, current, latest
|
|
}
|
|
num, _ := strconv.Atoi(versionPart[1])
|
|
if num == 0 {
|
|
return betaVersionLatest, current, latest
|
|
}
|
|
if num >= 10 {
|
|
if current[:6] == currentVersion[:6] {
|
|
return betaVersionLatest, current, ""
|
|
}
|
|
return betaVersionLatest, "", latest
|
|
}
|
|
if current[:5] == currentVersion[:5] {
|
|
return betaVersionLatest, current, ""
|
|
}
|
|
return betaVersionLatest, "", latest
|
|
}
|
|
|
|
func (u *UpgradeService) loadVersion(isLatest bool, currentVersion, mode string) string {
|
|
path := fmt.Sprintf("%s/%s/latest", global.CONF.System.RepoUrl, mode)
|
|
if !isLatest {
|
|
path = fmt.Sprintf("%s/%s/latest.current", global.CONF.System.RepoUrl, mode)
|
|
}
|
|
_, latestVersionRes, err := httpUtil.HandleGet(path, http.MethodGet, constant.TimeOut20s)
|
|
if err != nil {
|
|
global.LOG.Errorf("load latest version from oss failed, err: %v", err)
|
|
return ""
|
|
}
|
|
version := string(latestVersionRes)
|
|
if strings.Contains(version, "<") {
|
|
global.LOG.Errorf("load latest version from oss failed, err: %v", version)
|
|
return ""
|
|
}
|
|
if isLatest {
|
|
return u.checkVersion(version, currentVersion)
|
|
}
|
|
|
|
versionMap := make(map[string]string)
|
|
if err := json.Unmarshal(latestVersionRes, &versionMap); err != nil {
|
|
global.LOG.Errorf("load latest version from oss failed (error unmarshal), err: %v", err)
|
|
return ""
|
|
}
|
|
|
|
versionPart := strings.Split(currentVersion, ".")
|
|
if len(versionPart) < 3 {
|
|
global.LOG.Errorf("current version is error format: %s", currentVersion)
|
|
return ""
|
|
}
|
|
num, _ := strconv.Atoi(versionPart[1])
|
|
if num == 0 {
|
|
global.LOG.Errorf("current version is error format: %s", currentVersion)
|
|
return ""
|
|
}
|
|
if num >= 10 {
|
|
if version, ok := versionMap[currentVersion[0:5]]; ok {
|
|
return u.checkVersion(version, currentVersion)
|
|
}
|
|
return ""
|
|
}
|
|
if version, ok := versionMap[currentVersion[0:4]]; ok {
|
|
return u.checkVersion(version, currentVersion)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (u *UpgradeService) checkVersion(v2, v1 string) string {
|
|
addSuffix := false
|
|
if !strings.Contains(v1, "-") {
|
|
v1 = v1 + "-lts"
|
|
}
|
|
if !strings.Contains(v2, "-") {
|
|
addSuffix = true
|
|
v2 = v2 + "-lts"
|
|
}
|
|
if common.ComparePanelVersion(v2, v1) {
|
|
if addSuffix {
|
|
return strings.TrimSuffix(v2, "-lts")
|
|
}
|
|
return v2
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (u *UpgradeService) loadReleaseNotes(path string) (string, error) {
|
|
_, releaseNotes, err := httpUtil.HandleGet(path, http.MethodGet, constant.TimeOut20s)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(releaseNotes), nil
|
|
}
|
|
|
|
func loadArch() (string, error) {
|
|
std, err := cmd.Exec("uname -a")
|
|
if err != nil {
|
|
return "", fmt.Errorf("std: %s, err: %s", std, err.Error())
|
|
}
|
|
if strings.Contains(std, "x86_64") {
|
|
return "amd64", nil
|
|
}
|
|
if strings.Contains(std, "arm64") || strings.Contains(std, "aarch64") {
|
|
return "arm64", nil
|
|
}
|
|
if strings.Contains(std, "armv7l") {
|
|
return "armv7", nil
|
|
}
|
|
if strings.Contains(std, "ppc64le") {
|
|
return "ppc64le", nil
|
|
}
|
|
if strings.Contains(std, "s390x") {
|
|
return "s390x", nil
|
|
}
|
|
if strings.Contains(std, "riscv64") {
|
|
return "riscv64", nil
|
|
}
|
|
return "", fmt.Errorf("unsupported such arch: %s", std)
|
|
}
|