feat(migrate-db): SQLite <-> .dump conversion and Download Migration in Overview

Binary: extend the migrate-db subcommand with --dump and --restore so a
SQLite database can be exported to a portable SQL text dump and rebuilt from
one, alongside the existing --dsn PostgreSQL copy. Implemented in Go via the
bundled sqlite driver (new database/dump_sqlite.go); no external sqlite3 client
is required. Add ExportPostgresToSQLite (reverse of MigrateData) to build a
SQLite .db from live PostgreSQL data, reusing the shared copyAllModels helper.

Overview: add a "Download Migration" item to Backup & Restore plus a
getMigration endpoint/service that returns a .dump on SQLite or a .db on
PostgreSQL, so the data can seed a panel on the other backend. Document the
endpoint in api-docs and translate the three new strings across all locales.

Tests: cover the destination-side copy (AutoMigrate + copyTable into SQLite)
and the dump/restore round-trip including quoted values. Ignore *.dump.

The x-ui.sh helper that drives this from the CLI is in PR #4910.
This commit is contained in:
MHSanaei
2026-06-04 15:32:22 +02:00
parent 5c1d64b841
commit a07c7b7f4e
24 changed files with 599 additions and 22 deletions

View File

@@ -307,6 +307,11 @@ export const sections: readonly Section[] = [
path: '/panel/api/server/getDb',
summary: 'Stream the SQLite database file as an attachment. Use as a manual backup.',
},
{
method: 'GET',
path: '/panel/api/server/getMigration',
summary: 'Stream a cross-engine migration file as an attachment: a .dump (SQL text) on SQLite, or a .db SQLite database built from the live data on PostgreSQL.',
},
{
method: 'GET',
path: '/panel/api/server/getNewUUID',

View File

@@ -25,6 +25,10 @@ export default function BackupModal({ open, basePath: _basePath, onClose, onBusy
window.location.href = (window.X_UI_BASE_PATH || '') + 'panel/api/server/getDb';
}
function exportMigration() {
window.location.href = (window.X_UI_BASE_PATH || '') + 'panel/api/server/getMigration';
}
function importDb() {
const fileInput = document.createElement('input');
fileInput.type = 'file';
@@ -82,6 +86,16 @@ export default function BackupModal({ open, basePath: _basePath, onClose, onBusy
<Button type="primary" onClick={exportDb} icon={<DownloadOutlined />} />
</div>
<div className="backup-item">
<div className="backup-meta">
<div className="backup-title">{t('pages.index.migrationDownload')}</div>
<div className="backup-description">
{isPostgres ? t('pages.index.migrationDownloadPgDesc') : t('pages.index.migrationDownloadDesc')}
</div>
</div>
<Button type="primary" onClick={exportMigration} icon={<DownloadOutlined />} />
</div>
<div className="backup-item">
<div className="backup-meta">
<div className="backup-title">{t('pages.index.importDatabase')}</div>