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
2 changes: 2 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions crates/replay/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ willow-state = { path = "../state" }
willow-identity = { path = "../identity" }
willow-network = { path = "../network" }
clap = { version = "4", features = ["derive"] }
dirs = "6"
anyhow = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
Expand Down
40 changes: 39 additions & 1 deletion crates/replay/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,28 @@

pub mod role;

use std::path::PathBuf;

use clap::Parser;
use role::{ReplayConfig, ReplayRole};

/// Compute the platform-aware default path for the replay identity key.
///
/// Prefers the user's config dir (e.g. `$XDG_CONFIG_HOME/willow/replay.key`
/// on Linux, `~/Library/Application Support/willow/replay.key` on macOS),
/// falling back to `/etc/willow/replay.key` when no config dir is available
/// (matches the historical Linux deployment path used by the Docker images).
fn default_replay_key() -> PathBuf {
dirs::config_dir()
.map(|d| d.join("willow").join("replay.key"))
.unwrap_or_else(|| PathBuf::from("/etc/willow/replay.key"))
}

#[derive(Parser)]
#[command(name = "willow-replay", about = "Willow replay worker node")]
struct Cli {
/// Path to the Ed25519 identity keypair file.
#[arg(long, default_value = "/etc/willow/replay.key")]
#[arg(long, default_value_t = default_replay_key().display().to_string())]
identity_path: String,

/// Iroh relay URL to connect through.
Expand Down Expand Up @@ -93,3 +107,27 @@ async fn main() -> anyhow::Result<()> {

willow_worker::runtime::run(Box::new(role), config, network).await
}

#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;

#[test]
fn default_replay_key_targets_willow_subdir() {
let p = default_replay_key();
assert_eq!(p.file_name().and_then(|s| s.to_str()), Some("replay.key"));
let parent = p.parent().expect("default path has parent");
assert_eq!(
parent.file_name().and_then(|s| s.to_str()),
Some("willow"),
"expected willow/ as parent dir, got {parent:?}"
);
assert!(p.is_absolute(), "default path must be absolute, got {p:?}");
// Sanity: path contains at least the `willow/replay.key` tail.
assert!(
p.ends_with(Path::new("willow/replay.key")),
"expected path to end with willow/replay.key, got {p:?}"
);
}
}
1 change: 1 addition & 0 deletions crates/storage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ willow-network = { path = "../network" }
willow-common = { path = "../common" }
rusqlite = { version = "0.31", features = ["bundled"] }
clap = { version = "4", features = ["derive"] }
dirs = "6"
anyhow = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
Expand Down
68 changes: 66 additions & 2 deletions crates/storage/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,47 @@
pub mod role;
pub mod store;

use std::path::PathBuf;

use clap::Parser;
use role::StorageRole;
use store::StorageEventStore;

/// Compute the platform-aware default path for the storage identity key.
///
/// Prefers the user's config dir (e.g. `$XDG_CONFIG_HOME/willow/storage.key`
/// on Linux), falling back to `/etc/willow/storage.key` when no config dir
/// is available (matches the historical Linux deployment path).
fn default_storage_key() -> PathBuf {
dirs::config_dir()
.map(|d| d.join("willow").join("storage.key"))
.unwrap_or_else(|| PathBuf::from("/etc/willow/storage.key"))
}

/// Compute the platform-aware default path for the storage SQLite database.
///
/// Prefers the user's data dir (e.g. `$XDG_DATA_HOME/willow/storage.db` on
/// Linux), falling back to `/var/lib/willow/storage.db` when no data dir is
/// available (matches the historical Linux deployment path).
fn default_storage_db() -> PathBuf {
dirs::data_dir()
.map(|d| d.join("willow").join("storage.db"))
.unwrap_or_else(|| PathBuf::from("/var/lib/willow/storage.db"))
}

#[derive(Parser)]
#[command(name = "willow-storage", about = "Willow storage worker node")]
struct Cli {
/// Path to the Ed25519 identity keypair file.
#[arg(long, default_value = "/etc/willow/storage.key")]
#[arg(long, default_value_t = default_storage_key().display().to_string())]
identity_path: String,

/// Iroh relay URL to connect through.
#[arg(long)]
relay_url: Option<String>,

/// Path to SQLite database.
#[arg(long, default_value = "/var/lib/willow/storage.db")]
#[arg(long, default_value_t = default_storage_db().display().to_string())]
db_path: String,

/// Active sync interval in seconds.
Expand Down Expand Up @@ -89,3 +113,43 @@ async fn main() -> anyhow::Result<()> {

willow_worker::runtime::run(Box::new(role), config, network).await
}

#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;

#[test]
fn default_storage_key_targets_willow_subdir() {
let p = default_storage_key();
assert_eq!(p.file_name().and_then(|s| s.to_str()), Some("storage.key"));
let parent = p.parent().expect("default path has parent");
assert_eq!(
parent.file_name().and_then(|s| s.to_str()),
Some("willow"),
"expected willow/ as parent dir, got {parent:?}"
);
assert!(p.is_absolute(), "default path must be absolute, got {p:?}");
assert!(
p.ends_with(Path::new("willow/storage.key")),
"expected path to end with willow/storage.key, got {p:?}"
);
}

#[test]
fn default_storage_db_targets_willow_subdir() {
let p = default_storage_db();
assert_eq!(p.file_name().and_then(|s| s.to_str()), Some("storage.db"));
let parent = p.parent().expect("default path has parent");
assert_eq!(
parent.file_name().and_then(|s| s.to_str()),
Some("willow"),
"expected willow/ as parent dir, got {parent:?}"
);
assert!(p.is_absolute(), "default path must be absolute, got {p:?}");
assert!(
p.ends_with(Path::new("willow/storage.db")),
"expected path to end with willow/storage.db, got {p:?}"
);
}
}