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 InDataService SettingService } 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"` 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() configPath := config.GetBinFolderPath() data, err := os.ReadFile(configPath + "/config.json") if err != nil { if os.IsNotExist(err) { defaultConfig := []byte(config.GetDefaultConfig()) err = os.MkdirAll(configPath, 01764) if err != nil { return err } err = os.WriteFile(configPath+"/config.json", defaultConfig, 0764) if err != nil { return err } data = defaultConfig } else { return err } } var singboxConfig SingBoxConfig err = json.Unmarshal(data, &singboxConfig) if err != nil { return err } return nil } func (s *ConfigService) GetConfig() (*SingBoxConfig, error) { configPath := config.GetBinFolderPath() data, err := os.ReadFile(configPath + "/config.json") if err != nil { return nil, err } singboxConfig := SingBoxConfig{} err = json.Unmarshal(data, &singboxConfig) if err != nil { return nil, err } return &singboxConfig, 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 = corePtr.Restart() if err != nil { return err } } // s.Controller.Restart() return nil } func (s *ConfigService) DepleteClients() error { users, inbounds, err := s.ClientService.DepleteClients() if err != nil || len(users) == 0 || len(inbounds) == 0 { return err } singboxConfig, err := s.GetConfig() if err != nil { return err } for inbound_index, inbound := range singboxConfig.Inbounds { var inboundJson map[string]interface{} json.Unmarshal(inbound, &inboundJson) if s.contains(inbounds, inboundJson["tag"].(string)) { 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 } singboxConfig.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 } func (s *ConfigService) RestartCore() error { return corePtr.Restart() }