From 546fe6f1fcc90b6688cacfa0f338ca3255e8f3fc Mon Sep 17 00:00:00 2001 From: Alexander Weiss Date: Mon, 29 Jan 2024 23:04:56 +0100 Subject: [PATCH] Add config support for webdav --- config/full.toml | 8 +++++ src/commands.rs | 2 ++ src/commands/webdav.rs | 69 ++++++++++++++++++++++++++++-------------- src/config.rs | 7 +++++ 4 files changed, 64 insertions(+), 22 deletions(-) diff --git a/config/full.toml b/config/full.toml index 2e519cf77..12e9ab75f 100644 --- a/config/full.toml +++ b/config/full.toml @@ -169,3 +169,11 @@ warm-up-wait = "10min" # Default: not set [[copy.targets]] repository = "/repo/rustic2" # Must be set # ... + +[webdav] +address = "localhost:8000" +path-template = "[{hostname}]/[{label}]/{time}" # only relevant if no snapshot-path is given +time-template = "%Y-%m-%d_%H-%M-%S" # only relevant if no snapshot-path is given +symlinks = false +file-access = "read" # Default: "forbidden" for hot/cold repos, else "read" +snapshot-path = "latest:/dir" # Default: not set - this will generate a virtual tree with all snapshots using path-template diff --git a/src/commands.rs b/src/commands.rs index 11985f29e..a74736a6e 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -235,6 +235,8 @@ impl Configurable for EntryPoint { match &self.commands { RusticCmd::Forget(cmd) => cmd.override_config(config), + #[cfg(feature = "webdav")] + RusticCmd::Webdav(cmd) => cmd.override_config(config), // subcommands that don't need special overrides use a catch all _ => Ok(config), diff --git a/src/commands/webdav.rs b/src/commands/webdav.rs index 8d8be312f..9fc97cfca 100644 --- a/src/commands/webdav.rs +++ b/src/commands/webdav.rs @@ -1,17 +1,21 @@ //! `webdav` subcommand -use std::net::ToSocketAddrs; +use std::{net::ToSocketAddrs, str::FromStr}; -use crate::{commands::open_repository, status_err, Application, RUSTIC_APP}; -use abscissa_core::{Command, Runnable, Shutdown}; +use crate::{commands::open_repository, status_err, Application, RusticConfig, RUSTIC_APP}; +use abscissa_core::{config::Override, Command, FrameworkError, Runnable, Shutdown}; use anyhow::{anyhow, Result}; use dav_server::{warp::dav_handler, DavHandler}; +use merge::Merge; +use serde::Deserialize; + use rustic_core::vfs::{FilePolicy, IdenticalSnapshot, Latest, Vfs}; -#[derive(clap::Parser, Command, Debug)] -pub(crate) struct WebDavCmd { - /// Address to bind the webdav server to - #[clap(long, value_name = "ADDRESS", default_value = "localhost:8000")] - addr: String, +#[derive(Clone, Command, Default, Debug, clap::Parser, Deserialize, Merge)] +#[serde(default, rename_all = "kebab-case", deny_unknown_fields)] +pub struct WebDavCmd { + /// Address to bind the webdav server to. [default: "localhost:8000"] + #[clap(long, value_name = "ADDRESS")] + address: Option, /// The path template to use for snapshots. {id}, {id_long}, {time}, {username}, {hostname}, {label}, {tags}, {backup_start}, {backup_end} are replaced. [default: "[{hostname}]/[{label}]/{time}"] #[clap(long)] @@ -23,15 +27,29 @@ pub(crate) struct WebDavCmd { /// Use symlinks. This may not be supported by all WebDAV clients #[clap(long)] + #[merge(strategy = merge::bool::overwrite_false)] symlinks: bool, - /// How to handle access to files. Default: "forbidden" for hot/cold repositories, else "read" + /// How to handle access to files. [default: "forbidden" for hot/cold repositories, else "read"] #[clap(long)] - file_access: Option, + file_access: Option, - /// Specify directly which path to mount + /// Specify directly which snapshot/path to serve #[clap(value_name = "SNAPSHOT[:PATH]")] - snap: Option, + snapshot_path: Option, +} + +impl Override for WebDavCmd { + // Process the given command line options, overriding settings from + // a configuration file using explicit flags taken from command-line + // arguments. + fn override_config(&self, mut config: RusticConfig) -> Result { + let mut self_config = self.clone(); + // merge "webdav" section from config file, if given + self_config.merge(config.webdav); + config.webdav = self_config; + Ok(config) + } } impl Runnable for WebDavCmd { @@ -48,14 +66,6 @@ impl WebDavCmd { let config = RUSTIC_APP.config(); let repo = open_repository(&config.repository)?.to_indexed()?; - let file_access = self.file_access.unwrap_or_else(|| { - if repo.config().is_hot == Some(true) { - FilePolicy::Forbidden - } else { - FilePolicy::Read - } - }); - let path_template = self .path_template .clone() @@ -67,7 +77,7 @@ impl WebDavCmd { let sn_filter = |sn: &_| config.snapshot_filter.matches(sn); - let vfs = if let Some(snap) = &self.snap { + let vfs = if let Some(snap) = &self.snapshot_path { let node = repo.node_from_snapshot_path(snap, sn_filter)?; Vfs::from_dirnode(node) } else { @@ -79,11 +89,26 @@ impl WebDavCmd { }; Vfs::from_snapshots(snapshots, &path_template, &time_template, latest, identical)? }; + let addr = self - .addr + .address + .clone() + .unwrap_or_else(|| "localhost:8000".to_string()) .to_socket_addrs()? .next() .ok_or_else(|| anyhow!("no address given"))?; + + let file_access = self.file_access.as_ref().map_or_else( + || { + if repo.config().is_hot == Some(true) { + Ok(FilePolicy::Forbidden) + } else { + Ok(FilePolicy::Read) + } + }, + |s| FilePolicy::from_str(s), + )?; + let dav_server = DavHandler::builder() .filesystem(vfs.into_webdav_fs(repo, file_access)) .build_handler(); diff --git a/src/config.rs b/src/config.rs index 3ab0630d7..3a6d3757c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -22,6 +22,8 @@ use rustic_backend::BackendOptions; use rustic_core::RepositoryOptions; use serde::{Deserialize, Serialize}; +#[cfg(feature = "webdav")] +use crate::commands::webdav::WebDavCmd; use crate::{ commands::{backup::BackupCmd, copy::Targets, forget::ForgetOptions}, config::progress_options::ProgressOptions, @@ -60,6 +62,11 @@ pub struct RusticConfig { /// Forget options #[clap(skip)] pub forget: ForgetOptions, + + #[cfg(feature = "webdav")] + /// webdav options + #[clap(skip)] + pub webdav: WebDavCmd, } #[derive(Clone, Default, Debug, Parser, Deserialize, Merge)]