Skip to main content

fraiseql_server/backup/
backup_config.rs

1//! Backup configuration and scheduling.
2
3use std::time::Duration;
4
5use serde::{Deserialize, Serialize};
6
7/// Backup configuration for a data store.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct BackupConfig {
10    /// Enable backups for this store
11    pub enabled: bool,
12
13    /// Backup schedule (cron expression)
14    /// Examples: "0 * * * *" (hourly), "0 0 * * *" (daily)
15    pub schedule: String,
16
17    /// Maximum number of backups to retain
18    pub retention_count: u32,
19
20    /// Backup retention duration (delete older backups)
21    pub retention_days: u32,
22
23    /// Storage backend ('local', 's3', etc.)
24    pub storage: String,
25
26    /// Storage path or S3 bucket
27    pub storage_path: String,
28
29    /// Compression enabled (gzip, zstd)
30    pub compression: Option<String>,
31
32    /// Backup timeout
33    pub timeout_secs: u64,
34
35    /// Attempt to verify backup after creation
36    pub verify_after_backup: bool,
37}
38
39impl Default for BackupConfig {
40    fn default() -> Self {
41        Self {
42            enabled:             true,
43            schedule:            "0 * * * *".to_string(), // Hourly
44            retention_count:     24,                      // Keep 24 hourly backups
45            retention_days:      30,
46            storage:             "local".to_string(),
47            storage_path:        "/var/backups/fraiseql".to_string(),
48            compression:         Some("gzip".to_string()),
49            timeout_secs:        600,
50            verify_after_backup: true,
51        }
52    }
53}
54
55impl BackupConfig {
56    /// Create config for PostgreSQL backups (hourly, high retention).
57    pub fn postgres_default() -> Self {
58        Self {
59            enabled:             true,
60            schedule:            "0 * * * *".to_string(), // Hourly
61            retention_count:     24,
62            retention_days:      30,
63            storage:             "local".to_string(),
64            storage_path:        "/var/backups/fraiseql/postgres".to_string(),
65            compression:         Some("gzip".to_string()),
66            timeout_secs:        1800,
67            verify_after_backup: true,
68        }
69    }
70
71    /// Create config for Redis backups (daily).
72    pub fn redis_default() -> Self {
73        Self {
74            enabled:             true,
75            schedule:            "0 0 * * *".to_string(), // Daily at midnight
76            retention_count:     7,
77            retention_days:      7,
78            storage:             "local".to_string(),
79            storage_path:        "/var/backups/fraiseql/redis".to_string(),
80            compression:         Some("gzip".to_string()),
81            timeout_secs:        600,
82            verify_after_backup: false,
83        }
84    }
85
86    /// Create config for ClickHouse backups (daily).
87    pub fn clickhouse_default() -> Self {
88        Self {
89            enabled:             true,
90            schedule:            "0 1 * * *".to_string(), // Daily at 1 AM
91            retention_count:     7,
92            retention_days:      7,
93            storage:             "local".to_string(),
94            storage_path:        "/var/backups/fraiseql/clickhouse".to_string(),
95            compression:         None, // ClickHouse compression built-in
96            timeout_secs:        3600,
97            verify_after_backup: false,
98        }
99    }
100
101    /// Create config for Elasticsearch backups (daily).
102    pub fn elasticsearch_default() -> Self {
103        Self {
104            enabled:             true,
105            schedule:            "0 2 * * *".to_string(), // Daily at 2 AM
106            retention_count:     7,
107            retention_days:      7,
108            storage:             "local".to_string(),
109            storage_path:        "/var/backups/fraiseql/elasticsearch".to_string(),
110            compression:         None, // Elasticsearch snapshot built-in compression
111            timeout_secs:        3600,
112            verify_after_backup: true,
113        }
114    }
115
116    /// Get timeout as Duration.
117    pub fn timeout(&self) -> Duration {
118        Duration::from_secs(self.timeout_secs)
119    }
120}
121
122/// Backup status report.
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct BackupStatus {
125    /// Name of the data store (postgres, redis, etc.)
126    pub store_name: String,
127
128    /// Whether backups are enabled
129    pub enabled: bool,
130
131    /// Last successful backup timestamp (Unix seconds)
132    pub last_successful_backup: Option<i64>,
133
134    /// Size of last backup in bytes
135    pub last_backup_size: Option<u64>,
136
137    /// Number of available backups
138    pub available_backups: u32,
139
140    /// Last error message (if any)
141    pub last_error: Option<String>,
142
143    /// Health status: "healthy", "warning", "error"
144    pub status: String,
145}
146
147/// Recovery configuration.
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct RecoveryConfig {
150    /// Data store to recover (postgres, redis, etc.)
151    pub store_name: String,
152
153    /// Backup timestamp to restore from (Unix seconds)
154    pub backup_timestamp: i64,
155
156    /// Verify data after recovery
157    pub verify_after_recovery: bool,
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn test_postgres_default_config() {
166        let config = BackupConfig::postgres_default();
167        assert!(config.enabled);
168        assert_eq!(config.schedule, "0 * * * *");
169        assert_eq!(config.retention_count, 24);
170    }
171
172    #[test]
173    fn test_redis_default_config() {
174        let config = BackupConfig::redis_default();
175        assert!(config.enabled);
176        assert_eq!(config.schedule, "0 0 * * *");
177        assert_eq!(config.retention_count, 7);
178    }
179
180    #[test]
181    fn test_clickhouse_default_config() {
182        let config = BackupConfig::clickhouse_default();
183        assert!(config.enabled);
184        assert_eq!(config.schedule, "0 1 * * *");
185    }
186
187    #[test]
188    fn test_elasticsearch_default_config() {
189        let config = BackupConfig::elasticsearch_default();
190        assert!(config.enabled);
191        assert_eq!(config.schedule, "0 2 * * *");
192    }
193
194    #[test]
195    fn test_timeout_conversion() {
196        let config = BackupConfig::postgres_default();
197        let duration = config.timeout();
198        assert_eq!(duration.as_secs(), 1800);
199    }
200}