mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-23 05:47:20 +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>
304 lines
11 KiB
Go
304 lines
11 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
|
"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"
|
|
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover bool, req dto.SnapshotRecover) {
|
|
_ = global.Cron.Stop()
|
|
defer func() {
|
|
global.Cron.Start()
|
|
}()
|
|
|
|
snapFileDir := ""
|
|
if isRecover {
|
|
baseDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("system/%s", snap.Name))
|
|
if _, err := os.Stat(baseDir); err != nil && os.IsNotExist(err) {
|
|
_ = os.MkdirAll(baseDir, os.ModePerm)
|
|
}
|
|
if req.IsNew || snap.InterruptStep == "Download" || req.ReDownload {
|
|
if err := handleDownloadSnapshot(snap, baseDir); err != nil {
|
|
updateRecoverStatus(snap.ID, isRecover, "Backup", constant.StatusFailed, err.Error())
|
|
return
|
|
}
|
|
global.LOG.Debugf("download snapshot file to %s successful!", baseDir)
|
|
req.IsNew = true
|
|
}
|
|
if req.IsNew || snap.InterruptStep == "Decompress" {
|
|
if err := handleUnTar(fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name), baseDir, req.Secret); err != nil {
|
|
updateRecoverStatus(snap.ID, isRecover, "Decompress", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
|
|
return
|
|
}
|
|
global.LOG.Debug("decompress snapshot file successful!", baseDir)
|
|
req.IsNew = true
|
|
}
|
|
if req.IsNew || snap.InterruptStep == "Backup" {
|
|
if err := backupBeforeRecover(snap); err != nil {
|
|
updateRecoverStatus(snap.ID, isRecover, "Backup", constant.StatusFailed, fmt.Sprintf("handle backup before recover failed, err: %v", err))
|
|
return
|
|
}
|
|
global.LOG.Debug("handle backup before recover successful!")
|
|
req.IsNew = true
|
|
}
|
|
snapFileDir = fmt.Sprintf("%s/%s", baseDir, snap.Name)
|
|
if _, err := os.Stat(snapFileDir); err != nil {
|
|
snapFileDir = baseDir
|
|
}
|
|
} else {
|
|
snapFileDir = fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
|
|
if _, err := os.Stat(snapFileDir); err != nil {
|
|
updateRecoverStatus(snap.ID, isRecover, "", constant.StatusFailed, fmt.Sprintf("cannot find the backup file %s, please try to recover again.", snapFileDir))
|
|
return
|
|
}
|
|
}
|
|
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", snapFileDir))
|
|
if err != nil {
|
|
updateRecoverStatus(snap.ID, isRecover, "Readjson", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
|
|
return
|
|
}
|
|
if snap.InterruptStep == "Readjson" {
|
|
req.IsNew = true
|
|
}
|
|
if isRecover && (req.IsNew || snap.InterruptStep == "AppData") {
|
|
if err := recoverAppData(snapFileDir); err != nil {
|
|
updateRecoverStatus(snap.ID, isRecover, "DockerDir", constant.StatusFailed, fmt.Sprintf("handle recover app data failed, err: %v", err))
|
|
return
|
|
}
|
|
global.LOG.Debug("recover app data from snapshot file successful!")
|
|
req.IsNew = true
|
|
}
|
|
if req.IsNew || snap.InterruptStep == "DaemonJson" {
|
|
fileOp := files.NewFileOp()
|
|
if err := recoverDaemonJson(snapFileDir, fileOp); err != nil {
|
|
updateRecoverStatus(snap.ID, isRecover, "DaemonJson", constant.StatusFailed, err.Error())
|
|
return
|
|
}
|
|
global.LOG.Debug("recover daemon.json from snapshot file successful!")
|
|
req.IsNew = true
|
|
}
|
|
|
|
h, err := systemctl.DefaultHandler("1panel")
|
|
if err != nil {
|
|
updateRecoverStatus(snap.ID, isRecover, "ServiceHandle", constant.StatusFailed, fmt.Sprintf("initialize service handle failed: %v", err))
|
|
return
|
|
}
|
|
|
|
if req.IsNew || snap.InterruptStep == "1PanelBinary" {
|
|
binDir := systemctl.BinaryPath
|
|
// if err != nil {
|
|
// updateRecoverStatus(snap.ID, isRecover, "GetBinaryPath", constant.StatusFailed, fmt.Sprintf("get binary path failed: %v", err))
|
|
// return
|
|
// }
|
|
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel"), binDir); err != nil {
|
|
updateRecoverStatus(snap.ID, isRecover, "1PanelBinary", constant.StatusFailed, err.Error())
|
|
return
|
|
}
|
|
global.LOG.Debug("recover 1panel binary from snapshot file successful!")
|
|
req.IsNew = true
|
|
}
|
|
if req.IsNew || snap.InterruptStep == "1PctlBinary" {
|
|
binDir := systemctl.BinaryPath
|
|
// if err != nil {
|
|
// updateRecoverStatus(snap.ID, isRecover, "GetBinaryPath", constant.StatusFailed, fmt.Sprintf("get binary path failed: %v", err))
|
|
// return
|
|
// }
|
|
if err := recoverPanel(path.Join(snapFileDir, "1panel/1pctl"), binDir); err != nil {
|
|
updateRecoverStatus(snap.ID, isRecover, "1PctlBinary", constant.StatusFailed, err.Error())
|
|
return
|
|
}
|
|
langDir := path.Join(binDir, "lang")
|
|
if err := os.RemoveAll(langDir); err != nil {
|
|
updateRecoverStatus(snap.ID, isRecover, "RemoveLang", constant.StatusFailed, fmt.Sprintf("remove lang dir failed: %v", err))
|
|
return
|
|
}
|
|
if err := common.CopyDirs(path.Join(snapFileDir, "1panel/lang"), langDir); err != nil {
|
|
updateRecoverStatus(snap.ID, isRecover, "CopyLang", constant.StatusFailed, fmt.Sprintf("copy lang files failed: %v", err))
|
|
return
|
|
}
|
|
global.LOG.Debug("recover 1pctl from snapshot file successful!")
|
|
req.IsNew = true
|
|
}
|
|
if req.IsNew || snap.InterruptStep == "1PanelService" {
|
|
servicePath, err := h.GetServicePath()
|
|
if err != nil {
|
|
updateRecoverStatus(snap.ID, isRecover, "GetServicePath", constant.StatusFailed, fmt.Sprintf("get service path failed: %v", err))
|
|
return
|
|
}
|
|
if err := recoverPanel(path.Join(snapFileDir, "1panel/"+filepath.Base(servicePath)), filepath.Dir(servicePath)); err != nil {
|
|
updateRecoverStatus(snap.ID, isRecover, "1PanelService", constant.StatusFailed, err.Error())
|
|
return
|
|
}
|
|
global.LOG.Debug("recover 1panel service from snapshot file successful!")
|
|
req.IsNew = true
|
|
}
|
|
|
|
if req.IsNew || snap.InterruptStep == "1PanelBackups" {
|
|
if err := u.handleUnTar(path.Join(snapFileDir, "/1panel/1panel_backup.tar.gz"), snapJson.BackupDataDir, ""); err != nil {
|
|
updateRecoverStatus(snap.ID, isRecover, "1PanelBackups", constant.StatusFailed, err.Error())
|
|
return
|
|
}
|
|
global.LOG.Debug("recover 1panel backups from snapshot file successful!")
|
|
req.IsNew = true
|
|
}
|
|
|
|
if req.IsNew || snap.InterruptStep == "1PanelData" {
|
|
checkPointOfWal()
|
|
if err := u.handleUnTar(path.Join(snapFileDir, "/1panel/1panel_data.tar.gz"), path.Join(snapJson.BaseDir, "1panel"), ""); err != nil {
|
|
updateRecoverStatus(snap.ID, isRecover, "1PanelData", constant.StatusFailed, err.Error())
|
|
return
|
|
}
|
|
global.LOG.Debug("recover 1panel data from snapshot file successful!")
|
|
req.IsNew = true
|
|
}
|
|
_ = rebuildAllAppInstall()
|
|
restartCompose(path.Join(snapJson.BaseDir, "1panel/docker/compose"))
|
|
|
|
global.LOG.Info("recover successful")
|
|
if !isRecover {
|
|
oriPath := fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
|
|
global.LOG.Debugf("remove the file %s after the operation is successful", oriPath)
|
|
_ = os.RemoveAll(oriPath)
|
|
} else {
|
|
global.LOG.Debugf("remove the file %s after the operation is successful", path.Dir(snapFileDir))
|
|
_ = os.RemoveAll(path.Dir(snapFileDir))
|
|
}
|
|
if h.ManagerName() == "systemd" {
|
|
_, _ = cmd.Exec("systemctl daemon-reload")
|
|
}
|
|
if err := systemctl.Restart("1panel"); err != nil {
|
|
global.LOG.Errorf("restart 1panel service failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func backupBeforeRecover(snap model.Snapshot) error {
|
|
baseDir := fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
|
|
var wg sync.WaitGroup
|
|
var status model.SnapshotStatus
|
|
itemHelper := snapHelper{SnapID: 0, Status: &status, Wg: &wg, FileOp: files.NewFileOp(), Ctx: context.Background()}
|
|
|
|
jsonItem := SnapshotJson{
|
|
BaseDir: global.CONF.System.BaseDir,
|
|
BackupDataDir: global.CONF.System.Backup,
|
|
PanelDataDir: path.Join(global.CONF.System.BaseDir, "1panel"),
|
|
}
|
|
_ = os.MkdirAll(path.Join(baseDir, "1panel"), os.ModePerm)
|
|
_ = os.MkdirAll(path.Join(baseDir, "docker"), os.ModePerm)
|
|
|
|
wg.Add(4)
|
|
itemHelper.Wg = &wg
|
|
go snapJson(itemHelper, jsonItem, baseDir)
|
|
go snapPanel(itemHelper, path.Join(baseDir, "1panel"))
|
|
go snapDaemonJson(itemHelper, path.Join(baseDir, "docker"))
|
|
go snapBackup(itemHelper, global.CONF.System.Backup, path.Join(baseDir, "1panel"))
|
|
wg.Wait()
|
|
itemHelper.Status.AppData = constant.StatusDone
|
|
|
|
allDone, msg := checkAllDone(status)
|
|
if !allDone {
|
|
return errors.New(msg)
|
|
}
|
|
snapPanelData(itemHelper, global.CONF.System.BaseDir, path.Join(baseDir, "1panel"))
|
|
if status.PanelData != constant.StatusDone {
|
|
return errors.New(status.PanelData)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func handleDownloadSnapshot(snap model.Snapshot, targetDir string) error {
|
|
backup, err := backupRepo.Get(commonRepo.WithByType(snap.DefaultDownload))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
client, err := NewIBackupService().NewClient(&backup)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pathItem := backup.BackupPath
|
|
if backup.BackupPath != "/" {
|
|
pathItem = strings.TrimPrefix(backup.BackupPath, "/")
|
|
}
|
|
filePath := fmt.Sprintf("%s/%s.tar.gz", targetDir, snap.Name)
|
|
_ = os.RemoveAll(filePath)
|
|
ok, err := client.Download(path.Join(pathItem, fmt.Sprintf("system_snapshot/%s.tar.gz", snap.Name)), filePath)
|
|
if err != nil || !ok {
|
|
return fmt.Errorf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func recoverAppData(src string) error {
|
|
if _, err := os.Stat(path.Join(src, "docker/docker_image.tar")); err != nil {
|
|
global.LOG.Debug("no such docker images in snapshot")
|
|
return nil
|
|
}
|
|
std, err := cmd.Execf("docker load < %s", path.Join(src, "docker/docker_image.tar"))
|
|
if err != nil {
|
|
return errors.New(std)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func recoverDaemonJson(src string, fileOp files.FileOp) error {
|
|
daemonJsonPath := "/etc/docker/daemon.json"
|
|
_, errSrc := os.Stat(path.Join(src, "docker/daemon.json"))
|
|
_, errPath := os.Stat(daemonJsonPath)
|
|
if os.IsNotExist(errSrc) && os.IsNotExist(errPath) {
|
|
global.LOG.Debug("the daemon.json file does not exist, nothing happens.")
|
|
return nil
|
|
}
|
|
if errSrc == nil {
|
|
if err := fileOp.CopyFile(path.Join(src, "docker/daemon.json"), "/etc/docker"); err != nil {
|
|
return fmt.Errorf("recover docker daemon.json failed, err: %v", err)
|
|
}
|
|
}
|
|
|
|
if err := restartDocker(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func recoverPanel(src string, dst string) error {
|
|
if _, err := os.Stat(src); err != nil {
|
|
return fmt.Errorf("file is not found in %s, err: %v", src, err)
|
|
}
|
|
if err := common.CopyFile(src, dst); err != nil {
|
|
return fmt.Errorf("cp file failed, err: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func restartCompose(composePath string) {
|
|
composes, err := composeRepo.ListRecord()
|
|
if err != nil {
|
|
return
|
|
}
|
|
for _, compose := range composes {
|
|
pathItem := path.Join(composePath, compose.Name, "docker-compose.yml")
|
|
if _, err := os.Stat(pathItem); err != nil {
|
|
continue
|
|
}
|
|
upCmd := fmt.Sprintf("docker-compose -f %s up -d", pathItem)
|
|
stdout, err := cmd.Exec(upCmd)
|
|
if err != nil {
|
|
global.LOG.Debugf("%s failed, err: %v", upCmd, stdout)
|
|
}
|
|
}
|
|
global.LOG.Debug("restart all compose successful!")
|
|
}
|