Files
s-ui/backend/service/config.go
T
2024-12-25 10:57:17 +01:00

464 lines
11 KiB
Go

package service
import (
"encoding/json"
"os"
"s-ui/config"
"s-ui/core"
"s-ui/database"
"s-ui/database/model"
"s-ui/logger"
"strconv"
"time"
)
var (
LastUpdate int64
IsSystemd bool
corePtr *core.Core
)
type ConfigService struct {
ClientService
TlsService
SettingService
InboundService
OutboundService
EndpointService
}
type SingBoxConfig struct {
Log json.RawMessage `json:"log"`
Dns json.RawMessage `json:"dns"`
Ntp json.RawMessage `json:"ntp"`
Inbounds []json.RawMessage `json:"inbounds"`
Outbounds []json.RawMessage `json:"outbounds"`
Endpoints []json.RawMessage `json:"endpoints"`
Route json.RawMessage `json:"route"`
Experimental json.RawMessage `json:"experimental"`
}
func NewConfigService(core *core.Core) *ConfigService {
corePtr = core
return &ConfigService{}
}
func (s *ConfigService) InitConfig() error {
IsSystemd = config.IsSystemd()
return nil
}
func (s *ConfigService) GetConfig() (*SingBoxConfig, error) {
data, err := s.SettingService.GetConfig()
if err != nil {
return nil, err
}
singboxConfig := SingBoxConfig{}
err = json.Unmarshal([]byte(data), &singboxConfig)
if err != nil {
return nil, err
}
singboxConfig.Inbounds, err = s.InboundService.GetAllConfig(database.GetDB())
if err != nil {
return nil, err
}
singboxConfig.Outbounds, err = s.OutboundService.GetAllConfig(database.GetDB())
if err != nil {
return nil, err
}
singboxConfig.Endpoints, err = s.EndpointService.GetAllConfig(database.GetDB())
if err != nil {
return nil, err
}
return &singboxConfig, nil
}
func (s *ConfigService) StartCore() error {
singboxConfig, err := s.GetConfig()
if err != nil {
return err
}
rawConfig, err := json.MarshalIndent(singboxConfig, "", " ")
if err != nil {
return err
}
err = corePtr.Start(rawConfig)
if err != nil {
logger.Error("start sing-box err:", err.Error())
return err
}
logger.Info("sing-box started")
return nil
}
func (s *ConfigService) RestartCore() error {
err := s.StartCore()
if err != nil {
return err
}
return s.StartCore()
}
func (s *ConfigService) StopCore() error {
err := corePtr.Stop()
if err != nil {
return err
}
logger.Info("sing-box stopped")
return nil
}
func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) error {
var err error
var clientChanges, tlsChanges, inChanges, settingChanges, configChanges []model.Changes
if _, ok := changes["clients"]; ok {
err = json.Unmarshal([]byte(changes["clients"]), &clientChanges)
if err != nil {
return err
}
}
if _, ok := changes["tls"]; ok {
err = json.Unmarshal([]byte(changes["tls"]), &tlsChanges)
if err != nil {
return err
}
}
if _, ok := changes["inData"]; ok {
err = json.Unmarshal([]byte(changes["inData"]), &inChanges)
if err != nil {
return err
}
}
if _, ok := changes["settings"]; ok {
err = json.Unmarshal([]byte(changes["settings"]), &settingChanges)
if err != nil {
return err
}
}
if _, ok := changes["config"]; ok {
err = json.Unmarshal([]byte(changes["config"]), &configChanges)
if err != nil {
return err
}
}
db := database.GetDB()
tx := db.Begin()
defer func() {
if err == nil {
tx.Commit()
} else {
tx.Rollback()
}
}()
if len(clientChanges) > 0 {
err = s.ClientService.Save(tx, clientChanges)
if err != nil {
return err
}
}
if len(tlsChanges) > 0 {
err = s.TlsService.Save(tx, tlsChanges)
if err != nil {
return err
}
}
// if len(inChanges) > 0 {
// err = s.InDataService.Save(tx, inChanges)
// if err != nil {
// return err
// }
// }
if len(settingChanges) > 0 {
err = s.SettingService.Save(tx, settingChanges)
if err != nil {
return err
}
}
needRestart := false
if len(configChanges) > 0 {
singboxConfig, err := s.GetConfig()
if err != nil {
return err
}
newConfig := *singboxConfig
for _, change := range configChanges {
rawObject := change.Obj
switch change.Key {
case "all":
err = json.Unmarshal(rawObject, &newConfig)
if err != nil {
return err
}
needRestart = true
case "log":
newConfig.Log = rawObject
needRestart = true
case "dns":
newConfig.Dns = rawObject
needRestart = true
case "ntp":
newConfig.Ntp = rawObject
needRestart = true
case "route":
newConfig.Route = rawObject
needRestart = true
case "experimental":
newConfig.Experimental = rawObject
needRestart = true
case "inbounds":
if change.Action == "edit" {
var object map[string]interface{}
err = json.Unmarshal(newConfig.Inbounds[change.Index], &object)
if err != nil {
return err
}
if tag, ok := object["tag"].(string); ok {
err = corePtr.RemoveInbound(tag)
if err == nil {
err = corePtr.AddInbound(rawObject)
if err != nil {
needRestart = true
}
} else {
needRestart = true
}
} else {
needRestart = true
}
newConfig.Inbounds[change.Index] = rawObject
} else if change.Action == "del" {
var object map[string]interface{}
err = json.Unmarshal(newConfig.Inbounds[change.Index], &object)
if err != nil {
return err
}
if tag, ok := object["tag"].(string); ok {
err = corePtr.RemoveInbound(tag)
if err != nil {
needRestart = true
}
} else {
needRestart = true
}
newConfig.Inbounds = append(newConfig.Inbounds[:change.Index], newConfig.Inbounds[change.Index+1:]...)
} else {
newConfig.Inbounds = append(newConfig.Inbounds, rawObject)
err = corePtr.AddInbound(rawObject)
if err != nil {
logger.Debug(err)
needRestart = true
}
}
case "outbounds":
if change.Action == "edit" {
var object map[string]interface{}
err = json.Unmarshal(newConfig.Outbounds[change.Index], &object)
if err != nil {
return err
}
if tag, ok := object["tag"].(string); ok {
err = corePtr.RemoveOutbound(tag)
if err == nil {
err = corePtr.AddOutbound(rawObject)
if err != nil {
needRestart = true
}
} else {
needRestart = true
}
} else {
needRestart = true
}
newConfig.Outbounds[change.Index] = rawObject
} else if change.Action == "del" {
var object map[string]interface{}
err = json.Unmarshal(newConfig.Outbounds[change.Index], &object)
if err != nil {
return err
}
if tag, ok := object["tag"].(string); ok {
err = corePtr.RemoveOutbound(tag)
if err != nil {
needRestart = true
}
} else {
needRestart = true
}
newConfig.Outbounds = append(newConfig.Outbounds[:change.Index], newConfig.Outbounds[change.Index+1:]...)
} else {
err = corePtr.AddOutbound(rawObject)
if err != nil {
logger.Debug(err)
needRestart = true
}
newConfig.Outbounds = append(newConfig.Outbounds, rawObject)
}
}
}
err = s.Save(&newConfig, needRestart)
if err != nil {
return err
}
}
// Log changes
dt := time.Now().Unix()
allChanges := append(clientChanges, settingChanges...)
allChanges = append(allChanges, configChanges...)
allChanges = append(allChanges, tlsChanges...)
allChanges = append(allChanges, inChanges...)
if len(allChanges) > 0 {
for index := range allChanges {
allChanges[index].DateTime = dt
allChanges[index].Actor = loginUser
}
err = tx.Model(model.Changes{}).Create(&allChanges).Error
if err != nil {
return err
}
}
LastUpdate = dt
return nil
}
func (s *ConfigService) CheckChanges(lu string) (bool, error) {
if lu == "" {
return true, nil
}
if LastUpdate == 0 {
db := database.GetDB()
var count int64
err := db.Model(model.Changes{}).Where("date_time > " + lu).Count(&count).Error
if err == nil {
LastUpdate = time.Now().Unix()
}
return count > 0, err
} else {
intLu, err := strconv.ParseInt(lu, 10, 64)
return LastUpdate > intLu, err
}
}
func (s *ConfigService) Save(singboxConfig *SingBoxConfig, needRestart bool) error {
configPath := config.GetBinFolderPath()
_, err := os.Stat(configPath + "/config.json")
if os.IsNotExist(err) {
err = os.MkdirAll(configPath, 01764)
if err != nil {
return err
}
} else if err != nil {
return err
}
data, err := json.MarshalIndent(singboxConfig, "", " ")
if err != nil {
return err
}
err = os.WriteFile(configPath+"/config.json", data, 0764)
if err != nil {
return err
}
if needRestart {
err = s.RestartCore()
if err != nil {
return err
}
}
// s.Controller.Restart()
return nil
}
func (s *ConfigService) DepleteClients() error {
users, inboundIds, err := s.ClientService.DepleteClients()
if err != nil || len(users) == 0 || len(inboundIds) == 0 {
return err
}
// inbounds, err := s.InboundService.FromIds(inboundIds)
// if err != nil {
// return err
// }
// for inbound_index, inbound := range inbounds {
// var inboundJson map[string]interface{}
// json.Unmarshal(inbound.Options, &inboundJson)
// inbound_users, ok := inboundJson["users"].([]interface{})
// if ok {
// var updatedUsers []interface{}
// for _, user := range inbound_users {
// userMap, ok := user.(map[string]interface{})
// if ok {
// name, exists := userMap["name"].(string)
// if exists && s.contains(users, name) {
// // Skip the user exists
// continue
// }
// username, exists := userMap["username"].(string)
// if exists && s.contains(users, username) {
// // Skip the username exists
// continue
// }
// }
// updatedUsers = append(updatedUsers, user)
// }
// // Exception for Naive and ShadowTLSv3
// if len(updatedUsers) == 0 {
// if inboundJson["type"].(string) == "naive" ||
// (inboundJson["type"].(string) == "shadowtls" &&
// inboundJson["version"].(float64) == 3) {
// updatedUsers = append(updatedUsers, make(map[string]interface{}))
// }
// }
// inboundJson["users"] = updatedUsers
// }
// modifiedInbound, err := json.MarshalIndent(inboundJson, "", " ")
// if err != nil {
// return err
// }
// inbounds[inbound_index] = modifiedInbound
// }
// err = s.Save(singboxConfig, true)
// if err != nil {
// return err
// }
return nil
}
func (s *ConfigService) contains(slice []string, item string) bool {
for _, str := range slice {
if str == item {
return true
}
}
return false
}
func (s *ConfigService) GetChanges(actor string, chngKey string, count string) []model.Changes {
c, _ := strconv.Atoi(count)
whereString := "`id`>0"
if len(actor) > 0 {
whereString += " and `actor`='" + actor + "'"
}
if len(chngKey) > 0 {
whereString += " and `key`='" + chngKey + "'"
}
db := database.GetDB()
var chngs []model.Changes
err := db.Model(model.Changes{}).Where(whereString).Order("`id` desc").Limit(c).Scan(&chngs).Error
if err != nil {
logger.Warning(err)
}
return chngs
}