MHSanaei/3x-ui · error

open sqlite source: %w

Error message

open sqlite source: %w

What it means

MigrateData opens the SQLite source via gorm with WAL + 10s busy_timeout and wraps a failed open. The file exists (earlier Stat passed) but could not be opened as a usable SQLite DB: corrupt header, unreadable permissions, or a driver-level failure. The wrapped error distinguishes these.

Source

Thrown at internal/database/migrate_data.go:82

// (re)created with AutoMigrate; truncate and copy then run in one transaction,
// so a failed migration leaves the destination data unchanged. Source data is
// left untouched.
func MigrateData(srcPath, dstDSN string) error {
	if _, err := os.Stat(srcPath); err != nil {
		return fmt.Errorf("source sqlite not found at %s: %w", srcPath, err)
	}
	if dstDSN == "" {
		return errors.New("destination DSN is required")
	}

	if err := os.MkdirAll(path.Dir(srcPath), 0o755); err != nil {
		return err
	}

	srcDSN := srcPath + "?_journal_mode=WAL&_busy_timeout=10000"
	src, err := gorm.Open(sqlite.Open(srcDSN), &gorm.Config{Logger: logger.Discard})
	if err != nil {
		return fmt.Errorf("open sqlite source: %w", err)
	}
	srcSQL, err := src.DB()
	if err != nil {
		return err
	}
	defer srcSQL.Close()

	dst, err := gorm.Open(postgres.Open(dstDSN), &gorm.Config{Logger: logger.Discard})
	if err != nil {
		return fmt.Errorf("open postgres destination: %w", err)
	}
	dstSQL, err := dst.DB()
	if err != nil {
		return err
	}
	defer dstSQL.Close()
	dstSQL.SetConnMaxLifetime(time.Hour)

View on GitHub (pinned to ad32144c42)

Solutions

  1. Run `sqlite3 <src> 'PRAGMA integrity_check;'` to verify the file is a sound database.
  2. Fix ownership/permissions so the panel process can read (and briefly write, for WAL) the file.
  3. Re-copy the DB together with any -wal/-shm sidecars, or better: produce it with BackupSQLite and migrate from that snapshot.
Defensive patterns

Strategy: try-catch

Validate before calling

if err := verifySQLiteFile(srcPath); err != nil { // runs PRAGMA integrity_check via a throwaway handle
    return err
}

Try / catch

if err := database.MigrateData(src, dsn); err != nil {
    if strings.Contains(err.Error(), "open sqlite source") {
        // exists but unusable: check perms/corruption, re-copy from BackupSQLite output
    }
    return err
}

Prevention

When it happens

Trigger: srcPath exists but is a truncated/corrupt DB (interrupted copy, sparse transfer); file owned by another user without read permission; path is a directory; WAL sidecar files (-wal/-shm) missing after an unclean copy while the DB depended on them.

Common situations: Copying x-ui.db mid-write without VACUUM INTO/backup API; rsync excluding -wal/-shm; permissions after moving files between users/containers.

Related errors


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