From 26b748338a6c01b1122c161959ec4c1eb02eef72 Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami Date: Fri, 5 May 2023 22:55:40 +0430 Subject: [PATCH 1/8] add import db api route --- web/controller/server.go | 22 +++++++++- web/service/server.go | 91 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 3 deletions(-) diff --git a/web/controller/server.go b/web/controller/server.go index c365ae4b..51c220c3 100644 --- a/web/controller/server.go +++ b/web/controller/server.go @@ -41,6 +41,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) { g.POST("/logs/:count", a.getLogs) g.POST("/getConfigJson", a.getConfigJson) g.GET("/getDb", a.getDb) + g.POST("/importDB", a.importDB) g.POST("/getNewX25519Cert", a.getNewX25519Cert) } @@ -99,8 +100,8 @@ func (a *ServerController) stopXrayService(c *gin.Context) { return } jsonMsg(c, "Xray stoped", err) - } + func (a *ServerController) restartXrayService(c *gin.Context) { err := a.serverService.RestartXrayService() if err != nil { @@ -108,7 +109,6 @@ func (a *ServerController) restartXrayService(c *gin.Context) { return } jsonMsg(c, "Xray restarted", err) - } func (a *ServerController) getLogs(c *gin.Context) { @@ -144,6 +144,24 @@ func (a *ServerController) getDb(c *gin.Context) { c.Writer.Write(db) } +func (a *ServerController) importDB(c *gin.Context) { + // Get the file from the request body + file, _, err := c.Request.FormFile("db") + if err != nil { + jsonMsg(c, "Error reading db file", err) + return + } + defer file.Close() + // Import it + err = a.serverService.ImportDB(file) + if err != nil { + jsonMsg(c, "", err) + return + } + a.lastGetStatusTime = time.Now() + jsonObj(c, "Import DB", nil) +} + func (a *ServerController) getNewX25519Cert(c *gin.Context) { cert, err := a.serverService.GetNewX25519Cert() if err != nil { diff --git a/web/service/server.go b/web/service/server.go index d4048196..b4bce974 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "io/fs" + "mime/multipart" "net/http" "os" "os/exec" @@ -14,7 +15,9 @@ import ( "strings" "time" "x-ui/config" + "x-ui/database" "x-ui/logger" + "x-ui/util/common" "x-ui/util/sys" "x-ui/xray" @@ -73,7 +76,8 @@ type Release struct { } type ServerService struct { - xrayService XrayService + xrayService XrayService + inboundService InboundService } func (s *ServerService) GetStatus(lastStatus *Status) *Status { @@ -393,6 +397,91 @@ func (s *ServerService) GetDb() ([]byte, error) { return fileContents, nil } +func (s *ServerService) ImportDB(file multipart.File) error { + // Check if the file is a SQLite database + isValidDb, err := database.IsSQLiteDB(file) + if err != nil { + return common.NewErrorf("Error checking db file format: %v", err) + } + if !isValidDb { + return common.NewError("Invalid db file format") + } + + // Save the file as temporary file + tempPath := fmt.Sprintf("%s.temp", config.GetDBPath()) + tempFile, err := os.Create(tempPath) + if err != nil { + return common.NewErrorf("Error creating temporary db file: %v", err) + } + defer tempFile.Close() + + // Reset the file reader to the beginning + _, err = file.Seek(0, 0) + if err != nil { + defer os.Remove(tempPath) + return common.NewErrorf("Error resetting file reader: %v", err) + } + + // Save temp file + _, err = io.Copy(tempFile, file) + if err != nil { + defer os.Remove(tempPath) + return common.NewErrorf("Error saving db: %v", err) + } + + // Check if we can init db or not + err = database.InitDB(tempPath) + if err != nil { + defer os.Remove(tempPath) + return common.NewErrorf("Error checking db: %v", err) + } + + // Stop Xray if its running + if s.xrayService.IsXrayRunning() { + err := s.StopXrayService() + if err != nil { + defer os.Remove(tempPath) + return common.NewErrorf("Failed to stop Xray: %v", err) + } + } + + // Backup db for fallback + fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath()) + err = os.Rename(config.GetDBPath(), fallbackPath) + if err != nil { + defer os.Remove(tempPath) + return common.NewErrorf("Error backup temporary db file: %v", err) + } + + // Move temp to DB path + err = os.Rename(tempPath, config.GetDBPath()) + if err != nil { + defer os.Remove(tempPath) + defer os.Rename(fallbackPath, config.GetDBPath()) + return common.NewErrorf("Error moving db file: %v", err) + } + + // Migrate DB + err = database.InitDB(config.GetDBPath()) + if err != nil { + defer os.Rename(fallbackPath, config.GetDBPath()) + return common.NewErrorf("Error migrating db: %v", err) + } + s.inboundService.MigrationRequirements() + s.inboundService.RemoveOrphanedTraffics() + + // remove fallback file + defer os.Remove(fallbackPath) + + // Start Xray + err = s.RestartXrayService() + if err != nil { + return common.NewErrorf("Imported DB but Failed to start Xray: %v", err) + } + + return nil +} + func (s *ServerService) GetNewX25519Cert() (interface{}, error) { // Run the command cmd := exec.Command(xray.GetBinaryPath(), "x25519") From 3ee57fd51de2af99d72179afbcbf30db6c59d75c Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami Date: Fri, 5 May 2023 22:57:47 +0430 Subject: [PATCH 2/8] update axios-init and db.go --- database/db.go | 12 ++++++++++++ web/assets/css/custom.css | 2 +- web/assets/js/axios-init.js | 12 ++++++++---- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/database/db.go b/database/db.go index 57dd2cfb..54d64031 100644 --- a/database/db.go +++ b/database/db.go @@ -1,6 +1,8 @@ package database import ( + "bytes" + "io" "io/fs" "os" "path" @@ -98,3 +100,13 @@ func GetDB() *gorm.DB { func IsNotFound(err error) bool { return err == gorm.ErrRecordNotFound } + +func IsSQLiteDB(file io.Reader) (bool, error) { + signature := []byte("SQLite format 3\x00") + buf := make([]byte, len(signature)) + _, err := file.Read(buf) + if err != nil { + return false, err + } + return bytes.Equal(buf, signature), nil +} diff --git a/web/assets/css/custom.css b/web/assets/css/custom.css index 1eaf6ed2..b668551e 100644 --- a/web/assets/css/custom.css +++ b/web/assets/css/custom.css @@ -1,5 +1,5 @@ #app { - height: 100%; + height: 100vh; } .ant-space { diff --git a/web/assets/js/axios-init.js b/web/assets/js/axios-init.js index 22d14d76..bd55c3cf 100644 --- a/web/assets/js/axios-init.js +++ b/web/assets/js/axios-init.js @@ -3,10 +3,14 @@ axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; axios.interceptors.request.use( config => { - config.data = Qs.stringify(config.data, { - arrayFormat: 'repeat' - }); + if (config.data instanceof FormData) { + config.headers['Content-Type'] = 'multipart/form-data'; + } else { + config.data = Qs.stringify(config.data, { + arrayFormat: 'repeat', + }); + } return config; }, error => Promise.reject(error) -); \ No newline at end of file +); From ec1efabcaa29e9c6c495bf8863897a5566a5db5e Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami Date: Fri, 5 May 2023 22:58:22 +0430 Subject: [PATCH 3/8] add modal and button for import/export db --- web/html/common/text_modal.html | 3 +- web/html/xui/index.html | 102 ++++++++++++++++++++++++++++---- 2 files changed, 94 insertions(+), 11 deletions(-) diff --git a/web/html/common/text_modal.html b/web/html/common/text_modal.html index b2da6160..ce77d0ca 100644 --- a/web/html/common/text_modal.html +++ b/web/html/common/text_modal.html @@ -4,7 +4,8 @@ :class="siderDrawer.isDarkTheme ? darkClass : ''" :ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}"> + :href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)" + :download="txtModal.fileName"> {{ i18n "download" }} [[ txtModal.fileName ]] {{ i18n "menu.link" }}: - Logs - Config - Backup + {{ i18n "pages.index.logs" }} + {{ i18n "pages.index.config" }} + {{ i18n "pages.index.backup" }} @@ -187,6 +187,7 @@ + + + + +

+ + [[ backupModal.description ]] +

+ + + [[ backupModal.exportText ]] + + + [[ backupModal.importText ]] + + +
+ {{template "js" .}} {{template "textModal"}} @@ -338,6 +358,29 @@ }, }; + const backupModal = { + visible: false, + title: '', + description: '', + exportText: '', + importText: '', + show({ + title = '{{ i18n "pages.index.backupTitle" }}', + description = '{{ i18n "pages.index.backupDescription" }}', + exportText = '{{ i18n "pages.index.exportDatabase" }}', + importText = '{{ i18n "pages.index.importDatabase" }}', + }) { + this.title = title; + this.description = description; + this.exportText = exportText; + this.importText = importText; + this.visible = true; + }, + hide() { + this.visible = false; + }, + }; + const app = new Vue({ delimiters: ['[[', ']]'], el: '#app', @@ -346,6 +389,7 @@ status: new Status(), versionModal, logModal, + backupModal, spinning: false, loadingTip: '{{ i18n "loading"}}', }, @@ -387,7 +431,6 @@ }, }); }, - //here add stop xray function async stopXrayService() { this.loading(true); const msg = await HttpUtil.post('server/stopXrayService'); @@ -396,7 +439,6 @@ return; } }, - //here add restart xray function async restartXrayService() { this.loading(true); const msg = await HttpUtil.post('server/restartXrayService'); @@ -412,20 +454,60 @@ if (!msg.success) { return; } - logModal.show(msg.obj,rows); + logModal.show(msg.obj, rows); }, - async openConfig(){ + async openConfig() { this.loading(true); const msg = await HttpUtil.post('server/getConfigJson'); this.loading(false); if (!msg.success) { return; } - txtModal.show('config.json',JSON.stringify(msg.obj, null, 2),'config.json'); + txtModal.show('config.json', JSON.stringify(msg.obj, null, 2), 'config.json'); }, - getBackup(){ + openBackup() { + backupModal.show({ + title: '{{ i18n "pages.index.backupTitle" }}', + description: '{{ i18n "pages.index.backupDescription" }}', + exportText: '{{ i18n "pages.index.exportDatabase" }}', + importText: '{{ i18n "pages.index.importDatabase" }}', + }); + }, + exportDatabase() { window.location = basePath + 'server/getDb'; - } + }, + importDatabase() { + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = '.db'; + fileInput.addEventListener('change', async (event) => { + const dbFile = event.target.files[0]; + if (dbFile) { + const formData = new FormData(); + formData.append('db', dbFile); + backupModal.hide(); + this.loading(true); + const uploadMsg = await HttpUtil.post('server/importDB', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + } + }); + this.loading(false); + if (!uploadMsg.success) { + return; + } + this.loading(true); + const restartMsg = await HttpUtil.post("/xui/setting/restartPanel"); + this.loading(false); + if (restartMsg.success) { + this.loading(true); + await PromiseUtil.sleep(5000); + location.reload(); + } + } + }); + fileInput.click(); + }, }, async mounted() { while (true) { From c197165da7c353aed4cab717a67721936dd24c7e Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami Date: Fri, 5 May 2023 22:58:40 +0430 Subject: [PATCH 4/8] update translation --- web/translation/translate.en_US.toml | 7 +++++++ web/translation/translate.fa_IR.toml | 7 +++++++ web/translation/translate.zh_Hans.toml | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 5d27497c..7e0a869c 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -89,6 +89,13 @@ "xraySwitchVersionDialog" = "Switch xray version" "xraySwitchVersionDialogDesc" = "Whether to switch the xray version to" "dontRefreshh" = "Installation is in progress, please do not refresh this page" +"logs" = "Logs" +"config" = "Config" +"backup" = "Backup" +"backupTitle" = "Backup Database" +"backupDescription" = "Remember to backup before importing a new database." +"exportDatabase" = "Download Database" +"importDatabase" = "Upload Database" [pages.inbounds] "title" = "Inbounds" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index 277db594..ce48f647 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -89,6 +89,13 @@ "xraySwitchVersionDialog" = "تغییر ورژن Xray" "xraySwitchVersionDialogDesc" = "آیا از تغییر ورژن مطمئن هستین" "dontRefreshh" = "در حال نصب ، لطفا رفرش نکنید " +"logs" = "گزارش ها" +"config" = "تنظیمات" +"backup" = "پشتیبان گیری" +"backupTitle" = "پشتیبان گیری دیتابیس" +"backupDescription" = "به یاد داشته باشید که قبل از وارد کردن یک دیتابیس جدید، نسخه پشتیبان تهیه کنید." +"exportDatabase" = "دانلود دیتابیس" +"importDatabase" = "آپلود دیتابیس" [pages.inbounds] "title" = "کاربران" diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml index f396340f..e678fc23 100644 --- a/web/translation/translate.zh_Hans.toml +++ b/web/translation/translate.zh_Hans.toml @@ -89,6 +89,13 @@ "xraySwitchVersionDialog" = "切换 xray 版本" "xraySwitchVersionDialogDesc" = "是否切换 xray 版本至" "dontRefreshh" = "安装中,请不要刷新此页面" +"logs" = "日志" +"config" = "配置" +"backup" = "备份" +"backupTitle" = "备份数据库" +"backupDescription" = "请记住在导入新数据库之前进行备份。" +"exportDatabase" = "下载数据库" +"importDatabase" = "上传数据库" [pages.inbounds] "title" = "入站列表" From 5e3c0d6eccba1123b86ff3cc88f21d96bc461353 Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami Date: Fri, 5 May 2023 22:58:57 +0430 Subject: [PATCH 5/8] update README.md --- .gitignore | 2 ++ README.md | 34 +++++++++++++++++++--------------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index c03fe283..6277cfc9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .idea .vscode tmp +backup/ bin/ dist/ x-ui-*.tar.gz @@ -10,4 +11,5 @@ x-ui-*.tar.gz main release/ access.log +error.log .cache diff --git a/README.md b/README.md index 24789665..28b66ffe 100644 --- a/README.md +++ b/README.md @@ -40,28 +40,30 @@ xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)** - Support https access panel (self-provided domain name + ssl certificate) - Support one-click SSL certificate application and automatic renewal - For more advanced configuration items, please refer to the panel +- Support export/import database from panel ## API routes - `/login` with `PUSH` user data: `{username: '', password: ''}` for login - `/xui/API/inbounds` base for following actions: -| Method | Path | Action | -| :----: | --------------------------------- | ------------------------------------------- | -| `GET` | `"/"` | Get all inbounds | -| `GET` | `"/get/:id"` | Get inbound with inbound.id | -| `POST` | `"/add"` | Add inbound | -| `POST` | `"/del/:id"` | Delete Inbound | -| `POST` | `"/update/:id"` | Update Inbound | -| `POST` | `"/addClient/"` | Add Client to inbound | -| `POST` | `"/:id/delClient/:clientId"` | Delete Client by clientId* | -| `POST` | `"/updateClient/:clientId"` | Update Client by clientId* | -| `POST` | `"/getClientTraffics/:email"` | Get Client's Traffic | -| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds | -| `POST` | `"/resetAllClientTraffics/:id"` | Reset inbound clients traffics (-1: all) | -| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) | +| Method | Path | Action | +| :----: | ------------------------------- | ----------------------------------------- | +| `GET` | `"/"` | Get all inbounds | +| `GET` | `"/get/:id"` | Get inbound with inbound.id | +| `POST` | `"/add"` | Add inbound | +| `POST` | `"/del/:id"` | Delete Inbound | +| `POST` | `"/update/:id"` | Update Inbound | +| `POST` | `"/addClient/"` | Add Client to inbound | +| `POST` | `"/:id/delClient/:clientId"` | Delete Client by clientId\* | +| `POST` | `"/updateClient/:clientId"` | Update Client by clientId\* | +| `POST` | `"/getClientTraffics/:email"` | Get Client's Traffic | +| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds | +| `POST` | `"/resetAllClientTraffics/:id"` | Reset inbound clients traffics (-1: all) | +| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) | + +\*- The field `clientId` should be filled by: -*- The field `clientId` should be filled by: - `client.id` for VMESS and VLESS - `client.password` for TROJAN - `client.email` for Shadowsocks @@ -94,7 +96,9 @@ bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.s ``` ## Install custom version + To install your desired version you can add the version to the end of install command. Example for ver `0.5.2`: + ``` bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 0.5.2 ``` From b125f1835c8b04d7235c0cc41a972f5d31f487cb Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami Date: Sat, 6 May 2023 00:22:21 +0430 Subject: [PATCH 6/8] add MigrateDB func for a single source of truth --- main.go | 3 +-- web/service/inbound.go | 11 ++++++++++- web/service/server.go | 3 +-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/main.go b/main.go index 7fdfeeb8..4d2f05d5 100644 --- a/main.go +++ b/main.go @@ -211,8 +211,7 @@ func migrateDb() { log.Fatal(err) } fmt.Println("Start migrating database...") - inboundService.MigrationRequirements() - inboundService.RemoveOrphanedTraffics() + inboundService.MigrateDB() fmt.Println("Migration done!") } diff --git a/web/service/inbound.go b/web/service/inbound.go index bde6b1f9..119e497e 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -572,6 +572,7 @@ func (s *InboundService) DisableInvalidInbounds() (int64, error) { count := result.RowsAffected return count, err } + func (s *InboundService) DisableInvalidClients() (int64, error) { db := database.GetDB() now := time.Now().Unix() * 1000 @@ -582,7 +583,8 @@ func (s *InboundService) DisableInvalidClients() (int64, error) { count := result.RowsAffected return count, err } -func (s *InboundService) RemoveOrphanedTraffics() { + +func (s *InboundService) MigrationRemoveOrphanedTraffics() { db := database.GetDB() db.Exec(` DELETE FROM client_traffics @@ -593,6 +595,7 @@ func (s *InboundService) RemoveOrphanedTraffics() { ) `) } + func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error { db := database.GetDB() @@ -611,6 +614,7 @@ func (s *InboundService) AddClientStat(inboundId int, client *model.Client) erro } return nil } + func (s *InboundService) UpdateClientStat(email string, client *model.Client) error { db := database.GetDB() @@ -917,3 +921,8 @@ func (s *InboundService) MigrationRequirements() { // Remove orphaned traffics db.Where("inbound_id = 0").Delete(xray.ClientTraffic{}) } + +func (s *InboundService) MigrateDB() { + s.MigrationRequirements() + s.MigrationRemoveOrphanedTraffics() +} diff --git a/web/service/server.go b/web/service/server.go index b4bce974..23b6da4e 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -467,8 +467,7 @@ func (s *ServerService) ImportDB(file multipart.File) error { defer os.Rename(fallbackPath, config.GetDBPath()) return common.NewErrorf("Error migrating db: %v", err) } - s.inboundService.MigrationRequirements() - s.inboundService.RemoveOrphanedTraffics() + s.inboundService.MigrateDB() // remove fallback file defer os.Remove(fallbackPath) From 9d1aa0d45fc67668659ce02562ebdc4c664cdef3 Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami Date: Sat, 6 May 2023 02:22:38 +0430 Subject: [PATCH 7/8] fix import db and always restart xray --- web/controller/server.go | 2 ++ web/service/server.go | 26 ++++++++------------------ 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/web/controller/server.go b/web/controller/server.go index 51c220c3..b3dc0ad6 100644 --- a/web/controller/server.go +++ b/web/controller/server.go @@ -152,6 +152,8 @@ func (a *ServerController) importDB(c *gin.Context) { return } defer file.Close() + // Always restart Xray before return + defer a.serverService.RestartXrayService() // Import it err = a.serverService.ImportDB(file) if err != nil { diff --git a/web/service/server.go b/web/service/server.go index 23b6da4e..92207b8b 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -409,6 +409,8 @@ func (s *ServerService) ImportDB(file multipart.File) error { // Save the file as temporary file tempPath := fmt.Sprintf("%s.temp", config.GetDBPath()) + // remove temp file before return + defer os.Remove(tempPath) tempFile, err := os.Create(tempPath) if err != nil { return common.NewErrorf("Error creating temporary db file: %v", err) @@ -418,60 +420,48 @@ func (s *ServerService) ImportDB(file multipart.File) error { // Reset the file reader to the beginning _, err = file.Seek(0, 0) if err != nil { - defer os.Remove(tempPath) return common.NewErrorf("Error resetting file reader: %v", err) } // Save temp file _, err = io.Copy(tempFile, file) if err != nil { - defer os.Remove(tempPath) return common.NewErrorf("Error saving db: %v", err) } // Check if we can init db or not err = database.InitDB(tempPath) if err != nil { - defer os.Remove(tempPath) return common.NewErrorf("Error checking db: %v", err) } - // Stop Xray if its running - if s.xrayService.IsXrayRunning() { - err := s.StopXrayService() - if err != nil { - defer os.Remove(tempPath) - return common.NewErrorf("Failed to stop Xray: %v", err) - } - } + // Stop Xray + s.StopXrayService() // Backup db for fallback fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath()) + // remove fallback file before return + defer os.Remove(fallbackPath) err = os.Rename(config.GetDBPath(), fallbackPath) if err != nil { - defer os.Remove(tempPath) return common.NewErrorf("Error backup temporary db file: %v", err) } // Move temp to DB path err = os.Rename(tempPath, config.GetDBPath()) if err != nil { - defer os.Remove(tempPath) - defer os.Rename(fallbackPath, config.GetDBPath()) + os.Rename(fallbackPath, config.GetDBPath()) return common.NewErrorf("Error moving db file: %v", err) } // Migrate DB err = database.InitDB(config.GetDBPath()) if err != nil { - defer os.Rename(fallbackPath, config.GetDBPath()) + os.Rename(fallbackPath, config.GetDBPath()) return common.NewErrorf("Error migrating db: %v", err) } s.inboundService.MigrateDB() - // remove fallback file - defer os.Remove(fallbackPath) - // Start Xray err = s.RestartXrayService() if err != nil { From 788a1b9d6b9934249823040dc077f48e79e9264b Mon Sep 17 00:00:00 2001 From: Hamidreza Ghavami Date: Sat, 6 May 2023 04:47:34 +0430 Subject: [PATCH 8/8] update ImportDB and enhancement --- web/controller/server.go | 4 ++- web/service/server.go | 60 ++++++++++++++++++++++++++++------------ 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/web/controller/server.go b/web/controller/server.go index b3dc0ad6..9e649e6c 100644 --- a/web/controller/server.go +++ b/web/controller/server.go @@ -154,13 +154,15 @@ func (a *ServerController) importDB(c *gin.Context) { defer file.Close() // Always restart Xray before return defer a.serverService.RestartXrayService() + defer func() { + a.lastGetStatusTime = time.Now() + }() // Import it err = a.serverService.ImportDB(file) if err != nil { jsonMsg(c, "", err) return } - a.lastGetStatusTime = time.Now() jsonObj(c, "Import DB", nil) } diff --git a/web/service/server.go b/web/service/server.go index 92207b8b..813498a0 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -407,23 +407,33 @@ func (s *ServerService) ImportDB(file multipart.File) error { return common.NewError("Invalid db file format") } - // Save the file as temporary file - tempPath := fmt.Sprintf("%s.temp", config.GetDBPath()) - // remove temp file before return - defer os.Remove(tempPath) - tempFile, err := os.Create(tempPath) - if err != nil { - return common.NewErrorf("Error creating temporary db file: %v", err) - } - defer tempFile.Close() - // Reset the file reader to the beginning _, err = file.Seek(0, 0) if err != nil { return common.NewErrorf("Error resetting file reader: %v", err) } - // Save temp file + // Save the file as temporary file + tempPath := fmt.Sprintf("%s.temp", config.GetDBPath()) + // Remove the existing fallback file (if any) before creating one + _, err = os.Stat(tempPath) + if err == nil { + errRemove := os.Remove(tempPath) + if errRemove != nil { + return common.NewErrorf("Error removing existing temporary db file: %v", errRemove) + } + } + // Create the temporary file + tempFile, err := os.Create(tempPath) + if err != nil { + return common.NewErrorf("Error creating temporary db file: %v", err) + } + defer tempFile.Close() + + // Remove temp file before returning + defer os.Remove(tempPath) + + // Save uploaded file to temporary file _, err = io.Copy(tempFile, file) if err != nil { return common.NewErrorf("Error saving db: %v", err) @@ -438,26 +448,42 @@ func (s *ServerService) ImportDB(file multipart.File) error { // Stop Xray s.StopXrayService() - // Backup db for fallback + // Backup the current database for fallback fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath()) - // remove fallback file before return - defer os.Remove(fallbackPath) + // Remove the existing fallback file (if any) + _, err = os.Stat(fallbackPath) + if err == nil { + errRemove := os.Remove(fallbackPath) + if errRemove != nil { + return common.NewErrorf("Error removing existing fallback db file: %v", errRemove) + } + } + // Move the current database to the fallback location err = os.Rename(config.GetDBPath(), fallbackPath) if err != nil { - return common.NewErrorf("Error backup temporary db file: %v", err) + return common.NewErrorf("Error backing up temporary db file: %v", err) } + // Remove the temporary file before returning + defer os.Remove(fallbackPath) + // Move temp to DB path err = os.Rename(tempPath, config.GetDBPath()) if err != nil { - os.Rename(fallbackPath, config.GetDBPath()) + errRename := os.Rename(fallbackPath, config.GetDBPath()) + if errRename != nil { + return common.NewErrorf("Error moving db file and restoring fallback: %v", errRename) + } return common.NewErrorf("Error moving db file: %v", err) } // Migrate DB err = database.InitDB(config.GetDBPath()) if err != nil { - os.Rename(fallbackPath, config.GetDBPath()) + errRename := os.Rename(fallbackPath, config.GetDBPath()) + if errRename != nil { + return common.NewErrorf("Error migrating db and restoring fallback: %v", errRename) + } return common.NewErrorf("Error migrating db: %v", err) } s.inboundService.MigrateDB()