fix(database): make cross-db migration lossless, transactional, and pre-checked

migrationModels was missing ClientGroup and ClientGlobalTraffic, so both
migration directions silently dropped client groups and global client
traffic; the model list is now extracted to allModels and a parity test
keeps the two lists from drifting again. MigrateData runs its truncate
and copy inside one transaction so a failed import rolls back instead of
leaving the destination truncated (sequences resync after commit since
setval is non-transactional). New PrepareSQLiteForMigration rejects
uploads that are not a panel database and AutoMigrates old backups so
their missing tables cannot break the row copy.
This commit is contained in:
MHSanaei
2026-07-12 20:14:07 +02:00
parent 30b611614b
commit 54fc0fd47c
5 changed files with 242 additions and 31 deletions
+10 -6
View File
@@ -56,8 +56,8 @@ const (
defaultPassword = "admin"
)
func initModels() error {
models := []any{
func allModels() []any {
return []any{
&model.User{},
&model.Inbound{},
&model.OutboundTraffics{},
@@ -78,12 +78,16 @@ func initModels() error {
&model.ClientGlobalTraffic{},
&model.OutboundSubscription{},
}
}
func initModels() error {
models := allModels()
for _, mdl := range models {
if IsPostgres() && postgresModelSettled(mdl) {
continue
}
if err := db.AutoMigrate(mdl); err != nil {
if isIgnorableDuplicateColumnErr(err, mdl) {
if isIgnorableDuplicateColumnErr(db, err, mdl) {
log.Printf("Ignoring duplicate column during auto migration for %T: %v", mdl, err)
continue
}
@@ -999,7 +1003,7 @@ func dedupeInboundSettingsClients() error {
return nil
}
func isIgnorableDuplicateColumnErr(err error, mdl any) bool {
func isIgnorableDuplicateColumnErr(gdb *gorm.DB, err error, mdl any) bool {
if err == nil {
return false
}
@@ -1009,14 +1013,14 @@ func isIgnorableDuplicateColumnErr(err error, mdl any) bool {
if _, after, ok := strings.Cut(errMsg, sqlitePrefix); ok {
col := strings.TrimSpace(after)
col = strings.Trim(col, "`\"[]")
return col != "" && db != nil && db.Migrator().HasColumn(mdl, col)
return col != "" && gdb != nil && gdb.Migrator().HasColumn(mdl, col)
}
if strings.Contains(errMsg, "already exists") && strings.Contains(errMsg, "column ") {
if _, after, ok := strings.Cut(errMsg, "column \""); ok {
rest := after
if e := strings.Index(rest, "\""); e > 0 {
col := rest[:e]
return col != "" && db != nil && db.Migrator().HasColumn(mdl, col)
return col != "" && gdb != nil && gdb.Migrator().HasColumn(mdl, col)
}
}
}