1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
//! Rustic Config
//!
//! See instructions in `commands.rs` to specify the path to your
//! application's configuration file and/or command-line options
//! for specifying it.
pub(crate) mod progress_options;
use std::{collections::HashMap, path::PathBuf};
use directories::ProjectDirs;
use merge::Merge;
use abscissa_core::config::Config;
use abscissa_core::path::AbsPathBuf;
use abscissa_core::FrameworkError;
use clap::Parser;
use itertools::Itertools;
use rustic_core::RepositoryOptions;
use serde::{Deserialize, Serialize};
use crate::{
commands::{backup::BackupCmd, copy::Targets, forget::ForgetOptions},
config::progress_options::ProgressOptions,
filtering::SnapshotFilter,
};
/// Rustic Configuration
///
/// Further documentation can be found [here](https://github.com/rustic-rs/rustic/blob/main/config/README.md).
///
/// # Example
// TODO: add example
#[derive(Clone, Default, Debug, Parser, Deserialize, Merge)]
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
pub struct RusticConfig {
/// Global options
#[clap(flatten, next_help_heading = "Global options")]
pub global: GlobalOptions,
/// Repository options
#[clap(flatten, next_help_heading = "Repository options")]
pub repository: RepositoryOptions,
/// Snapshot filter options
#[clap(flatten, next_help_heading = "Snapshot filter options")]
pub snapshot_filter: SnapshotFilter,
/// Backup options
#[clap(skip)]
pub backup: BackupCmd,
/// Copy options
#[clap(skip)]
pub copy: Targets,
/// Forget options
#[clap(skip)]
pub forget: ForgetOptions,
}
impl RusticConfig {
/// Merge a profile into the current config
///
/// # Arguments
///
/// * `profile` - name of the profile to merge
///
// TODO!: Explain more
pub fn merge_profile(&mut self, profile: &str) -> Result<(), FrameworkError> {
let profile_filename = profile.to_string() + ".toml";
let paths = get_config_paths(&profile_filename);
if let Some(path) = paths.iter().find(|path| path.exists()) {
// TODO: This should be log::info! - however, the logging config
// can be stored in the config file and is needed to initialize the logger
eprintln!("using config {}", path.display());
let mut config = Self::load_toml_file(AbsPathBuf::canonicalize(path)?)?;
// if "use_profile" is defined in config file, merge the referenced profiles first
for profile in &config.global.use_profile.clone() {
config.merge_profile(profile)?;
}
self.merge(config);
} else {
let paths_string = paths.iter().map(|path| path.display()).join(", ");
// TODO: This should be log::warn! - however, the logging config
// can be stored in the config file and is needed to initialize the logger
eprintln!(
"using no config file, none of these exist: {}",
&paths_string
);
};
Ok(())
}
}
/// Global options
///
/// These options are available for all commands.
#[derive(Default, Debug, Parser, Clone, Deserialize, Serialize, Merge)]
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
pub struct GlobalOptions {
/// Config profile to use. This parses the file `<PROFILE>.toml` in the config directory.
/// [default: "rustic"]
#[clap(
short = 'P',
long,
global = true,
value_name = "PROFILE",
env = "RUSTIC_USE_PROFILE"
)]
#[merge(strategy = merge::vec::append)]
pub use_profile: Vec<String>,
/// Only show what would be done without modifying anything. Does not affect read-only commands.
#[clap(long, short = 'n', global = true, env = "RUSTIC_DRY_RUN")]
#[merge(strategy = merge::bool::overwrite_false)]
pub dry_run: bool,
/// Use this log level [default: info]
#[clap(long, global = true, env = "RUSTIC_LOG_LEVEL")]
pub log_level: Option<String>,
/// Write log messages to the given file instead of printing them.
///
/// # Note
///
/// Warnings and errors are still additionally printed unless they are ignored by `--log-level`
#[clap(long, global = true, env = "RUSTIC_LOG_FILE", value_name = "LOGFILE")]
pub log_file: Option<PathBuf>,
/// Settings to customize progress bars
#[clap(flatten)]
#[serde(flatten)]
pub progress_options: ProgressOptions,
/// List of environment variables to set (only in config file)
#[clap(skip)]
#[merge(strategy = extend)]
pub env: HashMap<String, String>,
}
/// Extend the contents of a [`HashMap`] with the contents of another
/// [`HashMap`] with the same key and value types.
fn extend(left: &mut HashMap<String, String>, right: HashMap<String, String>) {
left.extend(right);
}
/// Get the paths to the config file
///
/// # Arguments
///
/// * `filename` - name of the config file
///
/// # Returns
///
/// A vector of [`PathBuf`]s to the config files
fn get_config_paths(filename: &str) -> Vec<PathBuf> {
[
ProjectDirs::from("", "", "rustic")
.map(|project_dirs| project_dirs.config_dir().to_path_buf()),
get_global_config_path(),
Some(PathBuf::from(".")),
]
.into_iter()
.filter_map(|path| {
path.map(|mut p| {
p.push(filename);
p
})
})
.collect()
}
/// Get the path to the global config directory on Windows.
///
/// # Returns
///
/// The path to the global config directory on Windows.
/// If the environment variable `PROGRAMDATA` is not set, `None` is returned.
#[cfg(target_os = "windows")]
fn get_global_config_path() -> Option<PathBuf> {
std::env::var_os("PROGRAMDATA").map(|program_data| {
let mut path = PathBuf::from(program_data);
path.push(r"rustic\config");
path
})
}
/// Get the path to the global config directory on ios and wasm targets.
///
/// # Returns
///
/// `None` is returned.
#[cfg(any(target_os = "ios", target_arch = "wasm32"))]
fn get_global_config_path() -> Option<PathBuf> {
None
}
/// Get the path to the global config directory on non-Windows,
/// non-iOS, non-wasm targets.
///
/// # Returns
///
/// "/etc/rustic" is returned.
#[cfg(not(any(target_os = "windows", target_os = "ios", target_arch = "wasm32")))]
fn get_global_config_path() -> Option<PathBuf> {
Some(PathBuf::from("/etc/rustic"))
}