From bd8d33980fd38ce3c9ec296025d646400d1fb505 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Thu, 14 May 2026 11:10:38 +0200 Subject: [PATCH] fix: ignore duplicate column errors during AutoMigrate on upgraded DBs SQLite raises 'duplicate column name: ' when GORM tries to ADD a column that already exists in an older schema (seen: allow_private_address, node_id on the nodes table). This caused database initialisation to fail on every restart after an upgrade. The new isIgnorableDuplicateColumnErr helper skips the error only when: 1. The error message matches 'duplicate column name: ' 2. Migrator().HasColumn confirms the column is already present in the DB Fresh databases and all other error types are unaffected. --- database/db.go | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/database/db.go b/database/db.go index e1694e1a..89d4106b 100644 --- a/database/db.go +++ b/database/db.go @@ -10,6 +10,7 @@ import ( "os" "path" "slices" + "strings" "time" "github.com/mhsanaei/3x-ui/v3/config" @@ -42,8 +43,12 @@ func initModels() error { &model.Node{}, &model.ApiToken{}, } - for _, model := range models { - if err := db.AutoMigrate(model); err != nil { + for _, mdl := range models { + if err := db.AutoMigrate(mdl); err != nil { + if isIgnorableDuplicateColumnErr(err, mdl) { + log.Printf("Ignoring duplicate column during auto migration for %T: %v", mdl, err) + continue + } log.Printf("Error auto migrating model: %v", err) return err } @@ -51,6 +56,27 @@ func initModels() error { return nil } +func isIgnorableDuplicateColumnErr(err error, mdl any) bool { + if err == nil { + return false + } + errMsg := strings.ToLower(err.Error()) + const dupPrefix = "duplicate column name:" + if !strings.Contains(errMsg, dupPrefix) { + return false + } + idx := strings.Index(errMsg, dupPrefix) + if idx < 0 { + return false + } + col := strings.TrimSpace(errMsg[idx+len(dupPrefix):]) + col = strings.Trim(col, "`\"[]") + if col == "" { + return false + } + return db != nil && db.Migrator().HasColumn(mdl, col) +} + // initUser creates a default admin user if the users table is empty. func initUser() error { empty, err := isTableEmpty("users")