backup.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. package backup
  2. import (
  3. "bytes"
  4. "encoding/base64"
  5. "fmt"
  6. "os"
  7. "path/filepath"
  8. "time"
  9. "github.com/0xJacky/Nginx-UI/internal/version"
  10. "github.com/uozi-tech/cosy"
  11. "github.com/uozi-tech/cosy/logger"
  12. )
  13. // Directory and file names
  14. const (
  15. BackupDirPrefix = "nginx-ui-backup-"
  16. NginxUIDir = "nginx-ui"
  17. NginxDir = "nginx"
  18. HashInfoFile = "hash_info.txt"
  19. NginxUIZipName = "nginx-ui.zip"
  20. NginxZipName = "nginx.zip"
  21. )
  22. // BackupResult contains the results of a backup operation
  23. type BackupResult struct {
  24. BackupContent []byte `json:"-"` // Backup content as byte array
  25. BackupName string `json:"name"` // Backup file name
  26. AESKey string `json:"aes_key"` // Base64 encoded AES key
  27. AESIv string `json:"aes_iv"` // Base64 encoded AES IV
  28. }
  29. // HashInfo contains hash information for verification
  30. type HashInfo struct {
  31. NginxUIHash string `json:"nginx_ui_hash"`
  32. NginxHash string `json:"nginx_hash"`
  33. Timestamp string `json:"timestamp"`
  34. Version string `json:"version"`
  35. }
  36. // Backup creates a backup of nginx-ui configuration and database files,
  37. // and nginx configuration directory, compressed into an encrypted archive
  38. func Backup() (BackupResult, error) {
  39. // Generate timestamps for filenames
  40. timestamp := time.Now().Format("20060102-150405")
  41. backupName := fmt.Sprintf("backup-%s.zip", timestamp)
  42. // Generate AES key and IV
  43. key, err := GenerateAESKey()
  44. if err != nil {
  45. return BackupResult{}, cosy.WrapErrorWithParams(ErrGenerateAESKey, err.Error())
  46. }
  47. iv, err := GenerateIV()
  48. if err != nil {
  49. return BackupResult{}, cosy.WrapErrorWithParams(ErrGenerateIV, err.Error())
  50. }
  51. // Create temporary directory for files to be archived
  52. tempDir, err := os.MkdirTemp("", "nginx-ui-backup-*")
  53. if err != nil {
  54. return BackupResult{}, cosy.WrapErrorWithParams(ErrCreateTempDir, err.Error())
  55. }
  56. defer os.RemoveAll(tempDir)
  57. // Create directories in temp
  58. nginxUITempDir := filepath.Join(tempDir, NginxUIDir)
  59. nginxTempDir := filepath.Join(tempDir, NginxDir)
  60. if err := os.MkdirAll(nginxUITempDir, 0755); err != nil {
  61. return BackupResult{}, cosy.WrapErrorWithParams(ErrCreateTempSubDir, err.Error())
  62. }
  63. if err := os.MkdirAll(nginxTempDir, 0755); err != nil {
  64. return BackupResult{}, cosy.WrapErrorWithParams(ErrCreateTempSubDir, err.Error())
  65. }
  66. // Backup nginx-ui config and database to a directory
  67. if err := backupNginxUIFiles(nginxUITempDir); err != nil {
  68. return BackupResult{}, cosy.WrapErrorWithParams(ErrBackupNginxUI, err.Error())
  69. }
  70. // Backup nginx configs to a directory
  71. if err := backupNginxFiles(nginxTempDir); err != nil {
  72. return BackupResult{}, cosy.WrapErrorWithParams(ErrBackupNginx, err.Error())
  73. }
  74. // Create individual zip files for nginx-ui and nginx directories
  75. nginxUIZipPath := filepath.Join(tempDir, NginxUIZipName)
  76. nginxZipPath := filepath.Join(tempDir, NginxZipName)
  77. // Create zip archives for each directory
  78. if err := createZipArchive(nginxUIZipPath, nginxUITempDir); err != nil {
  79. return BackupResult{}, cosy.WrapErrorWithParams(ErrCreateZipArchive, err.Error())
  80. }
  81. if err := createZipArchive(nginxZipPath, nginxTempDir); err != nil {
  82. return BackupResult{}, cosy.WrapErrorWithParams(ErrCreateZipArchive, err.Error())
  83. }
  84. // Calculate hashes for the zip files
  85. nginxUIHash, err := calculateFileHash(nginxUIZipPath)
  86. if err != nil {
  87. return BackupResult{}, cosy.WrapErrorWithParams(ErrCalculateHash, err.Error())
  88. }
  89. nginxHash, err := calculateFileHash(nginxZipPath)
  90. if err != nil {
  91. return BackupResult{}, cosy.WrapErrorWithParams(ErrCalculateHash, err.Error())
  92. }
  93. // Get current version information
  94. versionInfo := version.GetVersionInfo()
  95. // Create hash info file
  96. hashInfo := HashInfo{
  97. NginxUIHash: nginxUIHash,
  98. NginxHash: nginxHash,
  99. Timestamp: timestamp,
  100. Version: versionInfo.Version,
  101. }
  102. // Write hash info to file
  103. hashInfoPath := filepath.Join(tempDir, HashInfoFile)
  104. if err := writeHashInfoFile(hashInfoPath, hashInfo); err != nil {
  105. return BackupResult{}, cosy.WrapErrorWithParams(ErrCreateHashFile, err.Error())
  106. }
  107. // Encrypt the individual files
  108. if err := encryptFile(hashInfoPath, key, iv); err != nil {
  109. return BackupResult{}, cosy.WrapErrorWithParams(ErrEncryptFile, HashInfoFile)
  110. }
  111. if err := encryptFile(nginxUIZipPath, key, iv); err != nil {
  112. return BackupResult{}, cosy.WrapErrorWithParams(ErrEncryptNginxUIDir, err.Error())
  113. }
  114. if err := encryptFile(nginxZipPath, key, iv); err != nil {
  115. return BackupResult{}, cosy.WrapErrorWithParams(ErrEncryptNginxDir, err.Error())
  116. }
  117. // Remove the original directories to avoid duplicating them in the final archive
  118. if err := os.RemoveAll(nginxUITempDir); err != nil {
  119. return BackupResult{}, cosy.WrapErrorWithParams(ErrCleanupTempDir, err.Error())
  120. }
  121. if err := os.RemoveAll(nginxTempDir); err != nil {
  122. return BackupResult{}, cosy.WrapErrorWithParams(ErrCleanupTempDir, err.Error())
  123. }
  124. // Create final zip file to memory buffer
  125. var buffer bytes.Buffer
  126. if err := createZipArchiveToBuffer(&buffer, tempDir); err != nil {
  127. return BackupResult{}, cosy.WrapErrorWithParams(ErrCreateZipArchive, err.Error())
  128. }
  129. // Convert AES key and IV to base64 encoded strings
  130. keyBase64 := base64.StdEncoding.EncodeToString(key)
  131. ivBase64 := base64.StdEncoding.EncodeToString(iv)
  132. // Return result
  133. result := BackupResult{
  134. BackupContent: buffer.Bytes(),
  135. BackupName: backupName,
  136. AESKey: keyBase64,
  137. AESIv: ivBase64,
  138. }
  139. logger.Infof("Backup created successfully: %s", backupName)
  140. return result, nil
  141. }