WIP: refactor(service): Refactor OpenRC service manager (#8416)
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:
巴山夜语 2025-04-23 17:04:08 +08:00 committed by GitHub
parent 5bcbf32823
commit 6b379389d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 355 additions and 190 deletions

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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端口
}