mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-20 10:00:58 +00:00
6e80a468e3
* feat(server): keep this machine's own settings when importing a database Import replaces the database wholesale, so the uploaded file's listen addresses, ports, base path, certificate paths and node identity land on the destination. Moving a configuration to a new host therefore leaves the panel answering on an address it does not own, presenting certificates it does not have, and claiming the source machine's identity towards its nodes. Capture the host-bound settings before the swap and write them back once the imported database opens. Everything else — inbounds, clients, templates, the rest of the settings — still comes from the file. A checkbox controls it, defaulting to keeping this machine's values; clearing it restores the old behaviour for anyone deliberately cloning a host. * fix(server): drop imported host settings this machine never had, and cover Postgres Two gaps in the previous commit. The snapshot only recorded rows that existed, so a key with no row here — the default for every certificate path, both listen addresses and all the node mTLS material — kept the imported value: exactly the case the change is meant to fix. The snapshot now records which keys were absent and deletes the imported row for them, letting the default apply again. The PostgreSQL path took the flag and ignored it, so a dump restore still adopted the source machine's settings. It now captures and restores the same way the SQLite path does. * chore: drop the accidentally committed dist build stub internal/web/dist/.gitkeep is what make dist-stub creates locally. Committing it changes fresh-clone behaviour for everyone: today a bare go build fails loudly on //go:embed all:dist, which is the documented signal to run the stub target; with the file present the build succeeds and the panel serves an empty dist instead. --------- Co-authored-by: n0ctal <n0ctal@users.noreply.github.com> Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
100 lines
3.2 KiB
Go
100 lines
3.2 KiB
Go
package service
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
)
|
|
|
|
// An imported database carries the source machine's listen addresses,
|
|
// certificates and node identity. Keeping this machine's own values is what
|
|
// stops the panel from becoming unreachable on its own address after a restore.
|
|
func TestImportKeepsHostBoundSettings(t *testing.T) {
|
|
setupConflictDB(t)
|
|
db := database.GetDB()
|
|
|
|
mine := map[string]string{
|
|
"webPort": "8443",
|
|
"webCertFile": "/etc/ssl/this-host.pem",
|
|
"webBasePath": "/mine/",
|
|
"subURI": "https://this-host.example/sub/",
|
|
"panelGuid": "this-host-guid",
|
|
"nodeMtlsClientCertPem": "this-host-leaf",
|
|
}
|
|
for key, value := range mine {
|
|
if err := db.Create(&model.Setting{Key: key, Value: value}).Error; err != nil {
|
|
t.Fatalf("seed %s: %v", key, err)
|
|
}
|
|
}
|
|
// A setting that belongs to the configuration, not the machine.
|
|
if err := db.Create(&model.Setting{Key: "remarkTemplate", Value: "mine"}).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
kept := captureHostBoundSettings()
|
|
if len(kept.values) != len(mine) {
|
|
t.Fatalf("captured %d host settings, want %d: %v", len(kept.values), len(mine), kept.values)
|
|
}
|
|
|
|
// Stand in for the import: every row now holds the source machine's value.
|
|
for key := range mine {
|
|
if err := db.Model(&model.Setting{}).Where("key = ?", key).
|
|
Update("value", "from-imported-file").Error; err != nil {
|
|
t.Fatalf("overwrite %s: %v", key, err)
|
|
}
|
|
}
|
|
if err := db.Model(&model.Setting{}).Where("key = ?", "remarkTemplate").
|
|
Update("value", "from-imported-file").Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
restoreHostBoundSettings(kept)
|
|
|
|
for key, want := range mine {
|
|
var got model.Setting
|
|
if err := db.Where("key = ?", key).First(&got).Error; err != nil {
|
|
t.Fatalf("read back %s: %v", key, err)
|
|
}
|
|
if got.Value != want {
|
|
t.Fatalf("setting %s = %q after import, want this machine's %q", key, got.Value, want)
|
|
}
|
|
}
|
|
|
|
var carried model.Setting
|
|
if err := db.Where("key = ?", "remarkTemplate").First(&carried).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if carried.Value != "from-imported-file" {
|
|
t.Fatalf("remarkTemplate = %q, want the imported value: only host-bound keys may survive", carried.Value)
|
|
}
|
|
}
|
|
|
|
// The destination usually has no row at all for the certificate paths and the
|
|
// node identity — the built-in default applies. The imported row must go, or
|
|
// the panel quietly adopts the source machine's certificate path.
|
|
func TestImportDropsHostBoundSettingsThisMachineNeverHad(t *testing.T) {
|
|
setupConflictDB(t)
|
|
db := database.GetDB()
|
|
|
|
kept := captureHostBoundSettings()
|
|
|
|
for _, key := range []string{"webCertFile", "subCertFile", "nodeMtlsClientCertPem"} {
|
|
if err := db.Create(&model.Setting{Key: key, Value: "from-imported-file"}).Error; err != nil {
|
|
t.Fatalf("seed imported %s: %v", key, err)
|
|
}
|
|
}
|
|
|
|
restoreHostBoundSettings(kept)
|
|
|
|
for _, key := range []string{"webCertFile", "subCertFile", "nodeMtlsClientCertPem"} {
|
|
var count int64
|
|
if err := db.Model(&model.Setting{}).Where("key = ?", key).Count(&count).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if count != 0 {
|
|
t.Fatalf("imported %s survived although this machine had no row for it", key)
|
|
}
|
|
}
|
|
}
|