backup.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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. // Constants for backup directory and file naming conventions
  14. const (
  15. NginxUIDir = "nginx-ui" // Directory name for Nginx UI files in backup
  16. NginxDir = "nginx" // Directory name for Nginx config files in backup
  17. HashInfoFile = "hash_info.txt" // Filename for hash verification information
  18. NginxUIZipName = "nginx-ui.zip" // Filename for Nginx UI archive within backup
  19. NginxZipName = "nginx.zip" // Filename for Nginx config archive within backup
  20. )
  21. // Result contains the complete results of a backup operation.
  22. // This structure encapsulates all data needed to restore or verify a backup.
  23. type Result struct {
  24. BackupContent []byte `json:"-"` // Encrypted backup content as byte array (excluded from JSON)
  25. BackupName string `json:"name"` // Generated backup filename with timestamp
  26. AESKey string `json:"aes_key"` // Base64 encoded AES encryption key
  27. AESIv string `json:"aes_iv"` // Base64 encoded AES initialization vector
  28. }
  29. // HashInfo contains cryptographic hash information for backup verification.
  30. // This structure ensures backup integrity and provides metadata for restoration.
  31. type HashInfo struct {
  32. NginxUIHash string `json:"nginx_ui_hash"` // SHA-256 hash of Nginx UI files archive
  33. NginxHash string `json:"nginx_hash"` // SHA-256 hash of Nginx config files archive
  34. Timestamp string `json:"timestamp"` // Backup creation timestamp
  35. Version string `json:"version"` // Nginx UI version at backup time
  36. }
  37. // Backup creates a comprehensive backup of nginx-ui configuration, database files,
  38. // and nginx configuration directory. The backup is compressed and encrypted for security.
  39. //
  40. // The backup process includes:
  41. // 1. Creating temporary directories for staging files
  42. // 2. Copying Nginx UI configuration and database files
  43. // 3. Copying Nginx configuration directory
  44. // 4. Creating individual ZIP archives for each component
  45. // 5. Calculating cryptographic hashes for integrity verification
  46. // 6. Encrypting all components with AES encryption
  47. // 7. Creating final encrypted archive in memory
  48. //
  49. // Returns:
  50. // - BackupResult: Complete backup data including encrypted content and keys
  51. // - error: CosyError if any step of the backup process fails
  52. func Backup() (Result, error) {
  53. // Generate timestamp for unique backup identification
  54. timestamp := time.Now().Format("20060102-150405")
  55. backupName := fmt.Sprintf("backup-%s.zip", timestamp)
  56. // Generate cryptographic keys for AES encryption
  57. key, err := GenerateAESKey()
  58. if err != nil {
  59. return Result{}, cosy.WrapErrorWithParams(ErrGenerateAESKey, err.Error())
  60. }
  61. iv, err := GenerateIV()
  62. if err != nil {
  63. return Result{}, cosy.WrapErrorWithParams(ErrGenerateIV, err.Error())
  64. }
  65. // Create temporary directory for staging backup files
  66. tempDir, err := os.MkdirTemp("", "nginx-ui-backup-*")
  67. if err != nil {
  68. return Result{}, cosy.WrapErrorWithParams(ErrCreateTempDir, err.Error())
  69. }
  70. defer os.RemoveAll(tempDir) // Ensure cleanup of temporary files
  71. // Create subdirectories for organizing backup components
  72. nginxUITempDir := filepath.Join(tempDir, NginxUIDir)
  73. nginxTempDir := filepath.Join(tempDir, NginxDir)
  74. if err := os.MkdirAll(nginxUITempDir, 0755); err != nil {
  75. return Result{}, cosy.WrapErrorWithParams(ErrCreateTempSubDir, err.Error())
  76. }
  77. if err := os.MkdirAll(nginxTempDir, 0755); err != nil {
  78. return Result{}, cosy.WrapErrorWithParams(ErrCreateTempSubDir, err.Error())
  79. }
  80. // Stage Nginx UI configuration and database files
  81. if err := backupNginxUIFiles(nginxUITempDir); err != nil {
  82. return Result{}, cosy.WrapErrorWithParams(ErrBackupNginxUI, err.Error())
  83. }
  84. // Stage Nginx configuration files
  85. if err := backupNginxFiles(nginxTempDir); err != nil {
  86. return Result{}, cosy.WrapErrorWithParams(ErrBackupNginx, err.Error())
  87. }
  88. // Create individual ZIP archives for each component
  89. nginxUIZipPath := filepath.Join(tempDir, NginxUIZipName)
  90. nginxZipPath := filepath.Join(tempDir, NginxZipName)
  91. // Compress Nginx UI files into archive
  92. if err := createZipArchive(nginxUIZipPath, nginxUITempDir); err != nil {
  93. return Result{}, cosy.WrapErrorWithParams(ErrCreateZipArchive, err.Error())
  94. }
  95. // Compress Nginx configuration files into archive
  96. if err := createZipArchive(nginxZipPath, nginxTempDir); err != nil {
  97. return Result{}, cosy.WrapErrorWithParams(ErrCreateZipArchive, err.Error())
  98. }
  99. // Calculate cryptographic hashes for integrity verification
  100. nginxUIHash, err := calculateFileHash(nginxUIZipPath)
  101. if err != nil {
  102. return Result{}, cosy.WrapErrorWithParams(ErrCalculateHash, err.Error())
  103. }
  104. nginxHash, err := calculateFileHash(nginxZipPath)
  105. if err != nil {
  106. return Result{}, cosy.WrapErrorWithParams(ErrCalculateHash, err.Error())
  107. }
  108. // Gather version information for backup metadata
  109. versionInfo := version.GetVersionInfo()
  110. // Create hash verification file with metadata
  111. hashInfo := HashInfo{
  112. NginxUIHash: nginxUIHash,
  113. NginxHash: nginxHash,
  114. Timestamp: timestamp,
  115. Version: versionInfo.Version,
  116. }
  117. // Write hash information to verification file
  118. hashInfoPath := filepath.Join(tempDir, HashInfoFile)
  119. if err := writeHashInfoFile(hashInfoPath, hashInfo); err != nil {
  120. return Result{}, cosy.WrapErrorWithParams(ErrCreateHashFile, err.Error())
  121. }
  122. // Encrypt all backup components for security
  123. if err := encryptFile(hashInfoPath, key, iv); err != nil {
  124. return Result{}, cosy.WrapErrorWithParams(ErrEncryptFile, HashInfoFile)
  125. }
  126. if err := encryptFile(nginxUIZipPath, key, iv); err != nil {
  127. return Result{}, cosy.WrapErrorWithParams(ErrEncryptNginxUIDir, err.Error())
  128. }
  129. if err := encryptFile(nginxZipPath, key, iv); err != nil {
  130. return Result{}, cosy.WrapErrorWithParams(ErrEncryptNginxDir, err.Error())
  131. }
  132. // Clean up unencrypted directories to prevent duplication in final archive
  133. if err := os.RemoveAll(nginxUITempDir); err != nil {
  134. return Result{}, cosy.WrapErrorWithParams(ErrCleanupTempDir, err.Error())
  135. }
  136. if err := os.RemoveAll(nginxTempDir); err != nil {
  137. return Result{}, cosy.WrapErrorWithParams(ErrCleanupTempDir, err.Error())
  138. }
  139. // Create final encrypted backup archive in memory
  140. var buffer bytes.Buffer
  141. if err := createZipArchiveToBuffer(&buffer, tempDir); err != nil {
  142. return Result{}, cosy.WrapErrorWithParams(ErrCreateZipArchive, err.Error())
  143. }
  144. // Encode encryption keys as base64 for safe transmission/storage
  145. keyBase64 := base64.StdEncoding.EncodeToString(key)
  146. ivBase64 := base64.StdEncoding.EncodeToString(iv)
  147. // Assemble final backup result
  148. result := Result{
  149. BackupContent: buffer.Bytes(),
  150. BackupName: backupName,
  151. AESKey: keyBase64,
  152. AESIv: ivBase64,
  153. }
  154. logger.Infof("Backup created successfully: %s (size: %d bytes)", backupName, len(buffer.Bytes()))
  155. return result, nil
  156. }