mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-04 13:12:07 +03:00
Write config.json after a hot apply (#6686)
* Write config.json after a hot apply tryHotApply only updated the in-memory snapshot, so bin/config.json stayed stale until the next cold start and the Telegram/Discord config backups uploaded old rules. Persist the new config once the API calls succeed; a write failure is logged and does not restart the running core. * Test that a hot apply refreshes config.json
This commit is contained in:
@@ -1454,6 +1454,7 @@ func (s *XrayService) tryHotApply(process *xray.Process, newCfg *xray.Config) bo
|
||||
}
|
||||
if diff.Empty() {
|
||||
process.SetConfig(newCfg)
|
||||
persistHotConfig(process)
|
||||
return true
|
||||
}
|
||||
// The core's RemoveUser drops the credential only, so a disabled or deleted
|
||||
@@ -1522,9 +1523,18 @@ func (s *XrayService) tryHotApply(process *xray.Process, newCfg *xray.Config) bo
|
||||
}
|
||||
|
||||
process.SetConfig(newCfg)
|
||||
persistHotConfig(process)
|
||||
return true
|
||||
}
|
||||
|
||||
// persistHotConfig refreshes config.json after a hot apply; a write failure is
|
||||
// logged only, since the running core already has the change.
|
||||
func persistHotConfig(process *xray.Process) {
|
||||
if err := process.PersistConfig(); err != nil {
|
||||
logger.Warning("hot apply: failed to update config.json:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// addUserReconciling adds a user, and on an email conflict (the user was
|
||||
// already applied through the runtime API) replaces the existing user instead.
|
||||
func addUserReconciling(api *xray.XrayAPI, u xray.UserOp) error {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/json_util"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
||||
)
|
||||
|
||||
// The empty-diff branch of tryHotApply makes no gRPC call, so it can prove the
|
||||
// hot-apply path refreshes config.json without a running core.
|
||||
func TestTryHotApplyWritesConfigFile(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "config.json")
|
||||
routing := json_util.RawMessage(`{"rules":[]}`)
|
||||
process := xray.NewTestProcess(&xray.Config{RouterConfig: routing}, path)
|
||||
|
||||
if !(&XrayService{}).tryHotApply(process, &xray.Config{RouterConfig: routing}) {
|
||||
t.Fatal("tryHotApply = false, want true for an unchanged config")
|
||||
}
|
||||
if data, err := os.ReadFile(path); err != nil || len(data) == 0 {
|
||||
t.Fatalf("config.json not written after hot apply (err %v)", err)
|
||||
}
|
||||
}
|
||||
@@ -316,6 +316,22 @@ func (p *Process) SetConfig(config *Config) {
|
||||
p.config = config
|
||||
}
|
||||
|
||||
// PersistConfig writes the current configuration snapshot to the config file,
|
||||
// keeping it in step after a hot apply (Start only writes it on a cold start).
|
||||
func (p *Process) PersistConfig() error {
|
||||
p.mu.RLock()
|
||||
data, err := json.MarshalIndent(p.config, "", " ")
|
||||
path := p.configPath
|
||||
p.mu.RUnlock()
|
||||
if err != nil {
|
||||
return common.NewErrorf("Failed to generate XRAY configuration files: %v", err)
|
||||
}
|
||||
if path == "" {
|
||||
path = GetConfigPath()
|
||||
}
|
||||
return writeFileAtomic(path, data, 0o600)
|
||||
}
|
||||
|
||||
// GetOnlineClients returns the union of locally-online clients and
|
||||
// node-online clients from every registered remote panel. Dedupes by
|
||||
// email so a client connected to both a local and a node-managed inbound
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package xray
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/json_util"
|
||||
)
|
||||
|
||||
func TestPersistConfigWritesCurrentSnapshot(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "config.json")
|
||||
p := NewTestProcess(&Config{RouterConfig: json_util.RawMessage(`{"rules":[]}`)}, path)
|
||||
|
||||
p.SetConfig(&Config{RouterConfig: json_util.RawMessage(`{"rules":[{"outboundTag":"warp"}]}`)})
|
||||
if err := p.PersistConfig(); err != nil {
|
||||
t.Fatalf("PersistConfig: %v", err)
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(data), `"outboundTag": "warp"`) && !strings.Contains(string(data), `"outboundTag":"warp"`) {
|
||||
t.Fatalf("config file does not hold the new routing:\n%s", data)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user