MHSanaei/3x-ui · error

open sqlite destination: %w

Error message

open sqlite destination: %w

What it means

Thrown by the PostgreSQL-to-SQLite export when gorm.Open(sqlite.Open(dstPath+"?_busy_timeout=10000")) fails on the destination file. The code already removed any existing file at dstPath and created its parent directory, so an open failure means the filesystem itself refused: unreadable directory, read-only mount, disk full, or a locked path (e.g. pointing at a directory).

Source

Thrown at internal/database/migrate_data.go:181

	// Start from an empty file so AutoMigrate creates the canonical schema.
	if err := os.Remove(dstPath); err != nil && !os.IsNotExist(err) {
		return err
	}

	src, err := gorm.Open(postgres.Open(srcDSN), &gorm.Config{Logger: logger.Discard})
	if err != nil {
		return fmt.Errorf("open postgres source: %w", err)
	}
	srcSQL, err := src.DB()
	if err != nil {
		return err
	}
	defer srcSQL.Close()

	// No WAL: keep all data in the main file so it is complete once closed.
	dst, err := gorm.Open(sqlite.Open(dstPath+"?_busy_timeout=10000"), &gorm.Config{Logger: logger.Discard})
	if err != nil {
		return fmt.Errorf("open sqlite destination: %w", err)
	}
	dstSQL, err := dst.DB()
	if err != nil {
		return err
	}
	defer dstSQL.Close()

	return copyAllModels(src, dst)
}

// copyAllModels (re)creates the schema on dst and copies every migrated table
// from src to dst in FK-safe order. src/dst may be any gorm backend.
func copyAllModels(src, dst *gorm.DB) error {
	for _, m := range migrationModels() {
		if err := dst.AutoMigrate(m); err != nil {
			return fmt.Errorf("AutoMigrate %T: %w", m, err)
		}
	}

View on GitHub (pinned to ad32144c42)

Solutions

  1. Check the wrapped error; the sqlite driver names the failing syscall (open: permission denied / read-only file system).
  2. Verify write access: touch the parent directory as the same user, and confirm the mount is writable.
  3. Free disk/inodes if the volume is full.
  4. Point dstPath at an absolute path in a known-writable directory (e.g. /etc/x-ui/ or a fresh backup dir).
Defensive patterns

Strategy: validation

Validate before calling

// ensure the destination path is writable before exporting
f, err := os.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600)
if err != nil { return fmt.Errorf("destination not writable: %w", err) }
f.Close()
os.Remove(dstPath)

Prevention

When it happens

Trigger: dstPath on a read-only filesystem or a directory the process cannot write; target file being held open/locked by another process; out of inode/space conditions; dstPath pointing to a directory; CGo sqlite driver unavailable in the build.

Common situations: Running the export in a container with a read-only volume mount; pointing dstPath at a path under a directory owned by root while running as a non-root user; leftover file handles from a previous crashed export on Windows.

Related errors


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