Skip to main content

tuwunel_database/engine/
backup.rs

1use std::{ffi::OsString, path::PathBuf};
2
3use rocksdb::backup::{BackupEngine, BackupEngineOptions};
4use tuwunel_core::{
5	Err, Result, error, implement, info, utils::time::rfc2822_from_seconds, warn,
6};
7
8use super::Engine;
9use crate::util::map_err;
10
11#[implement(Engine)]
12#[tracing::instrument(skip(self))]
13pub fn backup(&self) -> Result {
14	let mut engine = self.backup_engine()?;
15	let config = &self.ctx.server.config;
16	if config.database_backups_to_keep > 0 {
17		let flush = !self.is_read_only();
18		engine
19			.create_new_backup_flush(&self.db, flush)
20			.map_err(map_err)?;
21
22		let engine_info = engine.get_backup_info();
23		let info = &engine_info
24			.last()
25			.expect("backup engine info is not empty");
26		info!(
27			"Created database backup #{} using {} bytes in {} files",
28			info.backup_id, info.size, info.num_files,
29		);
30	}
31
32	if config.database_backups_to_keep >= 0 {
33		let keep = u32::try_from(config.database_backups_to_keep)?;
34		if let Err(e) = engine.purge_old_backups(keep.try_into()?) {
35			error!("Failed to purge old backup: {e:?}");
36		}
37	}
38
39	if config.database_backups_to_keep == 0 {
40		warn!("Configuration item `database_backups_to_keep` is set to 0.");
41	}
42
43	Ok(())
44}
45
46#[implement(Engine)]
47pub fn backup_list(&self) -> Result<impl Iterator<Item = String> + Send> {
48	let info = self.backup_engine()?.get_backup_info();
49
50	if info.is_empty() {
51		return Err!("No backups found.");
52	}
53
54	let list = info.into_iter().map(|info| {
55		format!(
56			"#{} {}: {} bytes, {} files",
57			info.backup_id,
58			rfc2822_from_seconds(info.timestamp),
59			info.size,
60			info.num_files,
61		)
62	});
63
64	Ok(list)
65}
66
67#[implement(Engine)]
68pub fn backup_count(&self) -> Result<usize> {
69	let info = self.backup_engine()?.get_backup_info();
70
71	Ok(info.len())
72}
73
74#[implement(Engine)]
75fn backup_engine(&self) -> Result<BackupEngine> {
76	let path = self.backup_path()?;
77	let options = BackupEngineOptions::new(path).map_err(map_err)?;
78	BackupEngine::open(&options, &*self.ctx.env.lock()?).map_err(map_err)
79}
80
81#[implement(Engine)]
82fn backup_path(&self) -> Result<OsString> {
83	let path = self
84		.ctx
85		.server
86		.config
87		.database_backup_path
88		.clone()
89		.map(PathBuf::into_os_string)
90		.unwrap_or_default();
91
92	if path.is_empty() {
93		return Err!(Config("database_backup_path", "Configure path to enable backups"));
94	}
95
96	Ok(path)
97}