From 7970d169c9578e0c07b097773350fc92ea5c8995 Mon Sep 17 00:00:00 2001 From: RedPanda Date: Fri, 30 Jan 2026 12:04:22 +0530 Subject: [PATCH 1/5] add: FOC_DEVNET_BASERDIR support --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + src/paths.rs | 15 +++++++++++---- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7554f8c..f195c51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -866,6 +866,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "sha3", + "shellexpand", "tar", "tempfile", "toml", @@ -2457,6 +2458,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shellexpand" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" +dependencies = [ + "dirs", +] + [[package]] name = "shlex" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 800c1ef..3b93d57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ bip32 = "0.5" rand = "0.8" bls-signatures = "0.15" names = { version = "0.14", default-features = false } +shellexpand = "3.0" flate2 = "1.0" tar = "0.4" reqwest = { version = "0.11", features = ["blocking"] } diff --git a/src/paths.rs b/src/paths.rs index 46f6a54..199f717 100644 --- a/src/paths.rs +++ b/src/paths.rs @@ -1,10 +1,17 @@ use std::path::PathBuf; -/// Returns the path to the foc-devnet home directory, e.g., ~/.foc-devnet +/// Returns the path to the foc-devnet home directory. +/// First checks for $FOC_DEVNET_BASEDIR environment variable. +/// If not set, defaults to ~/.foc-devnet +/// Supports tilde expansion for paths like ~/my-foc-devnet pub fn foc_devnet_home() -> PathBuf { - dirs::home_dir() - .unwrap_or_else(|| PathBuf::from("/tmp")) - .join(".foc-devnet") + if let Ok(base_dir) = std::env::var("FOC_DEVNET_BASEDIR") { + PathBuf::from(shellexpand::tilde(&base_dir).as_ref()) + } else { + dirs::home_dir() + .unwrap_or_else(|| PathBuf::from("/tmp")) + .join(".foc-devnet") + } } /// Returns the path to the foc-devnet logs directory, e.g., ~/.foc-devnet/logs From 30eff9a272a921a963d4d9943d8dc03b3d1096ac Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 12:23:59 +0530 Subject: [PATCH 2/5] Document FOC_DEVNET_BASEDIR environment variable (#53) * Initial plan * Add documentation for FOC_DEVNET_BASEDIR environment variable Co-authored-by: redpanda-f <181817029+redpanda-f@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: redpanda-f <181817029+redpanda-f@users.noreply.github.com> --- README.md | 1 + README_ADVANCED.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/README.md b/README.md index ff98b66..e8a8710 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,7 @@ Built for scripting and automation: - **Contract addresses**: `~/.foc-devnet/run//contract_addresses.json` - **Step context**: `~/.foc-devnet/run//step_context.json` - **Latest run symlink**: `~/.foc-devnet/state/latest/` → points to most recent run +- **Custom base directory**: Set `FOC_DEVNET_BASEDIR` env var to override default `~/.foc-devnet` location (see [Environment Variables](README_ADVANCED.md#environment-variables)) - Write scripts for testing, demos, CI/CD pipelines, etc. ### 🌐 Isolated Networks diff --git a/README_ADVANCED.md b/README_ADVANCED.md index 4c3d75b..b5f21bb 100644 --- a/README_ADVANCED.md +++ b/README_ADVANCED.md @@ -219,6 +219,63 @@ foc-devnet init --force --- +### Environment Variables + +#### `FOC_DEVNET_BASEDIR` + +**Purpose:** Overrides the default `~/.foc-devnet` directory location. + +**Use Cases:** +- **Testing multiple isolated environments:** Run separate instances of foc-devnet with different configurations +- **Custom directory locations:** Store data on a specific disk or partition (e.g., for SSD/HDD optimization) +- **CI/CD pipelines:** Use predictable paths in automated testing environments +- **Shared team environments:** Keep different team members' instances isolated + +**Tilde Expansion:** +The variable supports tilde (`~`) expansion, so you can use paths like `~/my-custom-foc` or `~/projects/foc-test`. + +**Example Usage:** + +```bash +# Use a custom directory with tilde expansion +export FOC_DEVNET_BASEDIR=~/foc-test-env +foc-devnet init +foc-devnet start + +# Use an absolute path +export FOC_DEVNET_BASEDIR=/mnt/ssd/foc-devnet +foc-devnet init + +# Run multiple isolated instances (in different terminals) +# Terminal 1: +export FOC_DEVNET_BASEDIR=~/foc-env-1 +foc-devnet start + +# Terminal 2: +export FOC_DEVNET_BASEDIR=~/foc-env-2 +foc-devnet start +``` + +**Default Behavior:** +If `FOC_DEVNET_BASEDIR` is not set, foc-devnet uses `~/.foc-devnet` as the base directory. + +**Directory Structure:** +When `FOC_DEVNET_BASEDIR` is set, all data directories are created under the specified path instead of `~/.foc-devnet`: +``` +$FOC_DEVNET_BASEDIR/ +├── config.toml +├── bin/ +├── code/ +├── docker/volumes/ +├── keys/ +├── logs/ +├── run/ +├── state/ +└── tmp/ +``` + +--- + ## Directory Structure ``` From 29289ca44de8d1e2b397976c52c54f1b67e08cf9 Mon Sep 17 00:00:00 2001 From: RedPanda Date: Fri, 30 Jan 2026 13:44:18 +0530 Subject: [PATCH 3/5] fix: edge case: FOC_DEVNET_BASEDIR is set but empty --- src/paths.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/paths.rs b/src/paths.rs index 199f717..c3afd83 100644 --- a/src/paths.rs +++ b/src/paths.rs @@ -1,16 +1,27 @@ use std::path::PathBuf; +use tracing::warn; + /// Returns the path to the foc-devnet home directory. /// First checks for $FOC_DEVNET_BASEDIR environment variable. /// If not set, defaults to ~/.foc-devnet /// Supports tilde expansion for paths like ~/my-foc-devnet pub fn foc_devnet_home() -> PathBuf { - if let Ok(base_dir) = std::env::var("FOC_DEVNET_BASEDIR") { - PathBuf::from(shellexpand::tilde(&base_dir).as_ref()) - } else { + let default_path = || { dirs::home_dir() .unwrap_or_else(|| PathBuf::from("/tmp")) .join(".foc-devnet") + }; + + if let Ok(base_dir) = std::env::var("FOC_DEVNET_BASEDIR") { + if !base_dir.trim().is_empty() { + PathBuf::from(shellexpand::tilde(&base_dir).as_ref()) + } else { + warn!("env var $FOC_DEVNET_BASEDIR is set but empty, falling back to default path"); + default_path() + } + } else { + default_path() } } From ad6ff292ab7f56608fcdc9fc90f30614dbb62cee Mon Sep 17 00:00:00 2001 From: RedPanda Date: Fri, 30 Jan 2026 15:33:46 +0530 Subject: [PATCH 4/5] Update Cargo.toml Co-authored-by: Rod Vagg --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3b93d57..d3b62ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ bip32 = "0.5" rand = "0.8" bls-signatures = "0.15" names = { version = "0.14", default-features = false } -shellexpand = "3.0" +shellexpand = "3" flate2 = "1.0" tar = "0.4" reqwest = { version = "0.11", features = ["blocking"] } From 8eccc866a99d42a00912835a16c9844940b1c23f Mon Sep 17 00:00:00 2001 From: RedPanda Date: Mon, 2 Feb 2026 13:11:21 +0530 Subject: [PATCH 5/5] add: testing around ENV_VAR --- src/paths.rs | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/src/paths.rs b/src/paths.rs index c3afd83..7ff297f 100644 --- a/src/paths.rs +++ b/src/paths.rs @@ -262,3 +262,106 @@ pub fn project_root() -> Result { // Constants for container paths /// Container path where Filecoin proof parameters are mounted pub const CONTAINER_FILECOIN_PROOF_PARAMS_PATH: &str = "/var/tmp/filecoin-proof-parameters"; + +#[cfg(test)] +mod tests { + use super::*; + use std::env; + + /// Helper to safely set and restore environment variables in tests + struct EnvGuard { + key: &'static str, + original: Option, + } + + impl EnvGuard { + fn new(key: &'static str, value: Option<&str>) -> Self { + let original = env::var(key).ok(); + match value { + Some(v) => env::set_var(key, v), + None => env::remove_var(key), + } + EnvGuard { key, original } + } + } + + impl Drop for EnvGuard { + fn drop(&mut self) { + match &self.original { + Some(v) => env::set_var(self.key, v), + None => env::remove_var(self.key), + } + } + } + + #[test] + fn test_foc_devnet_home_with_custom_basedir() { + let _guard = EnvGuard::new("FOC_DEVNET_BASEDIR", Some("/tmp/my-foc-devnet")); + let path = foc_devnet_home(); + assert_eq!(path, PathBuf::from("/tmp/my-foc-devnet")); + } + + #[test] + fn test_foc_devnet_home_with_tilde_expansion() { + let _guard = EnvGuard::new("FOC_DEVNET_BASEDIR", Some("~/my-foc-devnet")); + let path = foc_devnet_home(); + + // Path should be expanded to actual home directory + let home = dirs::home_dir().unwrap(); + assert!(path.starts_with(&home)); + assert!(path.ends_with("my-foc-devnet")); + } + + #[test] + fn test_foc_devnet_home_with_empty_basedir() { + let _guard = EnvGuard::new("FOC_DEVNET_BASEDIR", Some("")); + let path = foc_devnet_home(); + + // Should fall back to default ~/.foc-devnet + let expected = dirs::home_dir() + .unwrap_or_else(|| PathBuf::from("/tmp")) + .join(".foc-devnet"); + assert_eq!(path, expected); + } + + #[test] + fn test_foc_devnet_home_with_whitespace_basedir() { + let _guard = EnvGuard::new("FOC_DEVNET_BASEDIR", Some(" ")); + let path = foc_devnet_home(); + + // Should fall back to default ~/.foc-devnet when only whitespace + let expected = dirs::home_dir() + .unwrap_or_else(|| PathBuf::from("/tmp")) + .join(".foc-devnet"); + assert_eq!(path, expected); + } + + #[test] + fn test_foc_devnet_home_no_env_var() { + // Ensure the env var is not set before the test + env::remove_var("FOC_DEVNET_BASEDIR"); + + let path = foc_devnet_home(); + + // Should use default ~/.foc-devnet + let expected = dirs::home_dir() + .unwrap_or_else(|| PathBuf::from("/tmp")) + .join(".foc-devnet"); + assert_eq!(path, expected); + } + + #[test] + fn test_dependent_paths_use_foc_devnet_home() { + let _guard = EnvGuard::new("FOC_DEVNET_BASEDIR", Some("/tmp/test-foc")); + + // All dependent paths should use the custom base directory + assert!(foc_devnet_logs().starts_with("/tmp/test-foc")); + assert_eq!(foc_devnet_logs(), PathBuf::from("/tmp/test-foc/logs")); + + assert!(foc_devnet_tmp().starts_with("/tmp/test-foc")); + assert_eq!(foc_devnet_tmp(), PathBuf::from("/tmp/test-foc/tmp")); + + assert!(foc_devnet_bin().starts_with("/tmp/test-foc")); + assert_eq!(foc_devnet_bin(), PathBuf::from("/tmp/test-foc/bin")); + } +}