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

@@ -53,6 +53,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
g.GET("/getPanelUpdateInfo", a.getPanelUpdateInfo)
g.GET("/getConfigJson", a.getConfigJson)
g.GET("/getDb", a.getDb)
g.GET("/getMigration", a.getMigration)
g.GET("/getNewUUID", a.getNewUUID)
g.GET("/getWebCertFiles", a.getWebCertFiles)
g.GET("/getNewX25519Cert", a.getNewX25519Cert)
@@ -300,6 +301,24 @@ func (a *ServerController) getDb(c *gin.Context) {
c.Writer.Write(db)
}
// getMigration downloads a cross-engine migration file: a .dump on SQLite or a
// .db SQLite database on PostgreSQL, so the data can seed the other backend.
func (a *ServerController) getMigration(c *gin.Context) {
data, filename, err := a.serverService.GetMigration()
if err != nil {
jsonMsg(c, I18nWeb(c, "pages.index.getDatabaseError"), err)
return
}
if !filenameRegex.MatchString(filename) {
c.AbortWithError(http.StatusBadRequest, fmt.Errorf("invalid filename"))
return
}
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", "attachment; filename="+filename)
c.Writer.Write(data)
}
// importDB imports a database file and restarts the Xray service.
func (a *ServerController) importDB(c *gin.Context) {
file, _, err := c.Request.FormFile("db")