pub(crate) mod backup;
pub(crate) mod cat;
pub(crate) mod check;
pub(crate) mod completions;
pub(crate) mod config;
pub(crate) mod copy;
pub(crate) mod diff;
pub(crate) mod docs;
pub(crate) mod dump;
pub(crate) mod find;
pub(crate) mod forget;
pub(crate) mod init;
pub(crate) mod key;
pub(crate) mod list;
pub(crate) mod ls;
pub(crate) mod merge;
#[cfg(feature = "mount")]
pub(crate) mod mount;
pub(crate) mod prune;
pub(crate) mod repair;
pub(crate) mod repoinfo;
pub(crate) mod restore;
pub(crate) mod rewrite;
pub(crate) mod self_update;
pub(crate) mod show_config;
pub(crate) mod snapshots;
pub(crate) mod tag;
#[cfg(feature = "tui")]
pub(crate) mod tui;
#[cfg(feature = "webdav")]
pub(crate) mod webdav;
use std::fmt::Debug;
use std::path::PathBuf;
use std::sync::mpsc::channel;
#[cfg(feature = "mount")]
use crate::commands::mount::MountCmd;
#[cfg(feature = "webdav")]
use crate::commands::webdav::WebDavCmd;
use crate::{
Application, RUSTIC_APP,
commands::{
backup::BackupCmd, cat::CatCmd, check::CheckCmd, completions::CompletionsCmd,
config::ConfigCmd, copy::CopyCmd, diff::DiffCmd, docs::DocsCmd, dump::DumpCmd,
forget::ForgetCmd, init::InitCmd, key::KeyCmd, list::ListCmd, ls::LsCmd, merge::MergeCmd,
prune::PruneCmd, repair::RepairCmd, repoinfo::RepoInfoCmd, restore::RestoreCmd,
rewrite::RewriteCmd, self_update::SelfUpdateCmd, show_config::ShowConfigCmd,
snapshots::SnapshotCmd, tag::TagCmd,
},
config::RusticConfig,
};
use abscissa_core::{
Command, Configurable, FrameworkError, FrameworkErrorKind, Runnable, Shutdown, config::Override,
};
use anyhow::Result;
use clap::builder::{
Styles,
styling::{AnsiColor, Effects},
};
use convert_case::{Case, Casing};
use human_panic::setup_panic;
use log::{Level, info, log};
use reqwest::Url;
use self::find::FindCmd;
#[derive(clap::Parser, Command, Debug, Runnable)]
enum RusticCmd {
Backup(Box<BackupCmd>),
Cat(Box<CatCmd>),
Config(Box<ConfigCmd>),
Completions(Box<CompletionsCmd>),
Check(Box<CheckCmd>),
Copy(Box<CopyCmd>),
Diff(Box<DiffCmd>),
Docs(Box<DocsCmd>),
Dump(Box<DumpCmd>),
Find(Box<FindCmd>),
Forget(Box<ForgetCmd>),
Init(Box<InitCmd>),
Key(Box<KeyCmd>),
List(Box<ListCmd>),
#[cfg(feature = "mount")]
Mount(Box<MountCmd>),
Ls(Box<LsCmd>),
Merge(Box<MergeCmd>),
Snapshots(Box<SnapshotCmd>),
ShowConfig(Box<ShowConfigCmd>),
#[cfg_attr(not(feature = "self-update"), clap(hide = true))]
SelfUpdate(Box<SelfUpdateCmd>),
Prune(Box<PruneCmd>),
Restore(Box<RestoreCmd>),
Rewrite(Box<RewriteCmd>),
Repair(Box<RepairCmd>),
Repoinfo(Box<RepoInfoCmd>),
Tag(Box<TagCmd>),
#[cfg(feature = "webdav")]
Webdav(Box<WebDavCmd>),
}
fn styles() -> Styles {
Styles::styled()
.header(AnsiColor::Red.on_default() | Effects::BOLD)
.usage(AnsiColor::Red.on_default() | Effects::BOLD)
.literal(AnsiColor::Blue.on_default() | Effects::BOLD)
.placeholder(AnsiColor::Green.on_default())
}
fn version() -> &'static str {
option_env!("PROJECT_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"))
}
#[derive(clap::Parser, Command, Debug)]
#[command(author, about, name="rustic", styles=styles(), version=version())]
pub struct EntryPoint {
#[command(flatten)]
pub config: RusticConfig,
#[command(subcommand)]
commands: RusticCmd,
}
impl Runnable for EntryPoint {
fn run(&self) {
setup_panic!();
let (tx, rx) = channel();
ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel."))
.expect("Error setting Ctrl-C handler");
_ = std::thread::spawn(move || {
rx.recv().expect("Could not receive from channel.");
info!("Ctrl-C received, shutting down...");
RUSTIC_APP.shutdown(Shutdown::Graceful)
});
self.commands.run();
RUSTIC_APP.shutdown(Shutdown::Graceful)
}
}
impl Configurable<RusticConfig> for EntryPoint {
fn config_path(&self) -> Option<PathBuf> {
None
}
fn process_config(&self, _config: RusticConfig) -> Result<RusticConfig, FrameworkError> {
let mut config = self.config.clone();
for (var, value) in std::env::vars() {
if let Some(var) = var.strip_prefix("RUSTIC_REPO_OPT_") {
let var = var.from_case(Case::UpperSnake).to_case(Case::Kebab);
_ = config.repository.be.options.insert(var, value);
} else if let Some(var) = var.strip_prefix("OPENDAL_") {
let var = var.from_case(Case::UpperSnake).to_case(Case::Snake);
_ = config.repository.be.options.insert(var, value);
} else if let Some(var) = var.strip_prefix("RUSTIC_REPO_OPTHOT_") {
let var = var.from_case(Case::UpperSnake).to_case(Case::Kebab);
_ = config.repository.be.options_hot.insert(var, value);
} else if let Some(var) = var.strip_prefix("RUSTIC_REPO_OPTCOLD_") {
let var = var.from_case(Case::UpperSnake).to_case(Case::Kebab);
_ = config.repository.be.options_cold.insert(var, value);
} else if let Some(var) = var.strip_prefix("OPENDALHOT_") {
let var = var.from_case(Case::UpperSnake).to_case(Case::Snake);
_ = config.repository.be.options_hot.insert(var, value);
} else if let Some(var) = var.strip_prefix("OPENDALCOLD_") {
let var = var.from_case(Case::UpperSnake).to_case(Case::Snake);
_ = config.repository.be.options_cold.insert(var, value);
} else if var == "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT" {
#[cfg(feature = "opentelemetry")]
if let Ok(url) = Url::parse(&value) {
_ = config.global.opentelemetry.insert(url);
}
} else if var == "OTEL_SERVICE_NAME" && cfg!(feature = "opentelemetry") {
_ = config.backup.metrics_job.insert(value);
}
}
let mut merge_logs = Vec::new();
if config.global.use_profiles.is_empty() {
config.merge_profile("rustic", &mut merge_logs, Level::Info)?;
} else {
for profile in &config.global.use_profiles.clone() {
config.merge_profile(profile, &mut merge_logs, Level::Warn)?;
}
}
config
.global
.logging_options
.start_logger(config.global.dry_run)
.map_err(|e| FrameworkErrorKind::ConfigError.context(e))?;
if config.global.logging_options.log_file.is_some() {
info!("rustic {}", version());
info!("command: {:?}", std::env::args_os().collect::<Vec<_>>());
}
for (level, merge_log) in merge_logs {
log!(level, "{merge_log}");
}
match &self.commands {
RusticCmd::Forget(cmd) => cmd.override_config(config),
RusticCmd::Copy(cmd) => cmd.override_config(config),
#[cfg(feature = "webdav")]
RusticCmd::Webdav(cmd) => cmd.override_config(config),
#[cfg(feature = "mount")]
RusticCmd::Mount(cmd) => cmd.override_config(config),
_ => Ok(config),
}
}
}
#[cfg(test)]
mod tests {
use crate::commands::EntryPoint;
use clap::CommandFactory;
#[test]
fn verify_cli() {
EntryPoint::command().debug_assert();
}
}