Skip to content

Commit

Permalink
add webdav command
Browse files Browse the repository at this point in the history
  • Loading branch information
aawsome committed Jan 28, 2024
1 parent 9a41c74 commit 6e80452
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 5 deletions.
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ description = { workspace = true }
members = ["crates/rustic_testing", "xtask"]

[features]
default = ["self-update"]
default = ["self-update", "webdav"]
mimalloc = ["dep:mimalloc"]
jemallocator = ["dep:jemallocator-global"]
self-update = ["dep:self_update", "dep:semver"]
webdav = ["dep:dav-server", "dep:warp", "dep:tokio", "rustic_core/webdav"]

[[bin]]
name = "rustic"
Expand Down Expand Up @@ -89,6 +90,9 @@ jemallocator-global = { version = "0.3.2", optional = true }
mimalloc = { version = "0.1.39", default_features = false, optional = true }
rhai = { workspace = true }
simplelog = { workspace = true }
dav-server = { version = "0.5.8", default-features = false, features = ["warp-compat"], optional = true }
warp = { version = "0.3.6", optional = true }
tokio = {version = "1", optional = true }

[dev-dependencies]
abscissa_core = { workspace = true, features = ["testing"] }
Expand Down
8 changes: 8 additions & 0 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@ pub(crate) mod self_update;
pub(crate) mod show_config;
pub(crate) mod snapshots;
pub(crate) mod tag;
#[cfg(feature = "webdav")]
pub(crate) mod webdav;

use std::fs::File;
use std::path::PathBuf;
use std::str::FromStr;

#[cfg(feature = "webdav")]
use crate::commands::webdav::WebDavCmd;
use crate::{
commands::{
backup::BackupCmd, cat::CatCmd, check::CheckCmd, completions::CompletionsCmd,
Expand Down Expand Up @@ -128,6 +132,10 @@ enum RusticCmd {

/// Change tags of snapshots
Tag(TagCmd),

/// Start a webdav server which allows to access the repository
#[cfg(feature = "webdav")]
Webdav(WebDavCmd),
}

fn styles() -> Styles {
Expand Down
107 changes: 107 additions & 0 deletions src/commands/webdav.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//! `mount` subcommand
use std::net::ToSocketAddrs;

use crate::{commands::open_repository, status_err, Application, RUSTIC_APP};
use abscissa_core::{Command, Runnable, Shutdown};
use anyhow::{anyhow, Result};
use dav_server::{warp::dav_handler, DavHandler};
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,

/// 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)]
path_template: Option<String>,

/// The time template to use to display times in the path template. See https://docs.rs/chrono/latest/chrono/format/strftime/index.html for format options. [default: "%Y-%m-%d_%H-%M-%S"]
#[clap(long)]
time_template: Option<String>,

/// Use symlinks. This may not be supported by all WebDAV clients
#[clap(long)]
symlinks: bool,

/// How to handle access to files. Default: "forbidden" for hot/cold repositories, else "read"
#[clap(long)]
file_access: Option<FilePolicy>,

/// Specify directly which path to mount
#[clap(value_name = "SNAPSHOT[:PATH]")]
snap: Option<String>,
}

impl Runnable for WebDavCmd {
fn run(&self) {
if let Err(err) = self.inner_run() {
status_err!("{}", err);
RUSTIC_APP.shutdown(Shutdown::Crash);
};
}
}

impl WebDavCmd {
fn inner_run(&self) -> Result<()> {
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()
.unwrap_or_else(|| "[{hostname}]/[{label}]/{time}".to_string());
let time_template = self
.time_template
.clone()
.unwrap_or_else(|| "%Y-%m-%d_%H-%M-%S".to_string());

let sn_filter = |sn: &_| config.snapshot_filter.matches(sn);

let vfs = if let Some(snap) = &self.snap {
let node = repo.node_from_snapshot_path(snap, sn_filter)?;
Vfs::from_dirnode(node, file_access)
} else {
let snapshots = repo.get_matching_snapshots(sn_filter)?;
let (latest, identical) = if self.symlinks {
(Latest::AsLink, IdenticalSnapshot::AsLink)
} else {
(Latest::AsDir, IdenticalSnapshot::AsDir)
};
Vfs::from_snapshots(
snapshots,
path_template,
time_template,
latest,
identical,
file_access,
)?
};
let addr = self
.addr
.to_socket_addrs()?
.next()
.ok_or_else(|| anyhow!("no address given"))?;
let dav_server = DavHandler::builder()
.filesystem(vfs.into_webdav_fs(repo))
.build_handler();

tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?
.block_on(async {
warp::serve(dav_handler(dav_server)).run(addr).await;
});

Ok(())
}
}
8 changes: 4 additions & 4 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,15 @@ pub struct RusticConfig {
#[derive(Clone, Default, Debug, Parser, Deserialize, Merge)]
#[serde(default, rename_all = "kebab-case")]
pub struct AllRepositoryOptions {
/// Repository options
/// Backend options
#[clap(flatten)]
#[serde(flatten)]
pub repo: RepositoryOptions,
pub be: BackendOptions,

/// Backend options
/// Repository options
#[clap(flatten)]
#[serde(flatten)]
pub be: BackendOptions,
pub repo: RepositoryOptions,
}

impl RusticConfig {
Expand Down

0 comments on commit 6e80452

Please sign in to comment.