backup.go 7.1 KB

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