backup.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. package system
  2. import (
  3. "bytes"
  4. "encoding/base64"
  5. "net/http"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "time"
  10. "github.com/0xJacky/Nginx-UI/api"
  11. "github.com/0xJacky/Nginx-UI/internal/backup"
  12. "github.com/gin-gonic/gin"
  13. "github.com/jpillora/overseer"
  14. "github.com/uozi-tech/cosy"
  15. )
  16. // RestoreResponse contains the response data for restore operation
  17. type RestoreResponse struct {
  18. NginxUIRestored bool `json:"nginx_ui_restored"`
  19. NginxRestored bool `json:"nginx_restored"`
  20. HashMatch bool `json:"hash_match"`
  21. }
  22. // CreateBackup creates a backup of nginx-ui and nginx configurations
  23. // and sends files directly for download
  24. func CreateBackup(c *gin.Context) {
  25. result, err := backup.Backup()
  26. if err != nil {
  27. api.ErrHandler(c, err)
  28. return
  29. }
  30. // Concatenate Key and IV
  31. securityToken := result.AESKey + ":" + result.AESIv
  32. // Prepare response content
  33. reader := bytes.NewReader(result.BackupContent)
  34. modTime := time.Now()
  35. // Set HTTP headers for file download
  36. fileName := result.BackupName
  37. c.Header("Content-Description", "File Transfer")
  38. c.Header("Content-Type", "application/zip")
  39. c.Header("Content-Disposition", "attachment; filename="+fileName)
  40. c.Header("Content-Transfer-Encoding", "binary")
  41. c.Header("X-Backup-Security", securityToken) // Pass security token in header
  42. c.Header("Expires", "0")
  43. c.Header("Cache-Control", "must-revalidate")
  44. c.Header("Pragma", "public")
  45. // Send file content
  46. http.ServeContent(c.Writer, c.Request, fileName, modTime, reader)
  47. }
  48. // RestoreBackup restores from uploaded backup and security info
  49. func RestoreBackup(c *gin.Context) {
  50. // Get restore options
  51. restoreNginx := c.PostForm("restore_nginx") == "true"
  52. restoreNginxUI := c.PostForm("restore_nginx_ui") == "true"
  53. verifyHash := c.PostForm("verify_hash") == "true"
  54. securityToken := c.PostForm("security_token") // Get concatenated key and IV
  55. // Get backup file
  56. backupFile, err := c.FormFile("backup_file")
  57. if err != nil {
  58. api.ErrHandler(c, cosy.WrapErrorWithParams(backup.ErrBackupFileNotFound, err.Error()))
  59. return
  60. }
  61. // Validate security token
  62. if securityToken == "" {
  63. api.ErrHandler(c, backup.ErrInvalidSecurityToken)
  64. return
  65. }
  66. // Split security token to get Key and IV
  67. parts := strings.Split(securityToken, ":")
  68. if len(parts) != 2 {
  69. api.ErrHandler(c, backup.ErrInvalidSecurityToken)
  70. return
  71. }
  72. aesKey := parts[0]
  73. aesIv := parts[1]
  74. // Decode Key and IV from base64
  75. key, err := base64.StdEncoding.DecodeString(aesKey)
  76. if err != nil {
  77. api.ErrHandler(c, cosy.WrapErrorWithParams(backup.ErrInvalidAESKey, err.Error()))
  78. return
  79. }
  80. iv, err := base64.StdEncoding.DecodeString(aesIv)
  81. if err != nil {
  82. api.ErrHandler(c, cosy.WrapErrorWithParams(backup.ErrInvalidAESIV, err.Error()))
  83. return
  84. }
  85. // Create temporary directory for files
  86. tempDir, err := os.MkdirTemp("", "nginx-ui-restore-upload-*")
  87. if err != nil {
  88. api.ErrHandler(c, cosy.WrapErrorWithParams(backup.ErrCreateTempDir, err.Error()))
  89. return
  90. }
  91. defer os.RemoveAll(tempDir)
  92. // Save backup file
  93. backupPath := filepath.Join(tempDir, backupFile.Filename)
  94. if err := c.SaveUploadedFile(backupFile, backupPath); err != nil {
  95. api.ErrHandler(c, cosy.WrapErrorWithParams(backup.ErrCreateBackupFile, err.Error()))
  96. return
  97. }
  98. // Create temporary directory for restore operation
  99. restoreDir, err := os.MkdirTemp("", "nginx-ui-restore-*")
  100. if err != nil {
  101. api.ErrHandler(c, cosy.WrapErrorWithParams(backup.ErrCreateRestoreDir, err.Error()))
  102. return
  103. }
  104. // Set restore options
  105. options := backup.RestoreOptions{
  106. BackupPath: backupPath,
  107. AESKey: key,
  108. AESIv: iv,
  109. RestoreDir: restoreDir,
  110. RestoreNginx: restoreNginx,
  111. RestoreNginxUI: restoreNginxUI,
  112. VerifyHash: verifyHash,
  113. }
  114. // Perform restore
  115. result, err := backup.Restore(options)
  116. if err != nil {
  117. // Clean up temporary directory on error
  118. os.RemoveAll(restoreDir)
  119. api.ErrHandler(c, err)
  120. return
  121. }
  122. // If not actually restoring anything, clean up directory to avoid disk space waste
  123. if !restoreNginx && !restoreNginxUI {
  124. defer os.RemoveAll(restoreDir)
  125. }
  126. if restoreNginxUI {
  127. go func() {
  128. time.Sleep(3 * time.Second)
  129. // gracefully restart
  130. overseer.Restart()
  131. }()
  132. }
  133. c.JSON(http.StatusOK, RestoreResponse{
  134. NginxUIRestored: result.NginxUIRestored,
  135. NginxRestored: result.NginxRestored,
  136. HashMatch: result.HashMatch,
  137. })
  138. }