diff options
Diffstat (limited to 'web/service')
| -rw-r--r-- | web/service/inbound.go | 225 | ||||
| -rw-r--r-- | web/service/server.go | 15 | ||||
| -rw-r--r-- | web/service/setting.go | 85 | ||||
| -rw-r--r-- | web/service/tgbot.go | 7 | ||||
| -rw-r--r-- | web/service/xray.go | 26 | ||||
| -rw-r--r-- | web/service/xray_setting.go | 28 |
6 files changed, 351 insertions, 35 deletions
diff --git a/web/service/inbound.go b/web/service/inbound.go index 1646b5ed..01d4eb57 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -168,9 +168,13 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo err = tx.Save(inbound).Error if err == nil { - for _, client := range clients { - s.AddClientStat(tx, inbound.Id, &client) + if len(inbound.ClientStats) == 0 { + for _, client := range clients { + s.AddClientStat(tx, inbound.Id, &client) + } } + } else { + return inbound, false, err } needRestart := false @@ -263,7 +267,18 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, tag := oldInbound.Tag - err = s.updateClientTraffics(oldInbound, inbound) + db := database.GetDB() + tx := db.Begin() + + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + + err = s.updateClientTraffics(tx, oldInbound, inbound) if err != nil { return inbound, false, err } @@ -304,11 +319,10 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, } s.xrayApi.Close() - db := database.GetDB() - return inbound, needRestart, db.Save(oldInbound).Error + return inbound, needRestart, tx.Save(oldInbound).Error } -func (s *InboundService) updateClientTraffics(oldInbound *model.Inbound, newInbound *model.Inbound) error { +func (s *InboundService) updateClientTraffics(tx *gorm.DB, oldInbound *model.Inbound, newInbound *model.Inbound) error { oldClients, err := s.GetClients(oldInbound) if err != nil { return err @@ -318,17 +332,6 @@ func (s *InboundService) updateClientTraffics(oldInbound *model.Inbound, newInbo return err } - db := database.GetDB() - tx := db.Begin() - - defer func() { - if err != nil { - tx.Rollback() - } else { - tx.Commit() - } - }() - var emailExists bool for _, oldClient := range oldClients { @@ -601,7 +604,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin if len(clients[0].Email) > 0 { if len(oldEmail) > 0 { - err = s.UpdateClientStat(oldEmail, &clients[0]) + err = s.UpdateClientStat(tx, oldEmail, &clients[0]) if err != nil { return false, err } @@ -676,6 +679,13 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff return err, false } + needRestart0, count, err := s.autoRenewClients(tx) + if err != nil { + logger.Warning("Error in renew clients:", err) + } else if count > 0 { + logger.Debugf("%v clients renewed", count) + } + needRestart1, count, err := s.disableInvalidClients(tx) if err != nil { logger.Warning("Error in disabling invalid clients:", err) @@ -689,7 +699,7 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff } else if count > 0 { logger.Debugf("%v inbounds disabled", count) } - return nil, (needRestart1 || needRestart2) + return nil, (needRestart0 || needRestart1 || needRestart2) } func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error { @@ -716,9 +726,15 @@ func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTraffic) (err error) { if len(traffics) == 0 { + // Empty onlineUsers + if p != nil { + p.SetOnlineClients(nil) + } return nil } + var onlineClients []string + emails := make([]string, 0, len(traffics)) for _, traffic := range traffics { emails = append(emails, traffic.Email) @@ -744,11 +760,19 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr if dbClientTraffics[dbTraffic_index].Email == traffics[traffic_index].Email { dbClientTraffics[dbTraffic_index].Up += traffics[traffic_index].Up dbClientTraffics[dbTraffic_index].Down += traffics[traffic_index].Down + + // Add user in onlineUsers array on traffic + if traffics[traffic_index].Up+traffics[traffic_index].Down > 0 { + onlineClients = append(onlineClients, traffics[traffic_index].Email) + } break } } } + // Set onlineUsers + p.SetOnlineClients(onlineClients) + err = tx.Save(dbClientTraffics).Error if err != nil { logger.Warning("AddClientTraffic update data ", err) @@ -809,6 +833,102 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl return dbClientTraffics, nil } +func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) { + // check for time expired + var traffics []*xray.ClientTraffic + now := time.Now().Unix() * 1000 + var err, err1 error + + err = tx.Model(xray.ClientTraffic{}).Where("reset > 0 and expiry_time > 0 and expiry_time <= ?", now).Find(&traffics).Error + if err != nil { + return false, 0, err + } + // return if there is no client to renew + if len(traffics) == 0 { + return false, 0, nil + } + + var inbound_ids []int + var inbounds []*model.Inbound + needRestart := false + var clientsToAdd []struct { + protocol string + tag string + client map[string]interface{} + } + + for _, traffic := range traffics { + inbound_ids = append(inbound_ids, traffic.InboundId) + } + err = tx.Model(model.Inbound{}).Where("id IN ?", inbound_ids).Find(&inbounds).Error + if err != nil { + return false, 0, err + } + for inbound_index := range inbounds { + settings := map[string]interface{}{} + json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings) + clients := settings["clients"].([]interface{}) + for client_index := range clients { + c := clients[client_index].(map[string]interface{}) + for traffic_index, traffic := range traffics { + if traffic.Email == c["email"].(string) { + newExpiryTime := traffic.ExpiryTime + for newExpiryTime < now { + newExpiryTime += (int64(traffic.Reset) * 86400000) + } + c["expiryTime"] = newExpiryTime + traffics[traffic_index].ExpiryTime = newExpiryTime + traffics[traffic_index].Down = 0 + traffics[traffic_index].Up = 0 + if !traffic.Enable { + traffics[traffic_index].Enable = true + clientsToAdd = append(clientsToAdd, + struct { + protocol string + tag string + client map[string]interface{} + }{ + protocol: string(inbounds[inbound_index].Protocol), + tag: inbounds[inbound_index].Tag, + client: c, + }) + } + clients[client_index] = interface{}(c) + break + } + } + } + settings["clients"] = clients + newSettings, err := json.MarshalIndent(settings, "", " ") + if err != nil { + return false, 0, err + } + inbounds[inbound_index].Settings = string(newSettings) + } + err = tx.Save(inbounds).Error + if err != nil { + return false, 0, err + } + err = tx.Save(traffics).Error + if err != nil { + return false, 0, err + } + if p != nil { + err1 = s.xrayApi.Init(p.GetAPIPort()) + if err1 != nil { + return true, int64(len(traffics)), nil + } + for _, clientToAdd := range clientsToAdd { + err1 = s.xrayApi.AddUser(clientToAdd.protocol, clientToAdd.tag, clientToAdd.client) + if err1 != nil { + needRestart = true + } + } + s.xrayApi.Close() + } + return needRestart, int64(len(traffics)), nil +} + func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error) { now := time.Now().Unix() * 1000 needRestart := false @@ -881,6 +1001,17 @@ func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error) return needRestart, count, err } +func (s *InboundService) GetInboundTags() (string, error) { + db := database.GetDB() + var inboundTags []string + err := db.Model(model.Inbound{}).Select("tag").Find(&inboundTags).Error + if err != nil && err != gorm.ErrRecordNotFound { + return "", err + } + tags, _ := json.Marshal(inboundTags) + return string(tags), nil +} + func (s *InboundService) MigrationRemoveOrphanedTraffics() { db := database.GetDB() db.Exec(` @@ -902,6 +1033,7 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model clientTraffic.Enable = true clientTraffic.Up = 0 clientTraffic.Down = 0 + clientTraffic.Reset = client.Reset result := tx.Create(&clientTraffic) err := result.Error if err != nil { @@ -910,16 +1042,15 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model return nil } -func (s *InboundService) UpdateClientStat(email string, client *model.Client) error { - db := database.GetDB() - - result := db.Model(xray.ClientTraffic{}). +func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *model.Client) error { + result := tx.Model(xray.ClientTraffic{}). Where("email = ?", email). Updates(map[string]interface{}{ "enable": true, "email": client.Email, "total": client.TotalGB, - "expiry_time": client.ExpiryTime}) + "expiry_time": client.ExpiryTime, + "reset": client.Reset}) err := result.Error if err != nil { return err @@ -1415,7 +1546,7 @@ func (s *InboundService) DelDepletedClients(id int) (err error) { } }() - whereText := "inbound_id " + whereText := "reset = 0 and inbound_id " if id < 0 { whereText += "> ?" } else { @@ -1669,9 +1800,53 @@ func (s *InboundService) MigrationRequirements() { // Remove orphaned traffics tx.Where("inbound_id = 0").Delete(xray.ClientTraffic{}) + + // Migrate old MultiDomain to External Proxy + var externalProxy []struct { + Id int + Port int + StreamSettings []byte + } + err = tx.Raw(`select id, port, stream_settings + from inbounds + WHERE protocol in ('vmess','vless','trojan') + AND json_extract(stream_settings, '$.security') = 'tls' + AND json_extract(stream_settings, '$.tlsSettings.settings.domains') IS NOT NULL`).Scan(&externalProxy).Error + if err != nil || len(externalProxy) == 0 { + return + } + + for _, ep := range externalProxy { + var reverses interface{} + var stream map[string]interface{} + json.Unmarshal(ep.StreamSettings, &stream) + if tlsSettings, ok := stream["tlsSettings"].(map[string]interface{}); ok { + if settings, ok := tlsSettings["settings"].(map[string]interface{}); ok { + if domains, ok := settings["domains"].([]interface{}); ok { + for _, domain := range domains { + if domainMap, ok := domain.(map[string]interface{}); ok { + domainMap["forceTls"] = "same" + domainMap["port"] = ep.Port + domainMap["dest"] = domainMap["domain"].(string) + delete(domainMap, "domain") + } + } + } + reverses = settings["domains"] + delete(settings, "domains") + } + } + stream["externalProxy"] = reverses + newStream, _ := json.MarshalIndent(stream, " ", " ") + tx.Model(model.Inbound{}).Where("id = ?", ep.Id).Update("stream_settings", newStream) + } } func (s *InboundService) MigrateDB() { s.MigrationRequirements() s.MigrationRemoveOrphanedTraffics() } + +func (s *InboundService) GetOnlineClinets() []string { + return p.GetOnlineClients() +} diff --git a/web/service/server.go b/web/service/server.go index d88fa098..822918a8 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -230,7 +230,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status { status.AppStats.Mem = rtm.Sys status.AppStats.Threads = uint32(runtime.NumGoroutine()) - if p.IsRunning() { + if p != nil && p.IsRunning() { status.AppStats.Uptime = p.GetUptime() } else { status.AppStats.Uptime = 0 @@ -380,14 +380,6 @@ func (s *ServerService) UpdateXray(version string) error { if err != nil { return err } - err = copyZipFile("geosite.dat", xray.GetGeositePath()) - if err != nil { - return err - } - err = copyZipFile("geoip.dat", xray.GetGeoipPath()) - if err != nil { - return err - } return nil @@ -435,6 +427,11 @@ func (s *ServerService) GetConfigJson() (interface{}, error) { } func (s *ServerService) GetDb() ([]byte, error) { + // Update by manually trigger a checkpoint operation + err := database.Checkpoint() + if err != nil { + return nil, err + } // Open the file for reading file, err := os.Open(config.GetDBPath()) if err != nil { diff --git a/web/service/setting.go b/web/service/setting.go index b1565e1f..8b60d166 100644 --- a/web/service/setting.go +++ b/web/service/setting.go @@ -31,8 +31,10 @@ var defaultValueMap = map[string]string{ "secret": random.Seq(32), "webBasePath": "/", "sessionMaxAge": "0", + "pageSize": "0", "expireDiff": "0", "trafficDiff": "0", + "remarkModel": "-ieo", "timeLocation": "Asia/Tehran", "tgBotEnable": "false", "tgBotToken": "", @@ -53,6 +55,7 @@ var defaultValueMap = map[string]string{ "subUpdates": "12", "subEncrypt": "true", "subShowInfo": "true", + "subURI": "", } type SettingService struct { @@ -70,7 +73,7 @@ func (s *SettingService) GetDefaultJsonConfig() (interface{}, error) { func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) { db := database.GetDB() settings := make([]*model.Setting, 0) - err := db.Model(model.Setting{}).Find(&settings).Error + err := db.Model(model.Setting{}).Not("key = ?", "xrayTemplateConfig").Find(&settings).Error if err != nil { return nil, err } @@ -309,6 +312,10 @@ func (s *SettingService) GetSessionMaxAge() (int, error) { return s.getInt("sessionMaxAge") } +func (s *SettingService) GetRemarkModel() (string, error) { + return s.getString("remarkModel") +} + func (s *SettingService) GetSecretStatus() (bool, error) { return s.getBool("secretEnable") } @@ -406,6 +413,14 @@ func (s *SettingService) GetSubShowInfo() (bool, error) { return s.getBool("subShowInfo") } +func (s *SettingService) GetSubURI() (string, error) { + return s.getString("subURI") +} + +func (s *SettingService) GetPageSize() (int, error) { + return s.getInt("pageSize") +} + func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error { if err := allSetting.CheckValid(); err != nil { return err @@ -426,3 +441,71 @@ func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error { } return common.Combine(errs...) } + +func (s *SettingService) GetDefaultXrayConfig() (interface{}, error) { + var jsonData interface{} + err := json.Unmarshal([]byte(xrayTemplateConfig), &jsonData) + if err != nil { + return nil, err + } + return jsonData, nil +} + +func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) { + type settingFunc func() (interface{}, error) + settings := map[string]settingFunc{ + "expireDiff": func() (interface{}, error) { return s.GetExpireDiff() }, + "trafficDiff": func() (interface{}, error) { return s.GetTrafficDiff() }, + "pageSize": func() (interface{}, error) { return s.GetPageSize() }, + "defaultCert": func() (interface{}, error) { return s.GetCertFile() }, + "defaultKey": func() (interface{}, error) { return s.GetKeyFile() }, + "tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() }, + "subEnable": func() (interface{}, error) { return s.GetSubEnable() }, + "subURI": func() (interface{}, error) { return s.GetSubURI() }, + "remarkModel": func() (interface{}, error) { return s.GetRemarkModel() }, + } + + result := make(map[string]interface{}) + + for key, fn := range settings { + value, err := fn() + if err != nil { + return "", err + } + result[key] = value + } + + if result["subEnable"].(bool) && result["subURI"].(string) == "" { + subURI := "" + subPort, _ := s.GetSubPort() + subPath, _ := s.GetSubPath() + subDomain, _ := s.GetSubDomain() + subKeyFile, _ := s.GetSubKeyFile() + subCertFile, _ := s.GetSubCertFile() + subTLS := false + if subKeyFile != "" && subCertFile != "" { + subTLS = true + } + if subDomain == "" { + subDomain = strings.Split(host, ":")[0] + } + if subTLS { + subURI = "https://" + } else { + subURI = "http://" + } + if (subPort == 443 && subTLS) || (subPort == 80 && !subTLS) { + subURI += subDomain + } else { + subURI += fmt.Sprintf("%s:%d", subDomain, subPort) + } + if subPath[0] == byte('/') { + subURI += subPath + } else { + subURI += "/" + subPath + } + result["subURI"] = subURI + } + + return result, nil +} diff --git a/web/service/tgbot.go b/web/service/tgbot.go index b54ceb1c..0847e418 100644 --- a/web/service/tgbot.go +++ b/web/service/tgbot.go @@ -9,6 +9,7 @@ import ( "strings" "time" "x-ui/config" + "x-ui/database" "x-ui/database/model" "x-ui/logger" "x-ui/util/common" @@ -1417,6 +1418,12 @@ func (t *Tgbot) sendBackup(chatId int64) { output := t.I18nBot("tgbot.messages.backupTime", "Time=="+time.Now().Format("2006-01-02 15:04:05")) t.SendMsgToTgbot(chatId, output) + // Update by manually trigger a checkpoint operation + err := database.Checkpoint() + if err != nil { + logger.Warning("Error in trigger a checkpoint operation: ", err) + } + file, err := os.Open(config.GetDBPath()) if err != nil { logger.Warning("Error in opening db file for backup: ", err) diff --git a/web/service/xray.go b/web/service/xray.go index 2dbc092d..7233cec5 100644 --- a/web/service/xray.go +++ b/web/service/xray.go @@ -134,6 +134,32 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) { inbound.Settings = string(modifiedSettings) } + + if len(inbound.StreamSettings) > 0 { + // Unmarshal stream JSON + var stream map[string]interface{} + json.Unmarshal([]byte(inbound.StreamSettings), &stream) + + // Remove the "settings" field under "tlsSettings" and "realitySettings" + tlsSettings, ok1 := stream["tlsSettings"].(map[string]interface{}) + realitySettings, ok2 := stream["realitySettings"].(map[string]interface{}) + if ok1 || ok2 { + if ok1 { + delete(tlsSettings, "settings") + } else if ok2 { + delete(realitySettings, "settings") + } + } + + delete(stream, "externalProxy") + + newStream, err := json.MarshalIndent(stream, "", " ") + if err != nil { + return nil, err + } + inbound.StreamSettings = string(newStream) + } + inboundConfig := inbound.GenXrayInboundConfig() xrayConfig.InboundConfigs = append(xrayConfig.InboundConfigs, *inboundConfig) } diff --git a/web/service/xray_setting.go b/web/service/xray_setting.go new file mode 100644 index 00000000..4550bde2 --- /dev/null +++ b/web/service/xray_setting.go @@ -0,0 +1,28 @@ +package service + +import ( + _ "embed" + "encoding/json" + "x-ui/util/common" + "x-ui/xray" +) + +type XraySettingService struct { + SettingService +} + +func (s *XraySettingService) SaveXraySetting(newXraySettings string) error { + if err := s.CheckXrayConfig(newXraySettings); err != nil { + return err + } + return s.SettingService.saveSetting("xrayTemplateConfig", newXraySettings) +} + +func (s *XraySettingService) CheckXrayConfig(XrayTemplateConfig string) error { + xrayConfig := &xray.Config{} + err := json.Unmarshal([]byte(XrayTemplateConfig), xrayConfig) + if err != nil { + return common.NewError("xray template config invalid:", err) + } + return nil +} |
