fix: ignore duplicate column errors during AutoMigrate on upgraded DBs

SQLite raises 'duplicate column name: <col>' 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: <col>'
  2. Migrator().HasColumn confirms the column is already present in the DB

Fresh databases and all other error types are unaffected.
This commit is contained in:
MHSanaei
2026-05-14 11:10:38 +02:00
parent 5dc02a9af3
commit bd8d33980f
+28 -2
View File
@@ -10,6 +10,7 @@ import (
"os" "os"
"path" "path"
"slices" "slices"
"strings"
"time" "time"
"github.com/mhsanaei/3x-ui/v3/config" "github.com/mhsanaei/3x-ui/v3/config"
@@ -42,8 +43,12 @@ func initModels() error {
&model.Node{}, &model.Node{},
&model.ApiToken{}, &model.ApiToken{},
} }
for _, model := range models { for _, mdl := range models {
if err := db.AutoMigrate(model); err != nil { 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) log.Printf("Error auto migrating model: %v", err)
return err return err
} }
@@ -51,6 +56,27 @@ func initModels() error {
return nil 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. // initUser creates a default admin user if the users table is empty.
func initUser() error { func initUser() error {
empty, err := isTableEmpty("users") empty, err := isTableEmpty("users")