Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 121 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ env_logger = "0.11.8"
log = "0.4.27"
# Message serialization/deserialization
serde = { version = "1.0.219", features = ["derive"] }
serde_bytes = "0.11"
serde_json = "1.0.143"
# Async
tokio = { version = "1.47.1", features = ["full"] }
# Directory walking
walkdir = "2.5.0"

# Release build with debug symbols
[profile.profiling]
inherits = "release"
debug = 1
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# seedmirror

seedmirror is a utility to monitor remote filesystem changes and automatically synchronize them to a local directory using rsync over ssh.
seedmirror is a utility to monitor remote filesystem changes and automatically synchronize them to a local directory over ssh.

## running (binary)

- openssh 6.7+ (for unix domain socket forwarding support, client and server)
- rsync 3.2.3+ (for --mkpath flag, client only)

Download the latest release [here](https://github.com/voidiz/seedmirror/releases) or [build](BUILDING.md) the binaries yourself.

Expand Down
54 changes: 39 additions & 15 deletions crates/seedmirror-client/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ pub(crate) struct Args {
value_parser = Self::parse_path_mapping,
action = clap::ArgAction::Append
)]
pub path_mappings: Vec<(PathBuf, PathBuf)>,
pub path_mappings: Vec<PathMapping>,

/// Perform full sync of remote directory upon connecting.
#[arg(long, default_value_t = true)]
pub initial_sync: bool,
/// Disable the full sync of every mapped path upon connecting.
#[arg(long)]
pub no_initial_sync: bool,

/// Preview all file changes through logs. No actual syncing of files (or full sync) will be
/// done.
Expand All @@ -41,10 +41,6 @@ pub(crate) struct Args {
#[arg(long, default_value_os_t = PathBuf::from("/tmp/forwarded-seedmirror-server.sock"))]
pub local_socket_path: PathBuf,

/// Additional rsync flags. Specify multiple times for multiple flags.
#[arg(long = "extra-rsync-flag")]
pub extra_rsync_flags: Vec<String>,

/// Whether the GUI HTTP server is enabled.
#[cfg(feature = "gui")]
#[arg(long, default_value_t = false)]
Expand All @@ -56,17 +52,45 @@ pub(crate) struct Args {
pub http_addr: String,
}

#[derive(Clone, Debug)]
pub(crate) struct PathMapping {
/// Remote root to watch and list (trailing slash normalized away).
pub remote: PathBuf,

/// Local destination root.
pub local: PathBuf,

/// The base path used to calculate the local path from the remote path.
/// It is `remote` for `src/`, the parent of `remote` for `src`.
pub strip: PathBuf,
}

impl Args {
fn parse_path_mapping(s: &str) -> clap::error::Result<(PathBuf, PathBuf), String> {
let parts: Vec<_> = s.split(':').collect();
if parts.len() != 2 {
fn parse_path_mapping(s: &str) -> clap::error::Result<PathMapping, String> {
let parts = s.split(':').collect::<Vec<_>>();
let [remote_str, local_str] = parts[..] else {
return Err("expected <remote source path>:<local destination path>".into());
}
};

let remote_path = Self::parse_absolute_path(remote_str)?;
let local_path = Self::parse_absolute_path(local_str)?;

let remote_path = Self::parse_absolute_path(parts[0])?;
let local_path = Self::parse_absolute_path(parts[1])?;
let strip = if remote_str.ends_with('/') {
remote_path.clone()
} else {
// Unreachable for `/`. Since it ends with `/` it falls under the branch above.
// `parent()` is `None` only for root.
remote_path
.parent()
.map(|p| p.to_path_buf())
.ok_or_else(|| format!("cannot place filesystem root into target: {s}"))?
};

Ok((remote_path, local_path))
Ok(PathMapping {
remote: remote_path,
local: local_path,
strip,
})
}

fn parse_absolute_path(s: &str) -> clap::error::Result<PathBuf, String> {
Expand Down
Loading
Loading