mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-09-11 17:15:44 +08:00
WIP: refactor(service): Refactor OpenRC service manager (#8416)
Some checks failed
SonarCloud Scan / SonarCloud (push) Has been cancelled
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>
This commit is contained in:
parent
5bcbf32823
commit
6b379389d3
6 changed files with 355 additions and 190 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
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
|
||||
}
|
||||
} else { // 处理系统日志格式
|
||||
dateParts := parts[:3]
|
||||
if len(dateParts) < 3 {
|
||||
continue
|
||||
}
|
||||
dateStr = strings.Join(dateParts, " ")
|
||||
timeIndex = 3
|
||||
}
|
||||
|
||||
// 根据日志类型解析内容
|
||||
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++
|
||||
}
|
||||
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++
|
||||
}
|
||||
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
|
||||
func parseFailedPasswordLog(parts []string, timeIndex int, dateStr string) dto.SSHHistory {
|
||||
data := dto.SSHHistory{
|
||||
DateStr: dateStr,
|
||||
Status: constant.StatusFailed,
|
||||
AuthMode: "publickey",
|
||||
}
|
||||
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
|
||||
return data
|
||||
}
|
||||
|
||||
func loadFailedAuthDatas(line string) dto.SSHHistory {
|
||||
var data dto.SSHHistory
|
||||
parts := strings.Fields(line)
|
||||
index, dataStr := analyzeDateStr(parts)
|
||||
if dataStr == "" {
|
||||
return data
|
||||
// 查找关键字段位置
|
||||
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"
|
||||
}
|
||||
}
|
||||
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]
|
||||
}
|
||||
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]
|
||||
}
|
||||
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]
|
||||
|
||||
if strings.Contains(strings.Join(parts, " "), "invalid user") {
|
||||
data.Message = "invalid user attempt"
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
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 parseAcceptedLog(parts []string, timeIndex int, dateStr string) dto.SSHHistory {
|
||||
data := dto.SSHHistory{
|
||||
DateStr: dateStr,
|
||||
Status: constant.StatusSuccess,
|
||||
AuthMode: "password", // 默认值
|
||||
}
|
||||
|
||||
// 处理不同日志格式
|
||||
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)
|
||||
}
|
||||
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, ""
|
||||
}
|
||||
return 1, t.Format("2006 Jan 2 15:04:05")
|
||||
func parseLogDate(currentYear int, dateStr string, loc *time.Location) time.Time {
|
||||
// 处理带年份和不带年份的情况
|
||||
formats := []string{
|
||||
"2006 Jan 2 15:04:05",
|
||||
"Jan 2 15:04:05",
|
||||
}
|
||||
|
||||
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 2, fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2])
|
||||
return time.Now().In(loc)
|
||||
}
|
||||
|
||||
func isIPAddress(s string) bool {
|
||||
return net.ParseIP(s) != nil
|
||||
|
||||
}
|
||||
|
|
|
@ -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), servicePath, 3},
|
||||
}
|
||||
|
||||
for _, update := range criticalUpdates {
|
||||
|
@ -400,3 +400,36 @@ 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)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
`
|
||||
}
|
||||
// 检测防火墙类型
|
||||
banaction := detectFirewall()
|
||||
|
||||
// 检测SSH端口(支持Alpine的sshd_config位置)
|
||||
sshPort := detectSSHPort()
|
||||
|
||||
// 检测日志路径(兼容Alpine的多种日志位置)
|
||||
logPath := detectAuthLogPath()
|
||||
|
||||
// 执行变量替换
|
||||
initFile = strings.ReplaceAll(initFile, "$banaction", banaction)
|
||||
|
||||
logPath := ""
|
||||
if _, err := os.Stat("/var/log/secure"); err == nil {
|
||||
logPath = "/var/log/secure"
|
||||
} else {
|
||||
logPath = "/var/log/auth.log"
|
||||
}
|
||||
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端口
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue