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

  1. Move/rename the existing dstPath first (e.g. mv x-ui.db x-ui.db.bak), then restore.
  2. Or restore to a fresh path and repoint the panel (XUI_DB_DSN / file location) at it.
  3. 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

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


AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15). Data as JSON: /api/errors/5f4827fb54e40f1f. Report an issue: GitHub.