package service import ( "encoding/json" "strconv" "time" "github.com/alireza0/s-ui/core" "github.com/alireza0/s-ui/database" "github.com/alireza0/s-ui/database/model" "github.com/alireza0/s-ui/logger" "github.com/alireza0/s-ui/util/common" ) var ( LastUpdate int64 corePtr *core.Core ) type ConfigService struct { ClientService TlsService SettingService InboundService OutboundService ServicesService 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"` Services []json.RawMessage `json:"services"` 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) GetConfig(data string) (*SingBoxConfig, error) { var err error if len(data) == 0 { 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.Services, err = s.ServicesService.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(defaultConfig string) error { if corePtr.IsRunning() { return nil } singboxConfig, err := s.GetConfig(defaultConfig) 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.StopCore() if err != nil { return err } return s.StartCore("") } func (s *ConfigService) restartCoreWithConfig(config json.RawMessage) error { err := s.StopCore() if err != nil { return err } return s.StartCore(string(config)) } func (s *ConfigService) StopCore() error { err := corePtr.Stop() if err != nil { return err } logger.Info("sing-box stopped") return nil } func (s *ConfigService) Save(obj string, act string, data json.RawMessage, initUsers string, loginUser string, hostname string) ([]string, error) { var err error var objs []string = []string{obj} db := database.GetDB() tx := db.Begin() defer func() { if err == nil { tx.Commit() // Try to start core if it is not running if !corePtr.IsRunning() { s.StartCore("") } } else { tx.Rollback() } }() switch obj { case "clients": var inboundIds []uint inboundIds, err = s.ClientService.Save(tx, act, data, hostname) if err == nil && len(inboundIds) > 0 { objs = append(objs, "inbounds") err = s.InboundService.RestartInbounds(tx, inboundIds) if err != nil { return nil, common.NewErrorf("failed to update users for inbounds: %v", err) } } case "tls": err = s.TlsService.Save(tx, act, data, hostname) objs = append(objs, "clients", "inbounds") case "inbounds": err = s.InboundService.Save(tx, act, data, initUsers, hostname) objs = append(objs, "clients") case "outbounds": err = s.OutboundService.Save(tx, act, data) case "services": err = s.ServicesService.Save(tx, act, data) case "endpoints": err = s.EndpointService.Save(tx, act, data) case "config": err = s.SettingService.SaveConfig(tx, data) if err != nil { return nil, err } configData := make(json.RawMessage, len(data)) copy(configData, data) go func() { _ = s.restartCoreWithConfig(configData) }() case "settings": err = s.SettingService.Save(tx, data) default: return nil, common.NewError("unknown object: ", obj) } if err != nil { return nil, err } dt := time.Now().Unix() err = tx.Create(&model.Changes{ DateTime: dt, Actor: loginUser, Key: obj, Action: act, Obj: data, }).Error if err != nil { return nil, err } LastUpdate = time.Now().Unix() return objs, 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) 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 }