Files
1Panel/agent/app/service/setting.go
ssongliu 7915230121 refactor: rebuild firewall management (#13628)
* refactor(firewall): rebuild rule management foundation

* refactor(firewall): streamline rule checks and inventory

* feat(firewall): improve native rule inventory

* refactor(firewall): refine rule management

* feat: add Docker port guard

* feat(firewall): support native nftables

* feat(firewall): add configurable firewall selection

* feat(firewall): support nftables docker port guard

* refactor(firewall): complete v2 rule management and migration

* refactor(firewall): align state and API contracts

* refactor(firewall): unify rule management operations

* feat: refine firewall v2 rules and forwarding

* fix(firewall): harden dual-stack rule management

* refactor(firewall): consolidate rule validation and persistence
2026-08-24 12:51:34 +08:00

357 lines
11 KiB
Go

package service
import (
"encoding/base64"
"encoding/json"
"errors"
"path"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
"github.com/1Panel-dev/1Panel/agent/utils/ssh"
terminalai "github.com/1Panel-dev/1Panel/agent/utils/terminal/ai"
"github.com/jinzhu/copier"
)
type SettingService struct{}
type ISettingService interface {
GetSettingInfo() (*dto.SettingInfo, error)
GetTerminalAIInfo() (*dto.TerminalAIInfo, error)
GetFileManageAIInfo() (*dto.FileManageAIInfo, error)
GetFileHistorySettingInfo() (*response.FileHistorySettingInfo, error)
GetWebsiteDir() string
Update(key, value string) error
UpdateTerminalAI(req dto.TerminalAIInfo) error
UpdateFileManageAI(req dto.FileManageAIInfo) error
UpdateFileHistorySetting(req request.FileHistorySettingUpdate) error
TestConnByInfo(req dto.SSHConnData) bool
SaveConnInfo(req dto.SSHConnData) error
SetDefaultIsConn(req dto.SSHDefaultConn) error
GetSystemProxy() (*dto.SystemProxy, error)
GetLocalConn() dto.SSHConnData
GetLocalConnForSSH() (dto.SSHConnData, error)
SaveDescription(req dto.CommonDescription) error
}
func NewISettingService() ISettingService {
return &SettingService{}
}
func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) {
setting, err := settingRepo.GetList()
if err != nil {
return nil, buserr.New("ErrRecordNotFound")
}
settingMap := make(map[string]string)
for _, set := range setting {
settingMap[set.Key] = set.Value
}
var info dto.SettingInfo
arr, err := json.Marshal(settingMap)
if err != nil {
return nil, err
}
if err := json.Unmarshal(arr, &info); err != nil {
return nil, err
}
info.LocalTime = time.Now().Format("2006-01-02 15:04:05 MST -0700")
return &info, err
}
func (u *SettingService) GetTerminalAIInfo() (*dto.TerminalAIInfo, error) {
info := &dto.TerminalAIInfo{
AIStatus: constant.StatusDisable,
AIAccountID: "",
AIPrefix: constant.DefaultTerminalAIPrefix,
AIRiskCommands: "[]",
AIRiskCommandsDefault: constant.DefaultTerminalAIRiskCommands,
}
if value, err := settingRepo.GetValueByKey("AIStatus"); err == nil && value != "" {
info.AIStatus = value
}
if value, err := settingRepo.GetValueByKey("AIAccountID"); err == nil {
info.AIAccountID = value
}
if value, err := settingRepo.GetValueByKey("AIPrefix"); err == nil {
info.AIPrefix = value
}
if value, err := settingRepo.GetValueByKey("AIRiskCommands"); err == nil && value != "" {
info.AIRiskCommands = value
}
return info, nil
}
func (u *SettingService) GetFileManageAIInfo() (*dto.FileManageAIInfo, error) {
info := &dto.FileManageAIInfo{
AIStatus: constant.StatusDisable,
AIAccountID: "",
}
if value, err := settingRepo.GetValueByKey("FileAIStatus"); err == nil && value != "" {
info.AIStatus = value
}
if value, err := settingRepo.GetValueByKey("FileAIAccountID"); err == nil {
info.AIAccountID = value
}
return info, nil
}
func (u *SettingService) GetFileHistorySettingInfo() (*response.FileHistorySettingInfo, error) {
return historyService.GetSettingInfo()
}
func (u *SettingService) GetWebsiteDir() string {
value, _ := settingRepo.GetValueByKey("WEBSITE_DIR")
if value == "" {
return path.Join(global.Dir.BaseDir, "1panel", "www")
}
return value
}
func (u *SettingService) Update(key, value string) error {
oldValue := constant.FirewallPortWhiteListValue
if key == constant.FirewallPortWhiteList {
if _, err := firewall.ParsePortWhitelist(value); err != nil {
return err
}
if val, err := settingRepo.GetValueByKey(key); err == nil {
oldValue = val
}
}
if err := settingRepo.UpdateOrCreate(key, value); err != nil {
return err
}
if key == constant.FirewallPortWhiteList {
return SyncFirewallPortWhitelistAfterUpdate(oldValue)
}
return nil
}
func (u *SettingService) UpdateTerminalAI(req dto.TerminalAIInfo) error {
if strings.EqualFold(strings.TrimSpace(req.AIStatus), constant.StatusEnable) {
accountID, err := strconv.ParseUint(strings.TrimSpace(req.AIAccountID), 10, 64)
if err != nil || accountID == 0 {
return buserr.New("ErrAgentAccountIDRequired")
}
currentStatus, _ := settingRepo.GetValueByKey("AIStatus")
currentAccountID, _ := settingRepo.GetValueByKey("AIAccountID")
needValidate := !strings.EqualFold(strings.TrimSpace(currentStatus), constant.StatusEnable) ||
strings.TrimSpace(currentAccountID) != strings.TrimSpace(req.AIAccountID)
if needValidate {
if err := terminalai.ValidateTerminalAccount(uint(accountID)); err != nil {
return buserr.WithErr("ErrAgentAccountUnavailable", err)
}
}
}
if err := settingRepo.UpdateOrCreate("AIStatus", req.AIStatus); err != nil {
return err
}
if err := settingRepo.UpdateOrCreate("AIAccountID", req.AIAccountID); err != nil {
return err
}
if err := settingRepo.UpdateOrCreate("AIPrefix", req.AIPrefix); err != nil {
return err
}
if err := settingRepo.UpdateOrCreate("AIRiskCommands", req.AIRiskCommands); err != nil {
return err
}
terminalai.InvalidateTerminalRuntimeCache()
return nil
}
func (u *SettingService) UpdateFileManageAI(req dto.FileManageAIInfo) error {
if strings.EqualFold(strings.TrimSpace(req.AIStatus), constant.StatusEnable) {
accountID, err := strconv.ParseUint(strings.TrimSpace(req.AIAccountID), 10, 64)
if err != nil || accountID == 0 {
return buserr.New("ErrAgentAccountIDRequired")
}
currentStatus, _ := settingRepo.GetValueByKey("FileAIStatus")
currentAccountID, _ := settingRepo.GetValueByKey("FileAIAccountID")
needValidate := !strings.EqualFold(strings.TrimSpace(currentStatus), constant.StatusEnable) ||
strings.TrimSpace(currentAccountID) != strings.TrimSpace(req.AIAccountID)
if needValidate {
if err := terminalai.ValidateTerminalAccount(uint(accountID)); err != nil {
return buserr.WithErr("ErrAgentAccountUnavailable", err)
}
}
}
accountVal := strings.TrimSpace(req.AIAccountID)
if !strings.EqualFold(strings.TrimSpace(req.AIStatus), constant.StatusEnable) {
accountVal = ""
}
if err := settingRepo.UpdateOrCreate("FileAIStatus", req.AIStatus); err != nil {
return err
}
if err := settingRepo.UpdateOrCreate("FileAIAccountID", accountVal); err != nil {
return err
}
terminalai.InvalidateFileAIRuntimeCache()
return nil
}
func (u *SettingService) UpdateFileHistorySetting(req request.FileHistorySettingUpdate) error {
return historyService.UpdateSetting(req)
}
func (u *SettingService) TestConnByInfo(req dto.SSHConnData) bool {
if req.AuthMode == "password" && len(req.Password) != 0 {
password, err := base64.StdEncoding.DecodeString(req.Password)
if err != nil {
return false
}
req.Password = string(password)
}
if req.AuthMode == "key" && len(req.PrivateKey) != 0 {
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
if err != nil {
return false
}
req.PrivateKey = string(privateKey)
}
var connInfo ssh.ConnInfo
_ = copier.Copy(&connInfo, &req)
connInfo.PrivateKey = []byte(req.PrivateKey)
if len(req.PassPhrase) != 0 {
connInfo.PassPhrase = []byte(req.PassPhrase)
}
client, err := ssh.NewClient(connInfo)
if err != nil {
return false
}
defer client.Close()
return true
}
func (u *SettingService) SaveConnInfo(req dto.SSHConnData) error {
if req.AuthMode == "password" && len(req.Password) != 0 {
password, err := base64.StdEncoding.DecodeString(req.Password)
if err != nil {
return err
}
req.Password = string(password)
}
if req.AuthMode == "key" && len(req.PrivateKey) != 0 {
privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
if err != nil {
return err
}
req.PrivateKey = string(privateKey)
}
var connInfo ssh.ConnInfo
_ = copier.Copy(&connInfo, &req)
connInfo.PrivateKey = []byte(req.PrivateKey)
if len(req.PassPhrase) != 0 {
connInfo.PassPhrase = []byte(req.PassPhrase)
}
client, err := ssh.NewClient(connInfo)
if err != nil {
return err
}
defer client.Close()
var connItem model.LocalConnInfo
_ = copier.Copy(&connItem, &req)
localConn, _ := json.Marshal(&connItem)
connAfterEncrypt, _ := encrypt.StringEncrypt(string(localConn))
_ = settingRepo.Update("LocalSSHConn", connAfterEncrypt)
return nil
}
func (u *SettingService) SetDefaultIsConn(req dto.SSHDefaultConn) error {
if req.DefaultConn == constant.StatusDisable && req.WithReset {
if err := settingRepo.Update("LocalSSHConn", ""); err != nil {
return err
}
}
return settingRepo.Update("LocalSSHConnShow", req.DefaultConn)
}
func (u *SettingService) GetSystemProxy() (*dto.SystemProxy, error) {
systemProxy := dto.SystemProxy{}
systemProxy.Type, _ = settingRepo.GetValueByKey("ProxyType")
systemProxy.URL, _ = settingRepo.GetValueByKey("ProxyUrl")
systemProxy.Port, _ = settingRepo.GetValueByKey("ProxyPort")
systemProxy.User, _ = settingRepo.GetValueByKey("ProxyUser")
passwd, _ := settingRepo.GetValueByKey("ProxyPasswd")
systemProxy.Password, _ = encrypt.StringDecrypt(passwd)
return &systemProxy, nil
}
func (u *SettingService) loadLocalConn() dto.SSHConnData {
var data dto.SSHConnData
localSSHConnShow, _ := settingRepo.GetValueByKey("LocalSSHConnShow")
data.LocalSSHConnShow = localSSHConnShow
connItem, _ := settingRepo.GetValueByKey("LocalSSHConn")
if len(connItem) == 0 {
return data
}
connInfoInDB, _ := encrypt.StringDecrypt(connItem)
if err := json.Unmarshal([]byte(connInfoInDB), &data); err != nil {
data.LocalSSHConnShow = localSSHConnShow
return data
}
data.LocalSSHConnShow = localSSHConnShow
return data
}
func (u *SettingService) GetLocalConn() dto.SSHConnData {
data := u.loadLocalConn()
if len(data.Password) != 0 {
data.Password = base64.StdEncoding.EncodeToString([]byte(data.Password))
}
if len(data.PrivateKey) != 0 {
data.PrivateKey = base64.StdEncoding.EncodeToString([]byte(data.PrivateKey))
}
if len(data.PassPhrase) != 0 {
data.PassPhrase = base64.StdEncoding.EncodeToString([]byte(data.PassPhrase))
}
return data
}
func (u *SettingService) GetLocalConnForSSH() (dto.SSHConnData, error) {
data := u.loadLocalConn()
if len(data.Addr) == 0 {
return data, errors.New("no such ssh conn info in db")
}
return data, nil
}
func (u *SettingService) SaveDescription(req dto.CommonDescription) error {
if len(req.Description) == 0 && !req.IsPinned {
_ = settingRepo.DelDescription(req.ID)
return nil
}
data, _ := settingRepo.GetDescription(settingRepo.WithByDescriptionID(req.ID), repo.WithByType(req.Type), repo.WithByDetailType(req.DetailType))
if data.ID == "" {
if err := copier.Copy(&data, &req); err != nil {
return err
}
return settingRepo.CreateDescription(&data)
}
valMap := make(map[string]interface{})
valMap["type"] = req.Type
valMap["detail_type"] = req.DetailType
valMap["is_pinned"] = req.IsPinned
valMap["description"] = req.Description
return settingRepo.UpdateDescription(data.ID, valMap)
}