backup.go 4.2 KB

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