mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-23 05:47:20 +08:00
Some checks failed
SonarCloud Scan / SonarCloud (push) Has been cancelled
* refactor(service): 重构 OpenRC 服务管理器 - 更新 IsEnabled 和 IsActive 检查逻辑,使用更可靠的命令 - 修复 ServiceExists 检查,直接使用文件路径判断 - 优化 FindServices 函数,扫描 /etc/init.d 目录 - 调整 BuildCommand 函数,支持 OpenRC 特定操作 - 修改 ParseStatus 函数,使用更新后的正则表达式 * feat(backend): 优化 Fail2ban 初始化配置以支持 Alpine 系统 - 增加对 Alpine 系统的特殊配置支持 - 改进防火墙类型检测逻辑,支持多种防火墙服务 - 增加 SSH 端口和认证日志路径的自动检测 - 优化配置文件模板,提高兼容性和安全性 * refactor(backend): 重构 SSH 日志解析功能 - 改进了对不同日志格式的支持,包括 secure, auth 和 messages 文件 - 优化了日志解析逻辑,提高了代码的可读性和可维护性 - 增加了对 RFC3339 时间格式的支持 - 改善了对失败登录尝试的解析,包括无效用户和连接关闭的情况 - 重构了日期解析和 IP 地址验证的逻辑 * refactor(upgrade): 优化升级服务中的初始化脚本选择逻辑 - 新增 selectInitScript 函数,根据系统初始化管理器类型选择合适的初始化脚本 - 支持 systemd、openrc 和 sysvinit 三种初始化管理器 - 对于 sysvinit,增加对 /etc/rc.common 文件存在性的判断,以区分不同的初始化脚本 - 默认情况下使用当前服务名称作为初始化脚本名称 * fix(upgrade): 修复升级时初始化脚本更新问题 - 修改了 criticalUpdates 数组中的服务脚本更新逻辑 - 在 selectInitScript 函数中增加了复制脚本文件的逻辑,以应对服务名和脚本名不一致的情况 * feat(snap): 添加初始化脚本到快照 - 在创建快照时,将服务脚本复制到 initscript 目录 - 然后将整个 initscript 目录复制到快照的目标目录 - 添加了日志输出,便于调试和记录 * refactor(backend): 重构快照恢复流程 - 移除了未使用的 import 语句 - 删除了注释掉的代码块 - 修改了 1Panel 服务恢复的逻辑,增加了对当前服务名称的获取 - 快照恢复过程中,根据宿主机类型,自动选择初始化服务脚本 - 添加了日志输出以提高可追踪性 - 优化了文件路径的处理方式 --------- Co-authored-by: gcsong023 <gcsong023@users.noreply.github.com>
304 lines
11 KiB
Go
304 lines
11 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"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"
|
|
)
|
|
|
|
type snapHelper struct {
|
|
SnapID uint
|
|
Status *model.SnapshotStatus
|
|
Ctx context.Context
|
|
FileOp files.FileOp
|
|
Wg *sync.WaitGroup
|
|
}
|
|
|
|
func snapJson(snap snapHelper, snapJson SnapshotJson, targetDir string) {
|
|
defer snap.Wg.Done()
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel_info": constant.Running})
|
|
status := constant.StatusDone
|
|
remarkInfo, _ := json.MarshalIndent(snapJson, "", "\t")
|
|
if err := os.WriteFile(fmt.Sprintf("%s/snapshot.json", targetDir), remarkInfo, 0640); err != nil {
|
|
status = err.Error()
|
|
}
|
|
snap.Status.PanelInfo = status
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel_info": status})
|
|
}
|
|
|
|
func snapPanel(snap snapHelper, targetDir string) {
|
|
defer snap.Wg.Done()
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": constant.Running})
|
|
status := constant.StatusDone
|
|
h, err := systemctl.DefaultHandler("1panel")
|
|
|
|
if err != nil {
|
|
status = fmt.Sprintf("initialize service handle failed: %v", err)
|
|
snap.Status.Panel = status
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": status})
|
|
return
|
|
}
|
|
|
|
binDir := systemctl.BinaryPath
|
|
servicePath, err := h.GetServicePath()
|
|
if err != nil {
|
|
status = fmt.Sprintf("get service path failed: %v", err)
|
|
snap.Status.Panel = status
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": status})
|
|
return
|
|
}
|
|
|
|
if err := common.CopyFile(path.Join(binDir, "1panel"), path.Join(targetDir, "1panel")); err != nil {
|
|
status = err.Error()
|
|
}
|
|
if err := common.CopyFile(path.Join(binDir, "1pctl"), targetDir); err != nil {
|
|
status = err.Error()
|
|
}
|
|
if _, err := cmd.Execf("cp -r %s/lang %s", binDir, targetDir); err != nil {
|
|
status = err.Error()
|
|
}
|
|
initScriptDir := path.Join(constant.DataDir, "initscript")
|
|
if err := common.CopyFile(servicePath, initScriptDir); err != nil {
|
|
status = err.Error()
|
|
}
|
|
global.LOG.Debugf("from %s copy init script to %s", initScriptDir, path.Join(targetDir, "initscript"))
|
|
if err := common.CopyDirs(initScriptDir, path.Join(targetDir, "initscript")); err != nil { // copy init script to targetDir
|
|
status = err.Error()
|
|
}
|
|
snap.Status.Panel = status
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": status})
|
|
}
|
|
|
|
func snapDaemonJson(snap snapHelper, targetDir string) {
|
|
defer snap.Wg.Done()
|
|
status := constant.StatusDone
|
|
if !snap.FileOp.Stat("/etc/docker/daemon.json") {
|
|
snap.Status.DaemonJson = status
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"daemon_json": status})
|
|
return
|
|
}
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"daemon_json": constant.Running})
|
|
if err := common.CopyFile("/etc/docker/daemon.json", targetDir); err != nil {
|
|
status = err.Error()
|
|
}
|
|
snap.Status.DaemonJson = status
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"daemon_json": status})
|
|
}
|
|
|
|
func snapAppData(snap snapHelper, targetDir string) {
|
|
defer snap.Wg.Done()
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": constant.Running})
|
|
appInstalls, err := appInstallRepo.ListBy()
|
|
if err != nil {
|
|
snap.Status.AppData = err.Error()
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": err.Error()})
|
|
return
|
|
}
|
|
runtimes, err := runtimeRepo.List()
|
|
if err != nil {
|
|
snap.Status.AppData = err.Error()
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": err.Error()})
|
|
return
|
|
}
|
|
imageRegex := regexp.MustCompile(`image:\s*(.*)`)
|
|
var imageSaveList []string
|
|
existStr, _ := cmd.Exec("docker images | awk '{print $1\":\"$2}' | grep -v REPOSITORY:TAG")
|
|
existImages := strings.Split(existStr, "\n")
|
|
duplicateMap := make(map[string]bool)
|
|
for _, app := range appInstalls {
|
|
matches := imageRegex.FindAllStringSubmatch(app.DockerCompose, -1)
|
|
for _, match := range matches {
|
|
for _, existImage := range existImages {
|
|
if match[1] == existImage && !duplicateMap[match[1]] {
|
|
imageSaveList = append(imageSaveList, match[1])
|
|
duplicateMap[match[1]] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for _, runtime := range runtimes {
|
|
for _, existImage := range existImages {
|
|
if runtime.Image == existImage && !duplicateMap[runtime.Image] {
|
|
imageSaveList = append(imageSaveList, runtime.Image)
|
|
duplicateMap[runtime.Image] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(imageSaveList) != 0 {
|
|
global.LOG.Debugf("docker save %s | gzip -c > %s", strings.Join(imageSaveList, " "), path.Join(targetDir, "docker_image.tar"))
|
|
std, err := cmd.Execf("docker save %s | gzip -c > %s", strings.Join(imageSaveList, " "), path.Join(targetDir, "docker_image.tar"))
|
|
if err != nil {
|
|
snap.Status.AppData = err.Error()
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": std})
|
|
return
|
|
}
|
|
}
|
|
snap.Status.AppData = constant.StatusDone
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"app_data": constant.StatusDone})
|
|
}
|
|
|
|
func snapBackup(snap snapHelper, localDir, targetDir string) {
|
|
defer snap.Wg.Done()
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"backup_data": constant.Running})
|
|
status := constant.StatusDone
|
|
if err := handleSnapTar(localDir, targetDir, "1panel_backup.tar.gz", "./system;./system_snapshot;", ""); err != nil {
|
|
status = err.Error()
|
|
}
|
|
snap.Status.BackupData = status
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"backup_data": status})
|
|
}
|
|
|
|
func snapPanelData(snap snapHelper, localDir, targetDir string) {
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel_data": constant.Running})
|
|
status := constant.StatusDone
|
|
dataDir := path.Join(global.CONF.System.BaseDir, "1panel")
|
|
exclusionRules := "./tmp;./log;./cache;./db/1Panel.db-*;"
|
|
if strings.Contains(localDir, dataDir) {
|
|
exclusionRules += ("." + strings.ReplaceAll(localDir, dataDir, "") + ";")
|
|
}
|
|
ignoreVal, _ := settingRepo.Get(settingRepo.WithByKey("SnapshotIgnore"))
|
|
rules := strings.Split(ignoreVal.Value, ",")
|
|
for _, ignore := range rules {
|
|
if len(ignore) == 0 || cmd.CheckIllegal(ignore) {
|
|
continue
|
|
}
|
|
exclusionRules += ("." + strings.ReplaceAll(ignore, dataDir, "") + ";")
|
|
}
|
|
_ = snapshotRepo.Update(snap.SnapID, map[string]interface{}{"status": "OnSaveData"})
|
|
sysIP, _ := settingRepo.Get(settingRepo.WithByKey("SystemIP"))
|
|
_ = settingRepo.Update("SystemIP", "")
|
|
checkPointOfWal()
|
|
if err := handleSnapTar(dataDir, targetDir, "1panel_data.tar.gz", exclusionRules, ""); err != nil {
|
|
status = err.Error()
|
|
}
|
|
_ = snapshotRepo.Update(snap.SnapID, map[string]interface{}{"status": constant.StatusWaiting})
|
|
|
|
snap.Status.PanelData = status
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel_data": status})
|
|
_ = settingRepo.Update("SystemIP", sysIP.Value)
|
|
}
|
|
|
|
func snapCompress(snap snapHelper, rootDir string, secret string) {
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"compress": constant.StatusRunning})
|
|
tmpDir := path.Join(global.CONF.System.TmpDir, "system")
|
|
fileName := fmt.Sprintf("%s.tar.gz", path.Base(rootDir))
|
|
if err := handleSnapTar(rootDir, tmpDir, fileName, "", secret); err != nil {
|
|
snap.Status.Compress = err.Error()
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"compress": err.Error()})
|
|
return
|
|
}
|
|
|
|
stat, err := os.Stat(path.Join(tmpDir, fileName))
|
|
if err != nil {
|
|
snap.Status.Compress = err.Error()
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"compress": err.Error()})
|
|
return
|
|
}
|
|
size := common.LoadSizeUnit2F(float64(stat.Size()))
|
|
global.LOG.Debugf("compress successful! size of file: %s", size)
|
|
snap.Status.Compress = constant.StatusDone
|
|
snap.Status.Size = size
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"compress": constant.StatusDone, "size": size})
|
|
|
|
global.LOG.Debugf("remove snapshot file %s", rootDir)
|
|
_ = os.RemoveAll(rootDir)
|
|
}
|
|
|
|
func snapUpload(snap snapHelper, accounts string, file string) {
|
|
source := path.Join(global.CONF.System.TmpDir, "system", path.Base(file))
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": constant.StatusUploading})
|
|
accountMap, err := loadClientMap(accounts)
|
|
if err != nil {
|
|
snap.Status.Upload = err.Error()
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": err.Error()})
|
|
return
|
|
}
|
|
targetAccounts := strings.Split(accounts, ",")
|
|
for _, item := range targetAccounts {
|
|
global.LOG.Debugf("start upload snapshot to %s, path: %s", item, path.Join(accountMap[item].backupPath, "system_snapshot", path.Base(file)))
|
|
if _, err := accountMap[item].client.Upload(source, path.Join(accountMap[item].backupPath, "system_snapshot", path.Base(file))); err != nil {
|
|
global.LOG.Debugf("upload to %s failed, err: %v", item, err)
|
|
snap.Status.Upload = err.Error()
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": err.Error()})
|
|
return
|
|
}
|
|
global.LOG.Debugf("upload to %s successful", item)
|
|
}
|
|
snap.Status.Upload = constant.StatusDone
|
|
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"upload": constant.StatusDone})
|
|
|
|
global.LOG.Debugf("remove snapshot file %s", source)
|
|
_ = os.Remove(source)
|
|
}
|
|
|
|
func handleSnapTar(sourceDir, targetDir, name, exclusionRules string, secret string) error {
|
|
if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) {
|
|
if err = os.MkdirAll(targetDir, os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
exMap := make(map[string]struct{})
|
|
exStr := ""
|
|
excludes := strings.Split(exclusionRules, ";")
|
|
for _, exclude := range excludes {
|
|
if len(exclude) == 0 {
|
|
continue
|
|
}
|
|
if _, ok := exMap[exclude]; ok {
|
|
continue
|
|
}
|
|
exStr += " --exclude "
|
|
exStr += exclude
|
|
exMap[exclude] = struct{}{}
|
|
}
|
|
path := ""
|
|
if strings.Contains(sourceDir, "/") {
|
|
itemDir := strings.ReplaceAll(sourceDir[strings.LastIndex(sourceDir, "/"):], "/", "")
|
|
aheadDir := sourceDir[:strings.LastIndex(sourceDir, "/")]
|
|
if len(aheadDir) == 0 {
|
|
aheadDir = "/"
|
|
}
|
|
path += fmt.Sprintf("-C %s %s", aheadDir, itemDir)
|
|
} else {
|
|
path = sourceDir
|
|
}
|
|
commands := ""
|
|
if len(secret) != 0 {
|
|
extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out"
|
|
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -print) -zcf %s %s %s %s", sourceDir, " -"+exStr, path, extraCmd, targetDir+"/"+name)
|
|
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
|
} else {
|
|
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -printf '%%P\n' | sed 's|^|./|') -zcf %s %s -C %s .", sourceDir, targetDir+"/"+name, exStr, sourceDir)
|
|
global.LOG.Debug(commands)
|
|
}
|
|
stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)
|
|
if err != nil {
|
|
if len(stdout) != 0 {
|
|
global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
|
|
return fmt.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkPointOfWal() {
|
|
if err := global.DB.Exec("PRAGMA wal_checkpoint(TRUNCATE);").Error; err != nil {
|
|
global.LOG.Errorf("handle check point failed, err: %v", err)
|
|
}
|
|
}
|