MHSanaei/3x-ui · error
destination already exists: %s
Error message
destination already exists: %s
What it means
RestoreSQLite rebuilds a database at dstPath from a SQL dump and intentionally refuses when dstPath already exists, so a live/old database is never silently clobbered. The check runs after reading the dump script but before opening the destination. It is a safety stop, not corruption.
Source
Thrown at internal/database/dump_sqlite.go:118
if err := rows2.Err(); err != nil {
return nil, err
}
b.WriteString("COMMIT;\n")
return []byte(b.String()), nil
}
// RestoreSQLite rebuilds a SQLite database at dstPath from a SQL text dump
// produced by DumpSQLite (or `sqlite3 .dump`). dstPath must not already exist so
// an existing database is never clobbered silently.
func RestoreSQLite(dumpPath, dstPath string) error {
script, err := os.ReadFile(dumpPath)
if err != nil {
return err
}
if _, err := os.Stat(dstPath); err == nil {
return fmt.Errorf("destination already exists: %s", dstPath)
}
gdb, err := gorm.Open(sqlite.Open(dstPath), &gorm.Config{Logger: logger.Discard})
if err != nil {
return err
}
sqlDB, err := gdb.DB()
if err != nil {
return err
}
// mattn/go-sqlite3 executes every statement in a multi-statement string.
if _, err := sqlDB.ExecContext(context.Background(), string(script)); err != nil {
sqlDB.Close()
os.Remove(dstPath)
return fmt.Errorf("restore failed: %w", err)
}
return sqlDB.Close()View on GitHub (pinned to ad32144c42)
Solutions
- Move/rename the existing dstPath first (e.g. mv x-ui.db x-ui.db.bak), then restore.
- Or restore to a fresh path and repoint the panel (XUI_DB_DSN / file location) at it.
- Verify the dump source is the intended one before deleting anything.
Example fix
# before
RestoreSQLite("dump.sql", "/etc/x-ui/x-ui.db")
# after
mv /etc/x-ui/x-ui.db /etc/x-ui/x-ui.db.bak
RestoreSQLite("dump.sql", "/etc/x-ui/x-ui.db") Defensive patterns
Strategy: validation
Validate before calling
if _, err := os.Stat(dstPath); err == nil {
return fmt.Errorf("destination exists; move it aside first: %s", dstPath)
} Prevention
- Restore runbooks must move the old DB aside (mv x-ui.db x-ui.db.bak) before restore.
- Prefer restoring to a fresh path and repointing the panel, so rollback stays possible.
When it happens
Trigger: Restoring over the panel's current DB file without moving it aside first; running the restore command twice; targeting a path where an old DB remains.
Common situations: Disaster-recovery runbooks skipping the 'move old db away' step; migration rollback attempts landing on the original filename.
Related errors
- sqlite backup destination already exists: %s
- source sqlite not found at %s: %w
- restore failed: %w
- open sqlite source: %w
- open sqlite destination: %w
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/5f4827fb54e40f1f.
Report an issue: GitHub.