mirror of
https://github.com/alireza0/x-ui.git
synced 2026-08-28 04:15:05 +00:00
migration manager
This commit is contained in:
parent
150f875b77
commit
e09cdc97a9
15 changed files with 673 additions and 492 deletions
|
|
@ -15,6 +15,9 @@ var version string
|
|||
//go:embed name
|
||||
var name string
|
||||
|
||||
//go:embed default_xray.json
|
||||
var defaultXrayTemplate string
|
||||
|
||||
type LogLevel string
|
||||
|
||||
const (
|
||||
|
|
@ -86,3 +89,7 @@ func GetDBFolderPath() string {
|
|||
func GetDBPath() string {
|
||||
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
|
||||
}
|
||||
|
||||
func GetDefaultXrayTemplate() string {
|
||||
return defaultXrayTemplate
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,4 +39,4 @@
|
|||
"rules": []
|
||||
},
|
||||
"stats": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"path"
|
||||
|
||||
"github.com/alireza0/x-ui/config"
|
||||
"github.com/alireza0/x-ui/database/migrations"
|
||||
"github.com/alireza0/x-ui/database/model"
|
||||
"github.com/alireza0/x-ui/util/common"
|
||||
"github.com/alireza0/x-ui/xray"
|
||||
|
|
@ -77,7 +78,7 @@ func InitDB(dbPath string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return migrations.Run(db)
|
||||
}
|
||||
|
||||
func CloseDB() error {
|
||||
|
|
|
|||
5
database/migrations/helpers.go
Normal file
5
database/migrations/helpers.go
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package migrations
|
||||
|
||||
func removeIndex(s []interface{}, index int) []interface{} {
|
||||
return append(s[:index], s[index+1:]...)
|
||||
}
|
||||
60
database/migrations/migrations.go
Normal file
60
database/migrations/migrations.go
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
VersionOutbound = 2
|
||||
VersionRouting = 3
|
||||
VersionInbound = 4
|
||||
)
|
||||
|
||||
type migration struct {
|
||||
version int
|
||||
fn func(*gorm.DB) error
|
||||
}
|
||||
|
||||
var registry = []migration{
|
||||
{VersionOutbound, migrateV002Outbound},
|
||||
{VersionRouting, migrateV003Routing},
|
||||
{VersionInbound, migrateV004Inbound},
|
||||
}
|
||||
|
||||
func Run(db *gorm.DB) error {
|
||||
if err := ensureSchemaVersionTable(db); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, m := range registry {
|
||||
applied, err := isVersionApplied(db, m.version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if applied {
|
||||
continue
|
||||
}
|
||||
if err := m.fn(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := recordVersion(db, m.version); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureSchemaVersionTable(db *gorm.DB) error {
|
||||
return db.Exec(`CREATE TABLE IF NOT EXISTS schema_version (
|
||||
version INTEGER PRIMARY KEY
|
||||
)`).Error
|
||||
}
|
||||
|
||||
func isVersionApplied(db *gorm.DB, version int) (bool, error) {
|
||||
var count int64
|
||||
err := db.Raw(`SELECT COUNT(*) FROM schema_version WHERE version = ?`, version).Scan(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
func recordVersion(db *gorm.DB, version int) error {
|
||||
return db.Exec(`INSERT INTO schema_version (version) VALUES (?)`, version).Error
|
||||
}
|
||||
38
database/migrations/settings.go
Normal file
38
database/migrations/settings.go
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/alireza0/x-ui/config"
|
||||
"github.com/alireza0/x-ui/database/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const xrayTemplateConfigKey = "xrayTemplateConfig"
|
||||
|
||||
func getXrayTemplate(db *gorm.DB) (string, error) {
|
||||
setting := &model.Setting{}
|
||||
err := db.Model(model.Setting{}).Where("key = ?", xrayTemplateConfigKey).First(setting).Error
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return config.GetDefaultXrayTemplate(), nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return setting.Value, nil
|
||||
}
|
||||
|
||||
func saveXrayTemplate(db *gorm.DB, value string) error {
|
||||
setting := &model.Setting{}
|
||||
err := db.Model(model.Setting{}).Where("key = ?", xrayTemplateConfigKey).First(setting).Error
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return db.Create(&model.Setting{
|
||||
Key: xrayTemplateConfigKey,
|
||||
Value: value,
|
||||
}).Error
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
setting.Value = value
|
||||
return db.Save(setting).Error
|
||||
}
|
||||
143
database/migrations/v002_outbound.go
Normal file
143
database/migrations/v002_outbound.go
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/alireza0/x-ui/database/model"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func migrateV002Outbound(db *gorm.DB) error {
|
||||
var count int64
|
||||
if err := db.Model(&model.Outbound{}).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := migrateOutboundsFromTemplate(db); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := db.Model(&model.Outbound{}).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
initDefaultOutbounds(db)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrateOutboundsFromTemplate(db *gorm.DB) error {
|
||||
templateConfig, err := getXrayTemplate(db)
|
||||
if err != nil {
|
||||
logger.Warning("outbound migration: get xray template failed:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
var cfg map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(templateConfig), &cfg); err != nil {
|
||||
logger.Warning("outbound migration: parse template failed:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
rawOutbounds, ok := cfg["outbounds"].([]interface{})
|
||||
if !ok || len(rawOutbounds) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
migrated := 0
|
||||
for i, item := range rawOutbounds {
|
||||
raw, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
outbound := outboundFromMap(raw, i)
|
||||
if outbound.Tag == "" || outbound.Protocol == "" {
|
||||
continue
|
||||
}
|
||||
if err := db.Create(outbound).Error; err != nil {
|
||||
logger.Warning("outbound migration: create failed:", err)
|
||||
continue
|
||||
}
|
||||
migrated++
|
||||
}
|
||||
if migrated == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg["outbounds"] = []interface{}{}
|
||||
newTemplate, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
logger.Warning("outbound migration: marshal template failed:", err)
|
||||
return nil
|
||||
}
|
||||
if err := saveXrayTemplate(db, string(newTemplate)); err != nil {
|
||||
logger.Warning("outbound migration: save template failed:", err)
|
||||
return nil
|
||||
}
|
||||
logger.Info("Migrated", migrated, "outbound(s) from xray settings to database")
|
||||
return nil
|
||||
}
|
||||
|
||||
func initDefaultOutbounds(db *gorm.DB) {
|
||||
for _, outbound := range defaultOutbounds() {
|
||||
if err := db.Create(outbound).Error; err != nil {
|
||||
logger.Warning("outbound init: create default failed:", err)
|
||||
}
|
||||
}
|
||||
logger.Info("Initialized default outbound(s): direct, blocked")
|
||||
}
|
||||
|
||||
func outboundFromMap(raw map[string]interface{}, sort int) *model.Outbound {
|
||||
o := &model.Outbound{Sort: sort}
|
||||
if v, ok := raw["sendThrough"].(string); ok {
|
||||
o.SendThrough = v
|
||||
}
|
||||
if v, ok := raw["protocol"].(string); ok {
|
||||
o.Protocol = v
|
||||
}
|
||||
if v, ok := raw["tag"].(string); ok {
|
||||
o.Tag = v
|
||||
}
|
||||
if v, ok := raw["targetStrategy"].(string); ok {
|
||||
o.TargetStrategy = v
|
||||
}
|
||||
if v, ok := raw["settings"]; ok && v != nil {
|
||||
b, _ := json.MarshalIndent(v, "", " ")
|
||||
o.Settings = string(b)
|
||||
}
|
||||
if v, ok := raw["streamSettings"]; ok && v != nil {
|
||||
b, _ := json.MarshalIndent(v, "", " ")
|
||||
o.StreamSettings = string(b)
|
||||
}
|
||||
if v, ok := raw["proxySettings"]; ok && v != nil {
|
||||
b, _ := json.MarshalIndent(v, "", " ")
|
||||
o.ProxySettings = string(b)
|
||||
}
|
||||
if v, ok := raw["mux"]; ok && v != nil {
|
||||
b, _ := json.MarshalIndent(v, "", " ")
|
||||
o.Mux = string(b)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func defaultOutbounds() []*model.Outbound {
|
||||
return []*model.Outbound{
|
||||
{
|
||||
Sort: 0,
|
||||
Protocol: "freedom",
|
||||
Tag: "direct",
|
||||
Settings: `{"domainStrategy":"UseIP","noises":[],"redirect":""}`,
|
||||
},
|
||||
{
|
||||
Sort: 1,
|
||||
Protocol: "blackhole",
|
||||
Tag: "blocked",
|
||||
Settings: `{}`,
|
||||
},
|
||||
}
|
||||
}
|
||||
224
database/migrations/v003_routing.go
Normal file
224
database/migrations/v003_routing.go
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/alireza0/x-ui/config"
|
||||
"github.com/alireza0/x-ui/database/model"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func migrateV003Routing(db *gorm.DB) error {
|
||||
templateConfig, err := getXrayTemplate(db)
|
||||
if err != nil {
|
||||
logger.Warning("routing rule migration: get xray template failed:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
var cfg map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(templateConfig), &cfg); err != nil {
|
||||
logger.Warning("routing rule migration: parse template failed:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
changed := migrateLegacyApiConfig(cfg)
|
||||
if changed {
|
||||
newTemplate, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
logger.Warning("routing rule migration: marshal legacy api config failed:", err)
|
||||
return nil
|
||||
}
|
||||
if err := saveXrayTemplate(db, string(newTemplate)); err != nil {
|
||||
logger.Warning("routing rule migration: save legacy api config failed:", err)
|
||||
return nil
|
||||
}
|
||||
logger.Info("Migrated legacy api inbound to api.listen in xray settings")
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := db.Model(&model.RoutingRule{}).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
routing, ok := cfg["routing"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
rawRules, ok := routing["rules"].([]interface{})
|
||||
if !ok || len(rawRules) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, item := range rawRules {
|
||||
raw, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
rule := routingRuleFromMap(raw, i)
|
||||
if err := db.Create(rule).Error; err != nil {
|
||||
logger.Warning("routing rule migration: create failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
routing["rules"] = []interface{}{}
|
||||
cfg["routing"] = routing
|
||||
newTemplate, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
logger.Warning("routing rule migration: marshal template failed:", err)
|
||||
return nil
|
||||
}
|
||||
if err := saveXrayTemplate(db, string(newTemplate)); err != nil {
|
||||
logger.Warning("routing rule migration: save template failed:", err)
|
||||
return nil
|
||||
}
|
||||
logger.Info("Migrated", len(rawRules), "routing rule(s) from xray settings to database")
|
||||
return nil
|
||||
}
|
||||
|
||||
func apiListenMissing(api map[string]interface{}) bool {
|
||||
_, ok := api["listen"]
|
||||
return !ok
|
||||
}
|
||||
|
||||
func defaultApiListen() string {
|
||||
var cfg map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(config.GetDefaultXrayTemplate()), &cfg); err != nil {
|
||||
return "127.0.0.1:62789"
|
||||
}
|
||||
api, ok := cfg["api"].(map[string]interface{})
|
||||
if !ok {
|
||||
return "127.0.0.1:62789"
|
||||
}
|
||||
s, _ := api["listen"].(string)
|
||||
if s == "" {
|
||||
return "127.0.0.1:62789"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func inboundListenAddress(inbound map[string]interface{}) string {
|
||||
port := 0
|
||||
switch p := inbound["port"].(type) {
|
||||
case float64:
|
||||
port = int(p)
|
||||
case int:
|
||||
port = p
|
||||
case int64:
|
||||
port = int(p)
|
||||
}
|
||||
if port == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
host := "127.0.0.1"
|
||||
if listen, ok := inbound["listen"]; ok {
|
||||
switch v := listen.(type) {
|
||||
case string:
|
||||
if v != "" {
|
||||
host = v
|
||||
}
|
||||
case []interface{}:
|
||||
if len(v) > 0 {
|
||||
if s, ok := v[0].(string); ok && s != "" {
|
||||
host = s
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", host, port)
|
||||
}
|
||||
|
||||
func isLegacyApiRoutingRule(rule map[string]interface{}) bool {
|
||||
outboundTag, _ := rule["outboundTag"].(string)
|
||||
if outboundTag != "api" {
|
||||
return false
|
||||
}
|
||||
val, ok := rule["inboundTag"]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
switch v := val.(type) {
|
||||
case []interface{}:
|
||||
return len(v) == 1 && v[0] == "api"
|
||||
case []string:
|
||||
return len(v) == 1 && v[0] == "api"
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func migrateLegacyApiConfig(cfg map[string]interface{}) bool {
|
||||
api, ok := cfg["api"].(map[string]interface{})
|
||||
if !ok || !apiListenMissing(api) {
|
||||
return false
|
||||
}
|
||||
|
||||
var listenAddr string
|
||||
inbounds, _ := cfg["inbounds"].([]interface{})
|
||||
|
||||
apiInboundIndex := -1
|
||||
var apiInbound map[string]interface{}
|
||||
for i, item := range inbounds {
|
||||
inbound, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
tag, _ := inbound["tag"].(string)
|
||||
protocol, _ := inbound["protocol"].(string)
|
||||
if tag == "api" && protocol == "dokodemo-door" {
|
||||
apiInboundIndex = i
|
||||
apiInbound = inbound
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if apiInboundIndex >= 0 {
|
||||
listenAddr = inboundListenAddress(apiInbound)
|
||||
if listenAddr == "" {
|
||||
listenAddr = defaultApiListen()
|
||||
}
|
||||
cfg["inbounds"] = removeIndex(inbounds, apiInboundIndex)
|
||||
|
||||
if routing, ok := cfg["routing"].(map[string]interface{}); ok {
|
||||
if rawRules, ok := routing["rules"].([]interface{}); ok {
|
||||
for i, item := range rawRules {
|
||||
rule, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if isLegacyApiRoutingRule(rule) {
|
||||
routing["rules"] = removeIndex(rawRules, i)
|
||||
cfg["routing"] = routing
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
listenAddr = defaultApiListen()
|
||||
}
|
||||
|
||||
api["listen"] = listenAddr
|
||||
cfg["api"] = api
|
||||
return true
|
||||
}
|
||||
|
||||
func routingRuleFromMap(raw map[string]interface{}, index int) *model.RoutingRule {
|
||||
tag, _ := raw["ruleTag"].(string)
|
||||
delete(raw, "ruleTag")
|
||||
if tag == "" {
|
||||
tag = fmt.Sprintf("migrated-rule-%d", index)
|
||||
}
|
||||
b, _ := json.Marshal(raw)
|
||||
return &model.RoutingRule{
|
||||
Tag: tag,
|
||||
Sort: index,
|
||||
RawJson: string(b),
|
||||
}
|
||||
}
|
||||
190
database/migrations/v004_inbound.go
Normal file
190
database/migrations/v004_inbound.go
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/alireza0/x-ui/database/model"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
"github.com/alireza0/x-ui/xray"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func migrateV004Inbound(db *gorm.DB) error {
|
||||
if err := migrationInboundRequirements(db); err != nil {
|
||||
return err
|
||||
}
|
||||
migrationRemoveOrphanedTraffics(db)
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrationInboundRequirements(db *gorm.DB) error {
|
||||
tx := db.Begin()
|
||||
var err error
|
||||
defer func() {
|
||||
if err == nil {
|
||||
tx.Commit()
|
||||
if dbErr := db.Exec(`VACUUM "main"`).Error; dbErr != nil {
|
||||
logger.Warningf("VACUUM failed: %v", dbErr)
|
||||
}
|
||||
} else {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
var inbounds []*model.Inbound
|
||||
err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return err
|
||||
}
|
||||
for inboundIndex := range inbounds {
|
||||
settings := map[string]interface{}{}
|
||||
json.Unmarshal([]byte(inbounds[inboundIndex].Settings), &settings)
|
||||
clients, ok := settings["clients"].([]interface{})
|
||||
if ok {
|
||||
var newClients []interface{}
|
||||
for clientIndex := range clients {
|
||||
c := clients[clientIndex].(map[string]interface{})
|
||||
|
||||
if _, ok := c["email"]; !ok {
|
||||
c["email"] = ""
|
||||
}
|
||||
|
||||
if _, ok := c["flow"]; ok {
|
||||
if c["flow"] == "xtls-rprx-direct" {
|
||||
c["flow"] = ""
|
||||
}
|
||||
}
|
||||
newClients = append(newClients, interface{}(c))
|
||||
}
|
||||
settings["clients"] = newClients
|
||||
modifiedSettings, marshalErr := json.MarshalIndent(settings, "", " ")
|
||||
if marshalErr != nil {
|
||||
err = marshalErr
|
||||
return err
|
||||
}
|
||||
|
||||
inbounds[inboundIndex].Settings = string(modifiedSettings)
|
||||
}
|
||||
|
||||
modelClients := parseInboundClients(inbounds[inboundIndex].Settings)
|
||||
for _, modelClient := range modelClients {
|
||||
if len(modelClient.Email) > 0 {
|
||||
var count int64
|
||||
tx.Model(xray.ClientTraffic{}).Where("email = ?", modelClient.Email).Count(&count)
|
||||
if count == 0 {
|
||||
if addErr := addClientStat(tx, inbounds[inboundIndex].Id, &modelClient); addErr != nil {
|
||||
err = addErr
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if saveErr := tx.Save(inbounds).Error; saveErr != nil {
|
||||
err = saveErr
|
||||
return err
|
||||
}
|
||||
|
||||
tx.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
|
||||
|
||||
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 nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrationRemoveOrphanedTraffics(db *gorm.DB) {
|
||||
db.Exec(`
|
||||
DELETE FROM client_traffics
|
||||
WHERE email NOT IN (
|
||||
SELECT JSON_EXTRACT(client.value, '$.email')
|
||||
FROM inbounds,
|
||||
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||
)
|
||||
`)
|
||||
}
|
||||
|
||||
func parseInboundClients(settingsJSON string) []model.Client {
|
||||
settings := map[string]interface{}{}
|
||||
if err := json.Unmarshal([]byte(settingsJSON), &settings); err != nil {
|
||||
return nil
|
||||
}
|
||||
raw, ok := settings["clients"]
|
||||
if !ok || raw == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v := raw.(type) {
|
||||
case []interface{}:
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var clients []model.Client
|
||||
if json.Unmarshal(b, &clients) != nil {
|
||||
return nil
|
||||
}
|
||||
return clients
|
||||
case string:
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
var clients []model.Client
|
||||
if json.Unmarshal([]byte(v), &clients) != nil {
|
||||
return nil
|
||||
}
|
||||
return clients
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func addClientStat(tx *gorm.DB, inboundId int, client *model.Client) error {
|
||||
clientTraffic := xray.ClientTraffic{
|
||||
InboundId: inboundId,
|
||||
Email: client.Email,
|
||||
Total: client.TotalGB,
|
||||
ExpiryTime: client.ExpiryTime,
|
||||
Enable: client.Enable,
|
||||
Up: 0,
|
||||
Down: 0,
|
||||
Reset: client.Reset,
|
||||
}
|
||||
return tx.Create(&clientTraffic).Error
|
||||
}
|
||||
11
main.go
11
main.go
|
|
@ -47,11 +47,6 @@ func runWebServer() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
outboundService := service.OutboundService{}
|
||||
outboundService.MigrateDB()
|
||||
routingRuleService := service.RoutingRuleService{}
|
||||
routingRuleService.MigrateDB()
|
||||
|
||||
var server *web.Server
|
||||
|
||||
server = web.NewServer()
|
||||
|
|
@ -368,16 +363,10 @@ func getPanelURI() {
|
|||
}
|
||||
|
||||
func migrateDb() {
|
||||
inboundService := service.InboundService{}
|
||||
outboundService := service.OutboundService{}
|
||||
|
||||
err := database.InitDB(config.GetDBPath())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("Start migrating database...")
|
||||
inboundService.MigrateDB()
|
||||
outboundService.MigrateDB()
|
||||
fmt.Println("Migration done!")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1143,18 +1143,6 @@ func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error)
|
|||
return needRestart, count, err
|
||||
}
|
||||
|
||||
func (s *InboundService) MigrationRemoveOrphanedTraffics() {
|
||||
db := database.GetDB()
|
||||
db.Exec(`
|
||||
DELETE FROM client_traffics
|
||||
WHERE email NOT IN (
|
||||
SELECT JSON_EXTRACT(client.value, '$.email')
|
||||
FROM inbounds,
|
||||
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||
)
|
||||
`)
|
||||
}
|
||||
|
||||
func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model.Client) error {
|
||||
clientTraffic := xray.ClientTraffic{}
|
||||
clientTraffic.InboundId = inboundId
|
||||
|
|
@ -1488,125 +1476,6 @@ func (s *InboundService) GetClientReverseTags() (string, error) {
|
|||
return string(result), nil
|
||||
}
|
||||
|
||||
func (s *InboundService) MigrationRequirements() {
|
||||
db := database.GetDB()
|
||||
tx := db.Begin()
|
||||
var err error
|
||||
defer func() {
|
||||
if err == nil {
|
||||
tx.Commit()
|
||||
if dbErr := db.Exec(`VACUUM "main"`).Error; dbErr != nil {
|
||||
logger.Warningf("VACUUM failed: %v", dbErr)
|
||||
}
|
||||
} else {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
// Fix inbounds based problems
|
||||
var inbounds []*model.Inbound
|
||||
err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan"}).Find(&inbounds).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return
|
||||
}
|
||||
for inbound_index := range inbounds {
|
||||
settings := map[string]interface{}{}
|
||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||
clients, ok := settings["clients"].([]interface{})
|
||||
if ok {
|
||||
// Fix Client configuration problems
|
||||
var newClients []interface{}
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]interface{})
|
||||
|
||||
// Add email='' if it is not exists
|
||||
if _, ok := c["email"]; !ok {
|
||||
c["email"] = ""
|
||||
}
|
||||
|
||||
// Remove "flow": "xtls-rprx-direct"
|
||||
if _, ok := c["flow"]; ok {
|
||||
if c["flow"] == "xtls-rprx-direct" {
|
||||
c["flow"] = ""
|
||||
}
|
||||
}
|
||||
newClients = append(newClients, interface{}(c))
|
||||
}
|
||||
settings["clients"] = newClients
|
||||
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
inbounds[inbound_index].Settings = string(modifiedSettings)
|
||||
}
|
||||
|
||||
// Add client traffic row for all clients which has email
|
||||
modelClients, err := s.GetClients(inbounds[inbound_index])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, modelClient := range modelClients {
|
||||
if len(modelClient.Email) > 0 {
|
||||
var count int64
|
||||
tx.Model(xray.ClientTraffic{}).Where("email = ?", modelClient.Email).Count(&count)
|
||||
if count == 0 {
|
||||
s.AddClientStat(tx, inbounds[inbound_index].Id, &modelClient)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tx.Save(inbounds)
|
||||
|
||||
// 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) GetOnlineClients() []string {
|
||||
return p.GetOnlineClients()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -294,131 +294,3 @@ func (s *OutboundService) GetOnlineOutbounds() []string {
|
|||
}
|
||||
return p.GetOnlineOutbounds()
|
||||
}
|
||||
|
||||
func outboundFromMap(raw map[string]interface{}, sort int) *model.Outbound {
|
||||
o := &model.Outbound{Sort: sort}
|
||||
if v, ok := raw["sendThrough"].(string); ok {
|
||||
o.SendThrough = v
|
||||
}
|
||||
if v, ok := raw["protocol"].(string); ok {
|
||||
o.Protocol = v
|
||||
}
|
||||
if v, ok := raw["tag"].(string); ok {
|
||||
o.Tag = v
|
||||
}
|
||||
if v, ok := raw["targetStrategy"].(string); ok {
|
||||
o.TargetStrategy = v
|
||||
}
|
||||
if v, ok := raw["settings"]; ok && v != nil {
|
||||
b, _ := json.Marshal(v)
|
||||
o.Settings = string(b)
|
||||
}
|
||||
if v, ok := raw["streamSettings"]; ok && v != nil {
|
||||
b, _ := json.Marshal(v)
|
||||
o.StreamSettings = string(b)
|
||||
}
|
||||
if v, ok := raw["proxySettings"]; ok && v != nil {
|
||||
b, _ := json.Marshal(v)
|
||||
o.ProxySettings = string(b)
|
||||
}
|
||||
if v, ok := raw["mux"]; ok && v != nil {
|
||||
b, _ := json.Marshal(v)
|
||||
o.Mux = string(b)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func defaultOutbounds() []*model.Outbound {
|
||||
return []*model.Outbound{
|
||||
{
|
||||
Sort: 0,
|
||||
Protocol: "freedom",
|
||||
Tag: "direct",
|
||||
Settings: `{"domainStrategy":"UseIP","noises":[],"redirect":""}`,
|
||||
},
|
||||
{
|
||||
Sort: 1,
|
||||
Protocol: "blackhole",
|
||||
Tag: "blocked",
|
||||
Settings: `{}`,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *OutboundService) initDefaultOutbounds() {
|
||||
db := database.GetDB()
|
||||
for _, outbound := range defaultOutbounds() {
|
||||
if err := db.Create(outbound).Error; err != nil {
|
||||
logger.Warning("outbound init: create default failed:", err)
|
||||
}
|
||||
}
|
||||
logger.Info("Initialized default outbound(s): direct, blocked")
|
||||
}
|
||||
|
||||
func (s *OutboundService) migrateOutboundsFromTemplate() {
|
||||
db := database.GetDB()
|
||||
templateConfig, err := s.settingService.GetXrayConfigTemplate()
|
||||
if err != nil {
|
||||
logger.Warning("outbound migration: get xray template failed:", err)
|
||||
return
|
||||
}
|
||||
|
||||
var cfg map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(templateConfig), &cfg); err != nil {
|
||||
logger.Warning("outbound migration: parse template failed:", err)
|
||||
return
|
||||
}
|
||||
|
||||
rawOutbounds, ok := cfg["outbounds"].([]interface{})
|
||||
if !ok || len(rawOutbounds) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
migrated := 0
|
||||
for i, item := range rawOutbounds {
|
||||
raw, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
outbound := outboundFromMap(raw, i)
|
||||
if outbound.Tag == "" || outbound.Protocol == "" {
|
||||
continue
|
||||
}
|
||||
if err := db.Create(outbound).Error; err != nil {
|
||||
logger.Warning("outbound migration: create failed:", err)
|
||||
continue
|
||||
}
|
||||
migrated++
|
||||
}
|
||||
if migrated == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
cfg["outbounds"] = []interface{}{}
|
||||
newTemplate, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
logger.Warning("outbound migration: marshal template failed:", err)
|
||||
return
|
||||
}
|
||||
if err := s.settingService.saveSetting("xrayTemplateConfig", string(newTemplate)); err != nil {
|
||||
logger.Warning("outbound migration: save template failed:", err)
|
||||
return
|
||||
}
|
||||
logger.Info("Migrated", migrated, "outbound(s) from xray settings to database")
|
||||
}
|
||||
|
||||
func (s *OutboundService) MigrateDB() {
|
||||
db := database.GetDB()
|
||||
var count int64
|
||||
db.Model(&model.Outbound{}).Count(&count)
|
||||
if count > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
s.migrateOutboundsFromTemplate()
|
||||
|
||||
db.Model(&model.Outbound{}).Count(&count)
|
||||
if count == 0 {
|
||||
s.initDefaultOutbounds()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -353,216 +353,6 @@ func (s *RoutingRuleService) ReplaceBalancerTag(oldTag, newTag string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func apiListenMissing(api map[string]interface{}) bool {
|
||||
_, ok := api["listen"]
|
||||
return !ok
|
||||
}
|
||||
|
||||
func defaultApiListen() string {
|
||||
var cfg map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(xrayTemplateConfig), &cfg); err != nil {
|
||||
return "127.0.0.1:62789"
|
||||
}
|
||||
api, ok := cfg["api"].(map[string]interface{})
|
||||
if !ok {
|
||||
return "127.0.0.1:62789"
|
||||
}
|
||||
s, _ := api["listen"].(string)
|
||||
if s == "" {
|
||||
return "127.0.0.1:62789"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func inboundListenAddress(inbound map[string]interface{}) string {
|
||||
port := 0
|
||||
switch p := inbound["port"].(type) {
|
||||
case float64:
|
||||
port = int(p)
|
||||
case int:
|
||||
port = p
|
||||
case int64:
|
||||
port = int(p)
|
||||
}
|
||||
if port == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
host := "127.0.0.1"
|
||||
if listen, ok := inbound["listen"]; ok {
|
||||
switch v := listen.(type) {
|
||||
case string:
|
||||
if v != "" {
|
||||
host = v
|
||||
}
|
||||
case []interface{}:
|
||||
if len(v) > 0 {
|
||||
if s, ok := v[0].(string); ok && s != "" {
|
||||
host = s
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", host, port)
|
||||
}
|
||||
|
||||
func isLegacyApiRoutingRule(rule map[string]interface{}) bool {
|
||||
outboundTag, _ := rule["outboundTag"].(string)
|
||||
if outboundTag != "api" {
|
||||
return false
|
||||
}
|
||||
val, ok := rule["inboundTag"]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
switch v := val.(type) {
|
||||
case []interface{}:
|
||||
return len(v) == 1 && v[0] == "api"
|
||||
case []string:
|
||||
return len(v) == 1 && v[0] == "api"
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func migrateLegacyApiConfig(cfg map[string]interface{}) bool {
|
||||
api, ok := cfg["api"].(map[string]interface{})
|
||||
if !ok || !apiListenMissing(api) {
|
||||
return false
|
||||
}
|
||||
|
||||
var listenAddr string
|
||||
inbounds, _ := cfg["inbounds"].([]interface{})
|
||||
|
||||
apiInboundIndex := -1
|
||||
var apiInbound map[string]interface{}
|
||||
for i, item := range inbounds {
|
||||
inbound, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
tag, _ := inbound["tag"].(string)
|
||||
protocol, _ := inbound["protocol"].(string)
|
||||
if tag == "api" && protocol == "dokodemo-door" {
|
||||
apiInboundIndex = i
|
||||
apiInbound = inbound
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if apiInboundIndex >= 0 {
|
||||
listenAddr = inboundListenAddress(apiInbound)
|
||||
if listenAddr == "" {
|
||||
listenAddr = defaultApiListen()
|
||||
}
|
||||
cfg["inbounds"] = RemoveIndex(inbounds, apiInboundIndex)
|
||||
|
||||
if routing, ok := cfg["routing"].(map[string]interface{}); ok {
|
||||
if rawRules, ok := routing["rules"].([]interface{}); ok {
|
||||
for i, item := range rawRules {
|
||||
rule, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if isLegacyApiRoutingRule(rule) {
|
||||
routing["rules"] = RemoveIndex(rawRules, i)
|
||||
cfg["routing"] = routing
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
listenAddr = defaultApiListen()
|
||||
}
|
||||
|
||||
api["listen"] = listenAddr
|
||||
cfg["api"] = api
|
||||
return true
|
||||
}
|
||||
|
||||
func routingRuleFromMap(raw map[string]interface{}, index int) *model.RoutingRule {
|
||||
tag, _ := raw["ruleTag"].(string)
|
||||
delete(raw, "ruleTag")
|
||||
if tag == "" {
|
||||
tag = fmt.Sprintf("migrated-rule-%d", index)
|
||||
}
|
||||
b, _ := json.Marshal(raw)
|
||||
return &model.RoutingRule{
|
||||
Tag: tag,
|
||||
Sort: index,
|
||||
RawJson: string(b),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RoutingRuleService) MigrateDB() {
|
||||
db := database.GetDB()
|
||||
var count int64
|
||||
db.Model(&model.RoutingRule{}).Count(&count)
|
||||
|
||||
templateConfig, err := s.settingService.GetXrayConfigTemplate()
|
||||
if err != nil {
|
||||
logger.Warning("routing rule migration: get xray template failed:", err)
|
||||
return
|
||||
}
|
||||
|
||||
var cfg map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(templateConfig), &cfg); err != nil {
|
||||
logger.Warning("routing rule migration: parse template failed:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if migrateLegacyApiConfig(cfg) {
|
||||
newTemplate, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
logger.Warning("routing rule migration: marshal legacy api config failed:", err)
|
||||
return
|
||||
}
|
||||
if err := s.settingService.saveSetting("xrayTemplateConfig", string(newTemplate)); err != nil {
|
||||
logger.Warning("routing rule migration: save legacy api config failed:", err)
|
||||
return
|
||||
}
|
||||
logger.Info("Migrated legacy api inbound to api.listen in xray settings")
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
routing, ok := cfg["routing"].(map[string]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
rawRules, ok := routing["rules"].([]interface{})
|
||||
if !ok || len(rawRules) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for i, item := range rawRules {
|
||||
raw, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
rule := routingRuleFromMap(raw, i)
|
||||
if err := db.Create(rule).Error; err != nil {
|
||||
logger.Warning("routing rule migration: create failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
routing["rules"] = []interface{}{}
|
||||
cfg["routing"] = routing
|
||||
newTemplate, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
logger.Warning("routing rule migration: marshal template failed:", err)
|
||||
return
|
||||
}
|
||||
if err := s.settingService.saveSetting("xrayTemplateConfig", string(newTemplate)); err != nil {
|
||||
logger.Warning("routing rule migration: save template failed:", err)
|
||||
return
|
||||
}
|
||||
logger.Info("Migrated", len(rawRules), "routing rule(s) from xray settings to database")
|
||||
}
|
||||
|
||||
func (s *RoutingRuleService) BuildRulesArray() ([]interface{}, error) {
|
||||
rules, err := s.GetAllRules()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -581,10 +581,6 @@ func (s *ServerService) ImportDB(file multipart.File) error {
|
|||
}
|
||||
return common.NewErrorf("Error migrating db: %v", err)
|
||||
}
|
||||
s.inboundService.MigrateDB()
|
||||
s.outboundService.MigrateDB()
|
||||
s.routingRuleService.MigrateDB()
|
||||
|
||||
// Start Xray
|
||||
err = s.RestartXrayService()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -10,6 +9,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alireza0/x-ui/config"
|
||||
"github.com/alireza0/x-ui/database"
|
||||
"github.com/alireza0/x-ui/database/model"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
|
|
@ -19,11 +19,8 @@ import (
|
|||
"github.com/alireza0/x-ui/web/entity"
|
||||
)
|
||||
|
||||
//go:embed config.json
|
||||
var xrayTemplateConfig string
|
||||
|
||||
var defaultValueMap = map[string]string{
|
||||
"xrayTemplateConfig": xrayTemplateConfig,
|
||||
"xrayTemplateConfig": config.GetDefaultXrayTemplate(),
|
||||
"webListen": "",
|
||||
"webDomain": "",
|
||||
"webPort": "54321",
|
||||
|
|
@ -472,7 +469,7 @@ func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
|||
|
||||
func (s *SettingService) GetDefaultXrayConfig() (interface{}, error) {
|
||||
var jsonData interface{}
|
||||
err := json.Unmarshal([]byte(xrayTemplateConfig), &jsonData)
|
||||
err := json.Unmarshal([]byte(config.GetDefaultXrayTemplate()), &jsonData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue