MHSanaei/3x-ui · error

restore failed: %w

Error message

restore failed: %w

What it means

RestoreSQLite executed the dump's SQL via one multi-statement ExecContext and the driver returned an error — the dump did not apply. The function closes the connection and removes the half-built dstPath, so no partial database survives. Root causes live in the wrapped error: malformed/truncated dump, SQL features the SQLite build rejects, or destination filesystem problems.

Source

Thrown at internal/database/dump_sqlite.go:134

	}
	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()
}

// dumpTableData appends one INSERT statement per row of table to b.
func dumpTableData(db *sql.DB, table string, b *strings.Builder) error {
	rows, err := db.QueryContext(context.Background(), `SELECT * FROM "`+table+`"`)
	if err != nil {
		return err
	}
	defer rows.Close()

	cols, err := rows.Columns()
	if err != nil {
		return err
	}
	n := len(cols)
	prefix := `INSERT INTO "` + table + `" VALUES(`

View on GitHub (pinned to ad32144c42)

Solutions

  1. Read the wrapped driver error — 'near ... syntax error' means dump corruption, 'disk I/O error'/'database or disk is full' means storage.
  2. Re-generate the dump and transfer it in binary-safe mode (scp, not clipboard); compare sizes/checksums.
  3. If the dump came from `sqlite3 .dump` of a much newer SQLite, re-dump with plain features or update the panel so its bundled SQLite matches.
Defensive patterns

Strategy: try-catch

Validate before calling

// Sanity-check the dump before restoring:
head, err := os.ReadFile(dumpPath)
if err != nil { return err }
if !bytes.HasPrefix(head, []byte("-- SQLite dump")) && !bytes.Contains(head, []byte("BEGIN TRANSACTION;")) {
    return errors.New("file does not look like a sqlite dump")
}

Try / catch

if err := database.RestoreSQLite(dumpPath, dstPath); err != nil {
    if strings.Contains(err.Error(), "restore failed") {
        // dstPath was removed by the function; fix the dump then retry
        return diagnoseDump(dumpPath)
    }
    return err
}

Prevention

When it happens

Trigger: Dump file truncated during transfer (checksum mismatch not caught earlier); hand-edited dump with a syntax error; dump produced by a newer SQLite with features the bundled CGo SQLite lacks; disk full while replaying INSERTs.

Common situations: Moving dumps between hosts via copy/paste or proxies that mangle bytes; restoring dumps edited in tools that re-encode quotes/encoding; containers with a full overlay FS.

Related errors


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