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
10 changes: 10 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ bip32 = "0.5"
rand = "0.8"
bls-signatures = "0.15"
names = { version = "0.14", default-features = false }
shellexpand = "3"
flate2 = "1.0"
tar = "0.4"
reqwest = { version = "0.11", features = ["blocking"] }
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ Built for scripting and automation:
- **Contract addresses**: `~/.foc-devnet/run/<run-id>/contract_addresses.json`
- **Step context**: `~/.foc-devnet/run/<run-id>/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
Expand Down
57 changes: 57 additions & 0 deletions README_ADVANCED.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

```
Expand Down
129 changes: 125 additions & 4 deletions src/paths.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
use std::path::PathBuf;

/// Returns the path to the foc-devnet home directory, e.g., ~/.foc-devnet
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 {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("/tmp"))
.join(".foc-devnet")
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()
}
}

/// Returns the path to the foc-devnet logs directory, e.g., ~/.foc-devnet/logs
Expand Down Expand Up @@ -244,3 +262,106 @@ pub fn project_root() -> Result<PathBuf, std::io::Error> {
// 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<String>,
}

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"));
}
}