From 22461f95d081a69fe508e8a0c1c4b346b23e643b Mon Sep 17 00:00:00 2001 From: gcsong023 Date: Thu, 17 Apr 2025 21:21:48 +0800 Subject: [PATCH 1/7] =?UTF-8?q?refactor(service):=20=E9=87=8D=E6=9E=84=20O?= =?UTF-8?q?penRC=20=E6=9C=8D=E5=8A=A1=E7=AE=A1=E7=90=86=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 IsEnabled 和 IsActive 检查逻辑,使用更可靠的命令 - 修复 ServiceExists 检查,直接使用文件路径判断 - 优化 FindServices 函数,扫描 /etc/init.d 目录 - 调整 BuildCommand 函数,支持 OpenRC 特定操作 - 修改 ParseStatus 函数,使用更新后的正则表达式 --- backend/utils/systemctl/managers.go | 79 ++++++++++++++--------------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/backend/utils/systemctl/managers.go b/backend/utils/systemctl/managers.go index 2c6b3f7786bf..1302923a1cb3 100644 --- a/backend/utils/systemctl/managers.go +++ b/backend/utils/systemctl/managers.go @@ -297,7 +297,7 @@ func (m *sysvinitManager) BuildCommand(action string, config *ServiceConfig) ([] "sh", "-c", fmt.Sprintf("if ls /etc/rc*.d/S*%s >/dev/null 2>&1; then echo 'enabled'; else echo 'disabled'; fi", service)}, nil - case "is-active": + case "is-active", "status": return []string{ "sh", "-c", @@ -335,23 +335,6 @@ func (m *sysvinitManager) ParseStatus(output string, config *ServiceConfig, stat return result, err } return false, fmt.Errorf("unsupported status type: %s", statusType) - // service := config.ServiceName[m.name] - // serviceRegex := regexp.MustCompile(fmt.Sprintf(`\b%s\b`, regexp.QuoteMeta(service))) - // lines := strings.Split(output, "\n") - // for _, line := range lines { - // if serviceRegex.MatchString(line) { - // if strings.Contains(line, "not found") { - // return false, nil - // } - // result, err := m.baseManager.ParseStatus(line, config, statusType) - // if err != nil { - // return false, err - // } - // return result, nil - // } - // } - - // return false, nil } func (m *sysvinitManager) FindServices(keyword string) ([]string, error) { @@ -373,10 +356,12 @@ type openrcManager struct{ baseManager } func newOpenrcManager() ServiceManager { return &openrcManager{baseManager{ - name: "openrc", - cmdTool: "rc-service", - activeRegex: regexp.MustCompile(`(?i)^\s*status:\s+(started|running|active)\s*$`), - enabledRegex: regexp.MustCompile(`(?i)^[^\|]+\|\s*(default|enabled)\b.*$`), + name: "openrc", + cmdTool: "rc-service", + // activeRegex: regexp.MustCompile(`(?i)^\s*status:\s+(started|running|active)\s*$`), + // enabledRegex: regexp.MustCompile(`(?i)^[^\|]+\|\s*(default|enabled)\b.*$`), + activeRegex: regexp.MustCompile(`(?i)(?:^|\s)\b(running|active)\b(?:$|\s)`), + enabledRegex: regexp.MustCompile(`(?i)(?:^|\s)\b(enabled)\b(?:$|\s)`), }} } func (m *openrcManager) IsAvailable() bool { @@ -386,25 +371,39 @@ func (m *openrcManager) IsAvailable() bool { func (m *openrcManager) ServiceExists(config *ServiceConfig) (bool, error) { return m.commonServiceExists(config, func(name string) (bool, error) { - ctx, cancel := context.WithTimeout(context.Background(), serviceCheckTimeout) - defer cancel() - out, err := executeCommand(ctx, m.cmdTool, "-l") - if err != nil { - return false, fmt.Errorf("rc-service -l failed: %w", err) + _, err := os.Stat(filepath.Join("/etc/init.d", name)) + if os.IsNotExist(err) { + return false, nil + } else if err != nil { + return false, fmt.Errorf("stat /etc/init.d/%s failed: %w", name, err) } - return bytes.Contains(out, []byte(name)), nil + return true, nil }) } func (m *openrcManager) BuildCommand(action string, config *ServiceConfig) ([]string, error) { cmdArgs := m.buildBaseCommand() service := config.ServiceName[m.name] - if action == "is-enabled" { - cmdArgs = []string{"rc-update", "check", service} + switch action { + case "is-enabled": + return []string{ + "sh", + "-c", + fmt.Sprintf("if ls /etc/runlevels/default/%s >/dev/null 2>&1; then echo 'enabled'; else echo 'disabled'; fi", service)}, nil + case "is-active", "status": + return []string{ + "sh", + "-c", + fmt.Sprintf("if service %s status >/dev/null 2>&1; then echo 'active'; else echo 'inactive'; fi", service), + }, nil + case "enable": + return []string{"rc-update", "add", service, "default"}, nil + case "disable": + return []string{"rc-update", "del", service, "default"}, nil + default: + cmdArgs = append(cmdArgs, service, action) return cmdArgs, nil } - cmdArgs = append(cmdArgs, service, action) - return cmdArgs, nil } func (m *openrcManager) ParseStatus(output string, config *ServiceConfig, statusType string) (bool, error) { @@ -418,19 +417,15 @@ func (m *openrcManager) ParseStatus(output string, config *ServiceConfig, status return result, nil } func (m *openrcManager) FindServices(keyword string) ([]string, error) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - out, err := executeCommand(ctx, m.cmdTool, "-l") + files, err := os.ReadDir("/etc/init.d/") if err != nil { - return nil, fmt.Errorf("failed to list openrc services: %w", err) + return nil, fmt.Errorf("failed to read init.d directory: %w", err) } - + keyword = strings.ToLower(keyword) var services []string - lines := strings.Split(string(out), "\n") - for _, line := range lines { - if strings.Contains(line, keyword) { - services = append(services, strings.TrimSpace(line)) + for _, file := range files { + if strings.Contains(file.Name(), keyword) { + services = append(services, file.Name()) } } return services, nil From ef3f60c65b87540138564fdabec0e75b66bd8a90 Mon Sep 17 00:00:00 2001 From: gcsong023 Date: Fri, 18 Apr 2025 11:03:58 +0800 Subject: [PATCH 2/7] =?UTF-8?q?feat(backend):=20=E4=BC=98=E5=8C=96=20Fail2?= =?UTF-8?q?ban=20=E5=88=9D=E5=A7=8B=E5=8C=96=E9=85=8D=E7=BD=AE=E4=BB=A5?= =?UTF-8?q?=E6=94=AF=E6=8C=81=20Alpine=20=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增加对 Alpine 系统的特殊配置支持 - 改进防火墙类型检测逻辑,支持多种防火墙服务 - 增加 SSH 端口和认证日志路径的自动检测 - 优化配置文件模板,提高兼容性和安全性 --- backend/utils/toolbox/fail2ban.go | 120 +++++++++++++++++++++++++----- 1 file changed, 101 insertions(+), 19 deletions(-) diff --git a/backend/utils/toolbox/fail2ban.go b/backend/utils/toolbox/fail2ban.go index 08848780afc1..a2c74e1bd765 100644 --- a/backend/utils/toolbox/fail2ban.go +++ b/backend/utils/toolbox/fail2ban.go @@ -142,7 +142,32 @@ func initLocalFile() error { return err } defer f.Close() - initFile := `#DEFAULT-START + var initFile string + if systemctl.GetGlobalManager().Name() == "openrc" { + initFile = `[sshd] +enabled = true +filter = alpine-sshd +port = $ssh_port +logpath = $logpath +maxretry = 2 +banaction = $banaction + +[sshd-ddos] +enabled = true +filter = alpine-sshd-ddos +port = $ssh_port +logpath = /var/log/messages +maxretry = 2 + +[sshd-key] +enabled = true +filter = alpine-sshd-key +port = $ssh_port +logpath = /var/log/messages +maxretry = 2 +` + } else { + initFile = `# DEFAULT-START [DEFAULT] bantime = 600 findtime = 300 @@ -152,36 +177,93 @@ action = %(action_mwl)s #DEFAULT-END [sshd] -ignoreip = 127.0.0.1/8 +ignoreip = 127.0.0.1/8 ::1 enabled = true filter = sshd -port = 22 +port = $ssh_port maxretry = 5 findtime = 300 bantime = 600 banaction = $banaction action = %(action_mwl)s -logpath = $logpath` - - banaction := "" - if active, _ := systemctl.IsActive("firewalld"); active { - banaction = "firewallcmd-ipset" - } else if active, _ := systemctl.IsActive("ufw"); active { - banaction = "ufw" - } else { - banaction = "iptables-allports" +logpath = $logpath +maxmatches = 3 +` } - initFile = strings.ReplaceAll(initFile, "$banaction", banaction) + // 检测防火墙类型 + banaction := detectFirewall() - logPath := "" - if _, err := os.Stat("/var/log/secure"); err == nil { - logPath = "/var/log/secure" - } else { - logPath = "/var/log/auth.log" - } + // 检测SSH端口(支持Alpine的sshd_config位置) + sshPort := detectSSHPort() + + // 检测日志路径(兼容Alpine的多种日志位置) + logPath := detectAuthLogPath() + + // 执行变量替换 + initFile = strings.ReplaceAll(initFile, "$banaction", banaction) initFile = strings.ReplaceAll(initFile, "$logpath", logPath) + initFile = strings.ReplaceAll(initFile, "$ssh_port", sshPort) + if err := os.WriteFile(defaultPath, []byte(initFile), 0640); err != nil { return err } return nil } + +func detectFirewall() string { + if active, _ := systemctl.IsActive("firewalld"); active { + return "firewallcmd-ipset" + } + if active, _ := systemctl.IsActive("ufw"); active { + return "ufw" + } + if active, _ := systemctl.IsActive("iptables"); active { + return "iptables-allports" + } + if active, _ := systemctl.IsActive("nftables"); active { + return "nftables-allports" + } + return "iptables-allports" +} + +func detectAuthLogPath() string { + paths := []string{ + "/var/log/auth.log", // 常见默认路径 + "/var/log/messages", // Alpine系统日志 + "/var/log/secure", // RHEL风格路径 + "/var/log/syslog", // Debian风格路径 + } + + for _, path := range paths { + if _, err := os.Stat(path); err == nil { + return path + } + } + return "/var/log/auth.log" +} + +func detectSSHPort() string { + // 检查标准sshd_config路径 + configPaths := []string{ + "/etc/ssh/sshd_config", + "/etc/sshd_config", + } + + for _, path := range configPaths { + data, err := os.ReadFile(path) + if err != nil { + continue + } + + lines := strings.Split(string(data), "\n") + for _, line := range lines { + if strings.HasPrefix(strings.TrimSpace(line), "Port") { + parts := strings.Fields(line) + if len(parts) >= 2 { + return parts[1] + } + } + } + } + return "22" // 默认SSH端口 +} From 99dc8f1f8fc31d308b676842d7f618efe09e1216 Mon Sep 17 00:00:00 2001 From: gcsong023 Date: Fri, 18 Apr 2025 11:04:46 +0800 Subject: [PATCH 3/7] =?UTF-8?q?refactor(backend):=20=E9=87=8D=E6=9E=84=20S?= =?UTF-8?q?SH=20=E6=97=A5=E5=BF=97=E8=A7=A3=E6=9E=90=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 改进了对不同日志格式的支持,包括 secure, auth 和 messages 文件 - 优化了日志解析逻辑,提高了代码的可读性和可维护性 - 增加了对 RFC3339 时间格式的支持 - 改善了对失败登录尝试的解析,包括无效用户和连接关闭的情况 - 重构了日期解析和 IP 地址验证的逻辑 --- backend/app/service/ssh.go | 279 ++++++++++++++++++++++--------------- 1 file changed, 168 insertions(+), 111 deletions(-) diff --git a/backend/app/service/ssh.go b/backend/app/service/ssh.go index df7df89a7040..65b016f891f6 100644 --- a/backend/app/service/ssh.go +++ b/backend/app/service/ssh.go @@ -2,6 +2,7 @@ package service import ( "fmt" + "net" "os" "os/user" "path" @@ -291,7 +292,7 @@ func (u *SSHService) LoadLog(c *gin.Context, req dto.SearchSSHLog) (*dto.SSHLog, if err != nil { return err } - if !info.IsDir() && (strings.HasPrefix(info.Name(), "secure") || strings.HasPrefix(info.Name(), "auth")) { + if !info.IsDir() && (strings.HasPrefix(info.Name(), "secure") || strings.HasPrefix(info.Name(), "auth") || strings.HasPrefix(info.Name(), "messages")) { if !strings.HasSuffix(info.Name(), ".gz") { fileList = append(fileList, sshFileItem{Name: pathItem, Year: info.ModTime().Year()}) return nil @@ -319,7 +320,8 @@ func (u *SSHService) LoadLog(c *gin.Context, req dto.SearchSSHLog) (*dto.SSHLog, nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd()) for _, file := range fileList { commandItem := "" - if strings.HasPrefix(path.Base(file.Name), "secure") { + switch { + case strings.HasPrefix(path.Base(file.Name), "secure"): switch req.Status { case constant.StatusSuccess: commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command) @@ -328,8 +330,16 @@ func (u *SSHService) LoadLog(c *gin.Context, req dto.SearchSSHLog) (*dto.SSHLog, default: commandItem = fmt.Sprintf("cat %s | grep -aE '(Failed password for|Accepted)' %s", file.Name, command) } - } - if strings.HasPrefix(path.Base(file.Name), "auth.log") { + case strings.HasPrefix(path.Base(file.Name), "messages"): + switch req.Status { + case constant.StatusSuccess: + commandItem = fmt.Sprintf("cat %s | grep -aE 'sshd.*Accepted (password|publickey)' %s", file.Name, command) + case constant.StatusFailed: + commandItem = fmt.Sprintf("cat %s | grep -aE 'sshd.*(Failed password for|Connection closed by authenticating user)' %s", file.Name, command) + default: + commandItem = fmt.Sprintf("cat %s | grep -aE 'sshd.*(Accepted|Failed password for|Connection closed)' %s", file.Name, command) + } + case strings.HasPrefix(path.Base(file.Name), "auth.log"): switch req.Status { case constant.StatusSuccess: commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command) @@ -425,107 +435,160 @@ func loadSSHData(c *gin.Context, command string, showCountFrom, showCountTo, cur lines := strings.Split(string(stdout2), "\n") for i := len(lines) - 1; i >= 0; i-- { var itemData dto.SSHHistory - switch { - case strings.Contains(lines[i], "Failed password for"): - itemData = loadFailedSecureDatas(lines[i]) - if len(itemData.Address) != 0 { - if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo { - itemData.Area, _ = geo.GetIPLocation(itemData.Address, common.GetLang(c)) - itemData.Date = loadDate(currentYear, itemData.DateStr, nyc) - datas = append(datas, itemData) - } - failedCount++ - } - case strings.Contains(lines[i], "Connection closed by authenticating user"): - itemData = loadFailedAuthDatas(lines[i]) - if len(itemData.Address) != 0 { - if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo { - itemData.Area, _ = geo.GetIPLocation(itemData.Address, common.GetLang(c)) - itemData.Date = loadDate(currentYear, itemData.DateStr, nyc) - datas = append(datas, itemData) - } - failedCount++ + line := strings.TrimSpace(lines[i]) + if line == "" { + continue + } + + parts := strings.Fields(line) + if len(parts) < 12 { + continue + } + + // 统一时间解析逻辑 + var dateStr string + var timeIndex int + if strings.Contains(parts[0], "-") { // 处理RFC3339时间格式 + t, err := time.Parse(time.RFC3339Nano, parts[0]) + if err == nil { + dateStr = t.Format("2006 Jan 2 15:04:05") + timeIndex = 0 } - case strings.Contains(lines[i], "Accepted "): - itemData = loadSuccessDatas(lines[i]) - if len(itemData.Address) != 0 { - if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo { - itemData.Area, _ = geo.GetIPLocation(itemData.Address, common.GetLang(c)) - itemData.Date = loadDate(currentYear, itemData.DateStr, nyc) - datas = append(datas, itemData) - } - successCount++ + } else { // 处理系统日志格式 + dateParts := parts[:3] + if len(dateParts) < 3 { + continue } + dateStr = strings.Join(dateParts, " ") + timeIndex = 3 + } + + // 根据日志类型解析内容 + switch { + case strings.Contains(line, "Failed password for"): + itemData = parseFailedPasswordLog(parts, timeIndex, dateStr) + case strings.Contains(line, "Connection closed by authenticating user"): + itemData = parseConnectionClosedLog(parts, timeIndex, dateStr) + case strings.Contains(line, "Accepted"): + itemData = parseAcceptedLog(parts, timeIndex, dateStr) + default: + continue + } + if itemData.Address == "" { + continue + } + + total := successCount + failedCount + if total >= showCountFrom && total < showCountTo { + itemData.Area, _ = geo.GetIPLocation(itemData.Address, common.GetLang(c)) + itemData.Date = parseLogDate(currentYear, dateStr, nyc) + datas = append(datas, itemData) + } + + if itemData.Status == constant.StatusSuccess { + successCount++ + } else { + failedCount++ + } + + if total >= showCountTo { + break } } return datas, successCount, failedCount } -func loadSuccessDatas(line string) dto.SSHHistory { - var data dto.SSHHistory - parts := strings.Fields(line) - index, dataStr := analyzeDateStr(parts) - if dataStr == "" { - return data - } - data.DateStr = dataStr - data.AuthMode = parts[4+index] - data.User = parts[6+index] - data.Address = parts[8+index] - data.Port = parts[10+index] - data.Status = constant.StatusSuccess +func parseFailedPasswordLog(parts []string, timeIndex int, dateStr string) dto.SSHHistory { + data := dto.SSHHistory{ + DateStr: dateStr, + Status: constant.StatusFailed, + AuthMode: "publickey", + } + + // 查找关键字段位置 + for i := timeIndex; i < len(parts); i++ { + switch parts[i] { + case "for": + if i+1 < len(parts) { + data.User = parts[i+1] + } + case "from": + if i+1 < len(parts) { + data.Address = parts[i+1] + } + case "port": + if i+1 < len(parts) { + data.Port = parts[i+1] + } + case "password": + data.AuthMode = "password" + } + } + + if strings.Contains(strings.Join(parts, " "), "invalid user") { + data.Message = "invalid user attempt" + } return data } -func loadFailedAuthDatas(line string) dto.SSHHistory { - var data dto.SSHHistory - parts := strings.Fields(line) - index, dataStr := analyzeDateStr(parts) - if dataStr == "" { - return data - } - data.DateStr = dataStr - switch index { - case 1: - data.User = parts[9] - case 2: - data.User = parts[10] - default: - data.User = parts[7] - } - data.AuthMode = parts[6+index] - data.Address = parts[9+index] - data.Port = parts[11+index] - data.Status = constant.StatusFailed - if strings.Contains(line, ": ") { - data.Message = strings.Split(line, ": ")[1] +func parseConnectionClosedLog(parts []string, timeIndex int, dateStr string) dto.SSHHistory { + data := dto.SSHHistory{ + DateStr: dateStr, + Status: constant.StatusFailed, + } + + // 解析Alpine格式的特殊字段 + fieldStart := timeIndex + 5 // 跳过时间、主机、进程字段 + for i := fieldStart; i < len(parts); i++ { + switch { + case parts[i] == "user": + if i+1 < len(parts) { + data.User = parts[i+1] + } + case parts[i] == "port": + if i+1 < len(parts) { + data.Port = parts[i+1] + } + case isIPAddress(parts[i]): + data.Address = parts[i] + } } return data } -func loadFailedSecureDatas(line string) dto.SSHHistory { - var data dto.SSHHistory - parts := strings.Fields(line) - index, dataStr := analyzeDateStr(parts) - if dataStr == "" { - return data - } - data.DateStr = dataStr - if strings.Contains(line, " invalid ") { - data.AuthMode = parts[4+index] - index += 2 - } else { - data.AuthMode = parts[4+index] + +func parseAcceptedLog(parts []string, timeIndex int, dateStr string) dto.SSHHistory { + data := dto.SSHHistory{ + DateStr: dateStr, + Status: constant.StatusSuccess, + AuthMode: "password", // 默认值 } - data.User = parts[6+index] - data.Address = parts[8+index] - data.Port = parts[10+index] - data.Status = constant.StatusFailed - if strings.Contains(line, ": ") { - data.Message = strings.Split(line, ": ")[1] + + // 处理不同日志格式 + fieldStart := timeIndex + 5 // 基础字段偏移 + for i := fieldStart; i < len(parts); i++ { + switch { + case parts[i] == "for": + if i+1 < len(parts) { + data.User = parts[i+1] + } + case parts[i] == "from": + if i+1 < len(parts) { + data.Address = parts[i+1] + } + case parts[i] == "port": + if i+1 < len(parts) { + data.Port = parts[i+1] + } + case strings.Contains(parts[i], "ssh2:"): + data.AuthMode = "publickey" + case parts[i] == "publickey": + data.AuthMode = "publickey" + case parts[i] == "password": + data.AuthMode = "password" + } } return data } - func handleGunzip(path string) error { if _, err := cmd.Execf("gunzip %s", path); err != nil { return err @@ -544,32 +607,26 @@ func loadServiceName() (string, error) { return serviceName, nil } -func loadDate(currentYear int, DateStr string, nyc *time.Location) time.Time { - itemDate, err := time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", currentYear, DateStr), nyc) - if err != nil { - itemDate, _ = time.ParseInLocation("2006 Jan 2 15:04:05", DateStr, nyc) +func parseLogDate(currentYear int, dateStr string, loc *time.Location) time.Time { + // 处理带年份和不带年份的情况 + formats := []string{ + "2006 Jan 2 15:04:05", + "Jan 2 15:04:05", } - return itemDate -} -func analyzeDateStr(parts []string) (int, string) { - t, err := time.Parse(time.RFC3339Nano, parts[0]) - if err == nil { - if len(parts) < 12 { - return 0, "" - } - return 0, t.Format("2006 Jan 2 15:04:05") - } - t, err = time.Parse(constant.DateTimeLayout, fmt.Sprintf("%s %s", parts[0], parts[1])) - if err == nil { - if len(parts) < 14 { - return 0, "" + for _, format := range formats { + t, err := time.ParseInLocation(format, dateStr, loc) + if err == nil { + if t.Year() == 0 { + return time.Date(currentYear, t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, loc) + } + return t } - return 1, t.Format("2006 Jan 2 15:04:05") } + return time.Now().In(loc) +} + +func isIPAddress(s string) bool { + return net.ParseIP(s) != nil - if len(parts) < 14 { - return 0, "" - } - return 2, fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]) } From 30fa677d4aeeb434dbe2afd794d981807334f9cf Mon Sep 17 00:00:00 2001 From: gcsong023 Date: Fri, 18 Apr 2025 14:59:58 +0800 Subject: [PATCH 4/7] =?UTF-8?q?refactor(upgrade):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=8D=87=E7=BA=A7=E6=9C=8D=E5=8A=A1=E4=B8=AD=E7=9A=84=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E8=84=9A=E6=9C=AC=E9=80=89=E6=8B=A9=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 selectInitScript 函数,根据系统初始化管理器类型选择合适的初始化脚本 - 支持 systemd、openrc 和 sysvinit 三种初始化管理器 - 对于 sysvinit,增加对 /etc/rc.common 文件存在性的判断,以区分不同的初始化脚本 - 默认情况下使用当前服务名称作为初始化脚本名称 --- backend/app/service/upgrade.go | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/backend/app/service/upgrade.go b/backend/app/service/upgrade.go index e6f833da65cf..a0d3762b00ec 100644 --- a/backend/app/service/upgrade.go +++ b/backend/app/service/upgrade.go @@ -153,7 +153,7 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error { }{ {path.Join(tmpDir, "1panel"), path.Join(binDir, "1panel"), 1}, {path.Join(tmpDir, "1pctl"), path.Join(binDir, "1pctl"), 2}, - {path.Join(tmpDir, currentServiceName), servicePath, 3}, + {selectInitScript(path.Join(tmpDir, "initscript"), currentServiceName), path.Join(servicePath, currentServiceName), 3}, } for _, update := range criticalUpdates { @@ -400,3 +400,26 @@ func loadArch() (string, error) { } return "", fmt.Errorf("unsupported such arch: %s", std) } + +func selectInitScript(path string, serviceName string) string { + path = strings.TrimSuffix(path, "/") + mgr := systemctl.GetGlobalManager().Name() + var serviceFileName string + switch mgr { + case "systemd": + serviceFileName = "1panel.service" + case "openrc": + serviceFileName = "1paneld.openrc" + case "sysvinit": + isWrt := systemctl.FileExist("/etc/rc.common") + if isWrt { + serviceFileName = "1paneld.procd" + } else { + serviceFileName = "1paneld.init" + } + default: + serviceFileName = serviceName + global.LOG.Warnf("[%s]unselect InitScript, used default: %s", mgr, serviceName) + } + return filepath.Join(path, serviceFileName) +} From 101c5876306002cccf252ae2a06515eb0e12b56e Mon Sep 17 00:00:00 2001 From: gcsong023 Date: Fri, 18 Apr 2025 23:23:29 +0800 Subject: [PATCH 5/7] =?UTF-8?q?fix(upgrade):=20=E4=BF=AE=E5=A4=8D=E5=8D=87?= =?UTF-8?q?=E7=BA=A7=E6=97=B6=E5=88=9D=E5=A7=8B=E5=8C=96=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改了 criticalUpdates 数组中的服务脚本更新逻辑 - 在 selectInitScript 函数中增加了复制脚本文件的逻辑,以应对服务名和脚本名不一致的情况 --- backend/app/service/upgrade.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/backend/app/service/upgrade.go b/backend/app/service/upgrade.go index a0d3762b00ec..8b32a5957405 100644 --- a/backend/app/service/upgrade.go +++ b/backend/app/service/upgrade.go @@ -153,7 +153,7 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error { }{ {path.Join(tmpDir, "1panel"), path.Join(binDir, "1panel"), 1}, {path.Join(tmpDir, "1pctl"), path.Join(binDir, "1pctl"), 2}, - {selectInitScript(path.Join(tmpDir, "initscript"), currentServiceName), path.Join(servicePath, currentServiceName), 3}, + {selectInitScript(path.Join(tmpDir, "initscript"), currentServiceName), servicePath, 3}, } for _, update := range criticalUpdates { @@ -421,5 +421,15 @@ func selectInitScript(path string, serviceName string) string { serviceFileName = serviceName global.LOG.Warnf("[%s]unselect InitScript, used default: %s", mgr, serviceName) } - return filepath.Join(path, serviceFileName) + sourcePath := filepath.Join(path, serviceFileName) + targetPath := filepath.Join(path, serviceName) + + if serviceFileName != serviceName { + if _, err := cmd.Execf("cp %s %s", sourcePath, targetPath); err != nil { + global.LOG.Errorf("Failed to copy init script from %s to %s: %v", + serviceFileName, serviceName, err) + } + } + + return targetPath } From c4e663129abceb694bd780d632524eccf48bb95b Mon Sep 17 00:00:00 2001 From: gcsong023 Date: Sat, 19 Apr 2025 10:21:04 +0800 Subject: [PATCH 6/7] =?UTF-8?q?feat(snap):=20=E6=B7=BB=E5=8A=A0=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E8=84=9A=E6=9C=AC=E5=88=B0=E5=BF=AB=E7=85=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在创建快照时,将服务脚本复制到 initscript 目录 - 然后将整个 initscript 目录复制到快照的目标目录 - 添加了日志输出,便于调试和记录 --- backend/app/service/snapshot_create.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/app/service/snapshot_create.go b/backend/app/service/snapshot_create.go index 7deb2a6374d0..0b43f5352646 100644 --- a/backend/app/service/snapshot_create.go +++ b/backend/app/service/snapshot_create.go @@ -71,7 +71,12 @@ func snapPanel(snap snapHelper, targetDir string) { if _, err := cmd.Execf("cp -r %s/lang %s", binDir, targetDir); err != nil { status = err.Error() } - if err := common.CopyFile(servicePath, targetDir); err != nil { + 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 From 70291cbc4be5d96510297ec9f52fbf9c5fd25e66 Mon Sep 17 00:00:00 2001 From: gcsong023 Date: Sat, 19 Apr 2025 10:23:15 +0800 Subject: [PATCH 7/7] =?UTF-8?q?refactor(backend):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=BF=AB=E7=85=A7=E6=81=A2=E5=A4=8D=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除了未使用的 import 语句 - 删除了注释掉的代码块 - 修改了 1Panel 服务恢复的逻辑,增加了对当前服务名称的获取 - 快照恢复过程中,根据宿主机类型,自动选择初始化服务脚本 - 添加了日志输出以提高可追踪性 - 优化了文件路径的处理方式 --- backend/app/service/snapshot_recover.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/backend/app/service/snapshot_recover.go b/backend/app/service/snapshot_recover.go index 131be4a65cc6..e1a35bbcbd3f 100644 --- a/backend/app/service/snapshot_recover.go +++ b/backend/app/service/snapshot_recover.go @@ -5,7 +5,6 @@ import ( "fmt" "os" "path" - "path/filepath" "strings" "sync" @@ -101,10 +100,6 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b 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 @@ -114,10 +109,6 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b } 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 @@ -136,11 +127,13 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b } if req.IsNew || snap.InterruptStep == "1PanelService" { servicePath, err := h.GetServicePath() + currentServiceName := h.GetServiceName() 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 { + global.LOG.Debugf("current service path: %s", servicePath) + if err := common.CopyFile(selectInitScript(path.Join(snapFileDir, "1panel/initscript"), currentServiceName), servicePath); err != nil { updateRecoverStatus(snap.ID, isRecover, "1PanelService", constant.StatusFailed, err.Error()) return }