Skip to content

Commit 6b37938

Browse files
authored
WIP: refactor(service): Refactor OpenRC service manager (#8416)
* 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>
1 parent 5bcbf32 commit 6b37938

6 files changed

Lines changed: 349 additions & 184 deletions

File tree

backend/app/service/snapshot_create.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,12 @@ func snapPanel(snap snapHelper, targetDir string) {
7171
if _, err := cmd.Execf("cp -r %s/lang %s", binDir, targetDir); err != nil {
7272
status = err.Error()
7373
}
74-
if err := common.CopyFile(servicePath, targetDir); err != nil {
74+
initScriptDir := path.Join(constant.DataDir, "initscript")
75+
if err := common.CopyFile(servicePath, initScriptDir); err != nil {
76+
status = err.Error()
77+
}
78+
global.LOG.Debugf("from %s copy init script to %s", initScriptDir, path.Join(targetDir, "initscript"))
79+
if err := common.CopyDirs(initScriptDir, path.Join(targetDir, "initscript")); err != nil { // copy init script to targetDir
7580
status = err.Error()
7681
}
7782
snap.Status.Panel = status

backend/app/service/snapshot_recover.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"os"
77
"path"
8-
"path/filepath"
98
"strings"
109
"sync"
1110

@@ -101,10 +100,6 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
101100

102101
if req.IsNew || snap.InterruptStep == "1PanelBinary" {
103102
binDir := systemctl.BinaryPath
104-
// if err != nil {
105-
// updateRecoverStatus(snap.ID, isRecover, "GetBinaryPath", constant.StatusFailed, fmt.Sprintf("get binary path failed: %v", err))
106-
// return
107-
// }
108103
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel"), binDir); err != nil {
109104
updateRecoverStatus(snap.ID, isRecover, "1PanelBinary", constant.StatusFailed, err.Error())
110105
return
@@ -114,10 +109,6 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
114109
}
115110
if req.IsNew || snap.InterruptStep == "1PctlBinary" {
116111
binDir := systemctl.BinaryPath
117-
// if err != nil {
118-
// updateRecoverStatus(snap.ID, isRecover, "GetBinaryPath", constant.StatusFailed, fmt.Sprintf("get binary path failed: %v", err))
119-
// return
120-
// }
121112
if err := recoverPanel(path.Join(snapFileDir, "1panel/1pctl"), binDir); err != nil {
122113
updateRecoverStatus(snap.ID, isRecover, "1PctlBinary", constant.StatusFailed, err.Error())
123114
return
@@ -136,11 +127,13 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
136127
}
137128
if req.IsNew || snap.InterruptStep == "1PanelService" {
138129
servicePath, err := h.GetServicePath()
130+
currentServiceName := h.GetServiceName()
139131
if err != nil {
140132
updateRecoverStatus(snap.ID, isRecover, "GetServicePath", constant.StatusFailed, fmt.Sprintf("get service path failed: %v", err))
141133
return
142134
}
143-
if err := recoverPanel(path.Join(snapFileDir, "1panel/"+filepath.Base(servicePath)), filepath.Dir(servicePath)); err != nil {
135+
global.LOG.Debugf("current service path: %s", servicePath)
136+
if err := common.CopyFile(selectInitScript(path.Join(snapFileDir, "1panel/initscript"), currentServiceName), servicePath); err != nil {
144137
updateRecoverStatus(snap.ID, isRecover, "1PanelService", constant.StatusFailed, err.Error())
145138
return
146139
}

backend/app/service/ssh.go

Lines changed: 168 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package service
22

33
import (
44
"fmt"
5+
"net"
56
"os"
67
"os/user"
78
"path"
@@ -291,7 +292,7 @@ func (u *SSHService) LoadLog(c *gin.Context, req dto.SearchSSHLog) (*dto.SSHLog,
291292
if err != nil {
292293
return err
293294
}
294-
if !info.IsDir() && (strings.HasPrefix(info.Name(), "secure") || strings.HasPrefix(info.Name(), "auth")) {
295+
if !info.IsDir() && (strings.HasPrefix(info.Name(), "secure") || strings.HasPrefix(info.Name(), "auth") || strings.HasPrefix(info.Name(), "messages")) {
295296
if !strings.HasSuffix(info.Name(), ".gz") {
296297
fileList = append(fileList, sshFileItem{Name: pathItem, Year: info.ModTime().Year()})
297298
return nil
@@ -319,7 +320,8 @@ func (u *SSHService) LoadLog(c *gin.Context, req dto.SearchSSHLog) (*dto.SSHLog,
319320
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
320321
for _, file := range fileList {
321322
commandItem := ""
322-
if strings.HasPrefix(path.Base(file.Name), "secure") {
323+
switch {
324+
case strings.HasPrefix(path.Base(file.Name), "secure"):
323325
switch req.Status {
324326
case constant.StatusSuccess:
325327
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,
328330
default:
329331
commandItem = fmt.Sprintf("cat %s | grep -aE '(Failed password for|Accepted)' %s", file.Name, command)
330332
}
331-
}
332-
if strings.HasPrefix(path.Base(file.Name), "auth.log") {
333+
case strings.HasPrefix(path.Base(file.Name), "messages"):
334+
switch req.Status {
335+
case constant.StatusSuccess:
336+
commandItem = fmt.Sprintf("cat %s | grep -aE 'sshd.*Accepted (password|publickey)' %s", file.Name, command)
337+
case constant.StatusFailed:
338+
commandItem = fmt.Sprintf("cat %s | grep -aE 'sshd.*(Failed password for|Connection closed by authenticating user)' %s", file.Name, command)
339+
default:
340+
commandItem = fmt.Sprintf("cat %s | grep -aE 'sshd.*(Accepted|Failed password for|Connection closed)' %s", file.Name, command)
341+
}
342+
case strings.HasPrefix(path.Base(file.Name), "auth.log"):
333343
switch req.Status {
334344
case constant.StatusSuccess:
335345
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
425435
lines := strings.Split(string(stdout2), "\n")
426436
for i := len(lines) - 1; i >= 0; i-- {
427437
var itemData dto.SSHHistory
428-
switch {
429-
case strings.Contains(lines[i], "Failed password for"):
430-
itemData = loadFailedSecureDatas(lines[i])
431-
if len(itemData.Address) != 0 {
432-
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
433-
itemData.Area, _ = geo.GetIPLocation(itemData.Address, common.GetLang(c))
434-
itemData.Date = loadDate(currentYear, itemData.DateStr, nyc)
435-
datas = append(datas, itemData)
436-
}
437-
failedCount++
438-
}
439-
case strings.Contains(lines[i], "Connection closed by authenticating user"):
440-
itemData = loadFailedAuthDatas(lines[i])
441-
if len(itemData.Address) != 0 {
442-
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
443-
itemData.Area, _ = geo.GetIPLocation(itemData.Address, common.GetLang(c))
444-
itemData.Date = loadDate(currentYear, itemData.DateStr, nyc)
445-
datas = append(datas, itemData)
446-
}
447-
failedCount++
438+
line := strings.TrimSpace(lines[i])
439+
if line == "" {
440+
continue
441+
}
442+
443+
parts := strings.Fields(line)
444+
if len(parts) < 12 {
445+
continue
446+
}
447+
448+
// 统一时间解析逻辑
449+
var dateStr string
450+
var timeIndex int
451+
if strings.Contains(parts[0], "-") { // 处理RFC3339时间格式
452+
t, err := time.Parse(time.RFC3339Nano, parts[0])
453+
if err == nil {
454+
dateStr = t.Format("2006 Jan 2 15:04:05")
455+
timeIndex = 0
448456
}
449-
case strings.Contains(lines[i], "Accepted "):
450-
itemData = loadSuccessDatas(lines[i])
451-
if len(itemData.Address) != 0 {
452-
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
453-
itemData.Area, _ = geo.GetIPLocation(itemData.Address, common.GetLang(c))
454-
itemData.Date = loadDate(currentYear, itemData.DateStr, nyc)
455-
datas = append(datas, itemData)
456-
}
457-
successCount++
457+
} else { // 处理系统日志格式
458+
dateParts := parts[:3]
459+
if len(dateParts) < 3 {
460+
continue
458461
}
462+
dateStr = strings.Join(dateParts, " ")
463+
timeIndex = 3
464+
}
465+
466+
// 根据日志类型解析内容
467+
switch {
468+
case strings.Contains(line, "Failed password for"):
469+
itemData = parseFailedPasswordLog(parts, timeIndex, dateStr)
470+
case strings.Contains(line, "Connection closed by authenticating user"):
471+
itemData = parseConnectionClosedLog(parts, timeIndex, dateStr)
472+
case strings.Contains(line, "Accepted"):
473+
itemData = parseAcceptedLog(parts, timeIndex, dateStr)
474+
default:
475+
continue
476+
}
477+
if itemData.Address == "" {
478+
continue
479+
}
480+
481+
total := successCount + failedCount
482+
if total >= showCountFrom && total < showCountTo {
483+
itemData.Area, _ = geo.GetIPLocation(itemData.Address, common.GetLang(c))
484+
itemData.Date = parseLogDate(currentYear, dateStr, nyc)
485+
datas = append(datas, itemData)
486+
}
487+
488+
if itemData.Status == constant.StatusSuccess {
489+
successCount++
490+
} else {
491+
failedCount++
492+
}
493+
494+
if total >= showCountTo {
495+
break
459496
}
460497
}
461498
return datas, successCount, failedCount
462499
}
463500

464-
func loadSuccessDatas(line string) dto.SSHHistory {
465-
var data dto.SSHHistory
466-
parts := strings.Fields(line)
467-
index, dataStr := analyzeDateStr(parts)
468-
if dataStr == "" {
469-
return data
470-
}
471-
data.DateStr = dataStr
472-
data.AuthMode = parts[4+index]
473-
data.User = parts[6+index]
474-
data.Address = parts[8+index]
475-
data.Port = parts[10+index]
476-
data.Status = constant.StatusSuccess
501+
func parseFailedPasswordLog(parts []string, timeIndex int, dateStr string) dto.SSHHistory {
502+
data := dto.SSHHistory{
503+
DateStr: dateStr,
504+
Status: constant.StatusFailed,
505+
AuthMode: "publickey",
506+
}
507+
508+
// 查找关键字段位置
509+
for i := timeIndex; i < len(parts); i++ {
510+
switch parts[i] {
511+
case "for":
512+
if i+1 < len(parts) {
513+
data.User = parts[i+1]
514+
}
515+
case "from":
516+
if i+1 < len(parts) {
517+
data.Address = parts[i+1]
518+
}
519+
case "port":
520+
if i+1 < len(parts) {
521+
data.Port = parts[i+1]
522+
}
523+
case "password":
524+
data.AuthMode = "password"
525+
}
526+
}
527+
528+
if strings.Contains(strings.Join(parts, " "), "invalid user") {
529+
data.Message = "invalid user attempt"
530+
}
477531
return data
478532
}
479533

480-
func loadFailedAuthDatas(line string) dto.SSHHistory {
481-
var data dto.SSHHistory
482-
parts := strings.Fields(line)
483-
index, dataStr := analyzeDateStr(parts)
484-
if dataStr == "" {
485-
return data
486-
}
487-
data.DateStr = dataStr
488-
switch index {
489-
case 1:
490-
data.User = parts[9]
491-
case 2:
492-
data.User = parts[10]
493-
default:
494-
data.User = parts[7]
495-
}
496-
data.AuthMode = parts[6+index]
497-
data.Address = parts[9+index]
498-
data.Port = parts[11+index]
499-
data.Status = constant.StatusFailed
500-
if strings.Contains(line, ": ") {
501-
data.Message = strings.Split(line, ": ")[1]
534+
func parseConnectionClosedLog(parts []string, timeIndex int, dateStr string) dto.SSHHistory {
535+
data := dto.SSHHistory{
536+
DateStr: dateStr,
537+
Status: constant.StatusFailed,
538+
}
539+
540+
// 解析Alpine格式的特殊字段
541+
fieldStart := timeIndex + 5 // 跳过时间、主机、进程字段
542+
for i := fieldStart; i < len(parts); i++ {
543+
switch {
544+
case parts[i] == "user":
545+
if i+1 < len(parts) {
546+
data.User = parts[i+1]
547+
}
548+
case parts[i] == "port":
549+
if i+1 < len(parts) {
550+
data.Port = parts[i+1]
551+
}
552+
case isIPAddress(parts[i]):
553+
data.Address = parts[i]
554+
}
502555
}
503556
return data
504557
}
505-
func loadFailedSecureDatas(line string) dto.SSHHistory {
506-
var data dto.SSHHistory
507-
parts := strings.Fields(line)
508-
index, dataStr := analyzeDateStr(parts)
509-
if dataStr == "" {
510-
return data
511-
}
512-
data.DateStr = dataStr
513-
if strings.Contains(line, " invalid ") {
514-
data.AuthMode = parts[4+index]
515-
index += 2
516-
} else {
517-
data.AuthMode = parts[4+index]
558+
559+
func parseAcceptedLog(parts []string, timeIndex int, dateStr string) dto.SSHHistory {
560+
data := dto.SSHHistory{
561+
DateStr: dateStr,
562+
Status: constant.StatusSuccess,
563+
AuthMode: "password", // 默认值
518564
}
519-
data.User = parts[6+index]
520-
data.Address = parts[8+index]
521-
data.Port = parts[10+index]
522-
data.Status = constant.StatusFailed
523-
if strings.Contains(line, ": ") {
524-
data.Message = strings.Split(line, ": ")[1]
565+
566+
// 处理不同日志格式
567+
fieldStart := timeIndex + 5 // 基础字段偏移
568+
for i := fieldStart; i < len(parts); i++ {
569+
switch {
570+
case parts[i] == "for":
571+
if i+1 < len(parts) {
572+
data.User = parts[i+1]
573+
}
574+
case parts[i] == "from":
575+
if i+1 < len(parts) {
576+
data.Address = parts[i+1]
577+
}
578+
case parts[i] == "port":
579+
if i+1 < len(parts) {
580+
data.Port = parts[i+1]
581+
}
582+
case strings.Contains(parts[i], "ssh2:"):
583+
data.AuthMode = "publickey"
584+
case parts[i] == "publickey":
585+
data.AuthMode = "publickey"
586+
case parts[i] == "password":
587+
data.AuthMode = "password"
588+
}
525589
}
526590
return data
527591
}
528-
529592
func handleGunzip(path string) error {
530593
if _, err := cmd.Execf("gunzip %s", path); err != nil {
531594
return err
@@ -544,32 +607,26 @@ func loadServiceName() (string, error) {
544607
return serviceName, nil
545608
}
546609

547-
func loadDate(currentYear int, DateStr string, nyc *time.Location) time.Time {
548-
itemDate, err := time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", currentYear, DateStr), nyc)
549-
if err != nil {
550-
itemDate, _ = time.ParseInLocation("2006 Jan 2 15:04:05", DateStr, nyc)
610+
func parseLogDate(currentYear int, dateStr string, loc *time.Location) time.Time {
611+
// 处理带年份和不带年份的情况
612+
formats := []string{
613+
"2006 Jan 2 15:04:05",
614+
"Jan 2 15:04:05",
551615
}
552-
return itemDate
553-
}
554616

555-
func analyzeDateStr(parts []string) (int, string) {
556-
t, err := time.Parse(time.RFC3339Nano, parts[0])
557-
if err == nil {
558-
if len(parts) < 12 {
559-
return 0, ""
560-
}
561-
return 0, t.Format("2006 Jan 2 15:04:05")
562-
}
563-
t, err = time.Parse(constant.DateTimeLayout, fmt.Sprintf("%s %s", parts[0], parts[1]))
564-
if err == nil {
565-
if len(parts) < 14 {
566-
return 0, ""
617+
for _, format := range formats {
618+
t, err := time.ParseInLocation(format, dateStr, loc)
619+
if err == nil {
620+
if t.Year() == 0 {
621+
return time.Date(currentYear, t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, loc)
622+
}
623+
return t
567624
}
568-
return 1, t.Format("2006 Jan 2 15:04:05")
569625
}
626+
return time.Now().In(loc)
627+
}
628+
629+
func isIPAddress(s string) bool {
630+
return net.ParseIP(s) != nil
570631

571-
if len(parts) < 14 {
572-
return 0, ""
573-
}
574-
return 2, fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2])
575632
}

0 commit comments

Comments
 (0)