Skip to content

Commit f8d22d0

Browse files
committed
feat: add clean command, make init non-destructive
init no longer deletes existing state. It refuses if the home directory has contents and directs the user to run `clean` first. config.toml is preserved across clean by default (--all to remove it). Removes --force flag from init.
1 parent f6e4721 commit f8d22d0

11 files changed

Lines changed: 256 additions & 162 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,14 +150,14 @@ jobs:
150150
- name: "EXEC: {Initialize with cached Docker}, DEP: {C-docker-images-cache}"
151151
if: steps.cache-docker-images.outputs.cache-hit == 'true'
152152
run: |
153-
rm -rf ~/.foc-devnet
153+
./foc-devnet clean --all
154154
./foc-devnet init --no-docker-build
155155
156156
# If Docker images are not cached, do full init (downloads YugabyteDB and builds all images)
157157
- name: "EXEC: {Initialize without cache}, independent"
158158
if: steps.cache-docker-images.outputs.cache-hit != 'true'
159159
run: |
160-
rm -rf ~/.foc-devnet
160+
./foc-devnet clean --all
161161
./foc-devnet init
162162
163163
# CACHE-DOCKER: Build Docker images if not cached

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,11 @@ cargo run -- init \
5050
--curio local:/home/user/code/curio \
5151
--filecoin-services local:/home/user/code/filecoin-services \
5252
--lotus local:/home/user/code/lotus \
53-
--synapse-sdk local:/home/user/code/synapse-sdk \
54-
--force
53+
--synapse-sdk local:/home/user/code/synapse-sdk
5554
```
5655

56+
**Re-initializing?** Run `cargo run -- clean` first to remove existing state. Config is preserved by default; use `--all` to reset it too.
57+
5758
### Step 2: Build
5859

5960
```bash
@@ -176,7 +177,7 @@ Bundled with Portainer for browser-based Docker management—no terminal wizardr
176177
## 🛠️ Need More?
177178

178179
See **[README_ADVANCED.md](README_ADVANCED.md)** for comprehensive documentation on:
179-
- **All commands reference** (init, build, start, stop, status, version)
180+
- **All commands reference** (init, build, start, stop, clean, status, version)
180181
- **Configuration system** (config.toml structure, parameters, editing)
181182
- **Complete directory structure** (what's stored where and why)
182183
- **Resetting and cleanup** (manual cleanup, disk management)

README_ADVANCED.md

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,30 @@ This guide covers advanced usage, internal architecture, and operational details
66

77
## Commands Reference
88

9+
### `clean`
10+
Removes foc-devnet state. Preserves `config.toml` by default so it can be reused on next `init`.
11+
12+
```bash
13+
foc-devnet clean [OPTIONS]
14+
```
15+
16+
**Options:**
17+
- `--all` - Also remove `config.toml`
18+
- `--images` - Also remove cached foc-* Docker images
19+
20+
**Examples:**
21+
```bash
22+
foc-devnet clean # Remove state, preserve config.toml
23+
foc-devnet clean --all # Remove everything including config
24+
foc-devnet clean --images # Remove state + Docker images
25+
foc-devnet clean --all --images # Full reset
26+
```
27+
928
### `init`
1029
Initializes foc-devnet by downloading repositories, building Docker images, and preparing the environment.
1130

31+
Requires a clean home directory. If existing state is present, `init` will refuse and ask you to run `clean` first. If a `config.toml` was preserved across `clean`, it is reused (with any CLI overrides applied on top).
32+
1233
```bash
1334
foc-devnet init [OPTIONS]
1435
```
@@ -21,7 +42,6 @@ foc-devnet init [OPTIONS]
2142
- `--yugabyte-url <URL>` - Yugabyte download URL
2243
- `--yugabyte-archive <PATH>` - Local Yugabyte archive file
2344
- `--proof-params-dir <PATH>` - Local proof params directory
24-
- `--force` - Force regeneration of config file. Useful when switching between configurations.
2545
- `--rand` - Use random mnemonic instead of deterministic one. Use this for unique test scenarios.
2646

2747
**Source Format:**
@@ -35,8 +55,7 @@ foc-devnet init [OPTIONS]
3555
```bash
3656
foc-devnet init \
3757
--lotus local:/home/user/lotus \
38-
--curio gitbranch:pdpv0 \
39-
--force
58+
--curio gitbranch:pdpv0
4059
```
4160

4261
### `build`
@@ -273,8 +292,8 @@ tag = "synapse-sdk-v0.36.1"
273292
Defaults are defined in code (see [`src/config.rs`](src/config.rs) `Config::default()`) and written to `config.toml` during `init`. This means:
274293

275294
- **First-time setup:** Running `foc-devnet init` creates `config.toml` with current defaults from code
276-
- **Updating defaults:** When a new version of `foc-devnet` includes updated defaults (e.g., newer Lotus version), run `foc-devnet init --force` to regenerate `config.toml` with the new defaults
277-
- **Preserving customizations:** After regenerating, you'll need to reapply any custom settings you had modified
295+
- **Updating defaults:** When a new version of `foc-devnet` includes updated defaults (e.g., newer Lotus version), run `foc-devnet clean --all` then `foc-devnet init` to regenerate `config.toml` with the new defaults
296+
- **Preserving config across re-init:** Running `foc-devnet clean` (without `--all`) preserves your `config.toml`, so a subsequent `init` reuses your existing settings
278297
- **Source of truth:** The code defines what defaults are available; `config.toml` stores your specific configuration
279298

280299
### Editing Config
@@ -283,8 +302,9 @@ Defaults are defined in code (see [`src/config.rs`](src/config.rs) `Config::defa
283302
# Edit manually
284303
vim ~/.foc-devnet/config.toml
285304

286-
# Or use init --force to regenerate
287-
foc-devnet init --force
305+
# Or regenerate from defaults
306+
foc-devnet clean --all
307+
foc-devnet init
288308
```
289309

290310
---
@@ -475,10 +495,9 @@ cd ~/.foc-devnet/docker/volumes/run-specific
475495
ls | grep --invert-match "$CURRENT_RUN" | xargs rm -rf
476496
```
477497

478-
**Complete nuclear reset (delete EVERYTHING including config):**
498+
**Complete reset (delete EVERYTHING including config):**
479499
```bash
480-
# This deletes all runs, config, repos, binaries, keys - use with caution!
481-
rm -rf ~/.foc-devnet
500+
foc-devnet clean --all --images
482501
```
483502

484503
---
@@ -827,17 +846,15 @@ foc-devnet init \
827846
--lotus local:/home/user/dev/lotus \
828847
--curio local:/home/user/dev/curio \
829848
--filecoin-services local:/home/user/dev/filecoin-services \
830-
--synapse-sdk local:/home/user/dev/synapse-sdk \
831-
--force
849+
--synapse-sdk local:/home/user/dev/synapse-sdk
832850
```
833851

834852
**Mixed approach:**
835853

836854
```bash
837855
foc-devnet init \
838856
--lotus gitbranch:master \
839-
--curio local:/home/user/dev/curio \
840-
--force
857+
--curio local:/home/user/dev/curio
841858
```
842859

843860
### Sharing Configuration
@@ -1203,7 +1220,8 @@ docker logs foc-<run-id>-lotus
12031220
docker images | grep foc-lotus
12041221

12051222
# Rebuild if needed
1206-
foc-devnet init --force
1223+
foc-devnet clean
1224+
foc-devnet init
12071225
```
12081226

12091227
### Build failures

src/cli.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ pub enum Commands {
2121
},
2222
/// Stop the local cluster
2323
Stop,
24+
/// Remove foc-devnet state. Preserves config.toml unless --all is passed.
25+
Clean {
26+
/// Also remove config.toml
27+
#[arg(long)]
28+
all: bool,
29+
/// Also remove cached foc-* Docker images
30+
#[arg(long)]
31+
images: bool,
32+
},
2433
/// Initialize foc-devnet by building and caching Docker images
2534
Init {
2635
/// Curio source location (e.g., 'gittag:tag', 'gittag:url:tag', 'gitcommit:commit', 'gitcommit:url:commit', 'gitbranch:branch', 'gitbranch:url:branch', 'local:/path/to/curio')
@@ -41,9 +50,6 @@ pub enum Commands {
4150
/// Path to local filecoin-proof-params directory to use instead of downloading
4251
#[arg(long)]
4352
proof_params_dir: Option<String>,
44-
/// Force regeneration of config file even if it exists
45-
#[arg(long)]
46-
force: bool,
4753
/// Use random mnemonic instead of deterministic one
4854
#[arg(long)]
4955
rand: bool,

src/commands/clean.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
use std::io::ErrorKind;
2+
use std::process::Command;
3+
use tracing::{info, warn};
4+
5+
use crate::paths::{foc_devnet_config, foc_devnet_home};
6+
7+
/// Remove foc-devnet state from the home directory.
8+
///
9+
/// Preserves config.toml by default so `init` can reuse it. Pass `all` to
10+
/// remove config.toml too. The directory itself is always preserved to avoid
11+
/// permission errors when the parent is not user-writable (e.g. a mount point).
12+
pub fn clean(all: bool, remove_images: bool) -> Result<(), Box<dyn std::error::Error>> {
13+
let home_dir = foc_devnet_home();
14+
let config_path = foc_devnet_config();
15+
16+
if !home_dir.exists() {
17+
info!("Nothing to clean ({})", home_dir.display());
18+
return Ok(());
19+
}
20+
21+
info!("Cleaning {}", home_dir.display());
22+
let mut kept_config = false;
23+
24+
for entry in std::fs::read_dir(&home_dir)? {
25+
let entry = entry?;
26+
let path = entry.path();
27+
28+
if !all && path == config_path {
29+
kept_config = true;
30+
continue;
31+
}
32+
33+
if path.is_dir() {
34+
std::fs::remove_dir_all(&path)?;
35+
} else {
36+
std::fs::remove_file(&path)?;
37+
}
38+
}
39+
40+
if kept_config {
41+
info!("Preserved config.toml (use --all to remove it too)");
42+
}
43+
44+
info!("Cleaned foc-devnet state");
45+
46+
if remove_images {
47+
clean_docker_images()?;
48+
}
49+
50+
Ok(())
51+
}
52+
53+
/// Check whether the home directory is ready for init.
54+
///
55+
/// Returns true if the directory contains no meaningful state. Ignores
56+
/// config.toml (preserved by clean for reuse) and the state/ and run/
57+
/// directories which are created as side effects of the logging and poison
58+
/// infrastructure before any command runs.
59+
pub fn is_clean_for_init() -> Result<bool, Box<dyn std::error::Error>> {
60+
let home_dir = foc_devnet_home();
61+
if !home_dir.exists() {
62+
return Ok(true);
63+
}
64+
let config_path = foc_devnet_config();
65+
let state_dir = crate::paths::foc_devnet_state();
66+
let runs_dir = crate::paths::foc_devnet_runs();
67+
for entry in std::fs::read_dir(&home_dir)? {
68+
let path = entry?.path();
69+
if path == config_path || path == state_dir || path == runs_dir {
70+
continue;
71+
}
72+
return Ok(false);
73+
}
74+
Ok(true)
75+
}
76+
77+
fn docker_not_found_error() -> Box<dyn std::error::Error> {
78+
"Docker CLI not found. Install Docker and ensure the 'docker' command is on PATH."
79+
.to_string()
80+
.into()
81+
}
82+
83+
fn clean_docker_images() -> Result<(), Box<dyn std::error::Error>> {
84+
info!("Removing foc-* Docker images");
85+
let output = Command::new("docker")
86+
.args(["images", "--format", "{{.Repository}}:{{.Tag}}"])
87+
.output()
88+
.map_err(|err| match err.kind() {
89+
ErrorKind::NotFound => docker_not_found_error(),
90+
_ => err.into(),
91+
})?;
92+
93+
if !output.status.success() {
94+
warn!("Could not list Docker images (Docker may not be running)");
95+
return Ok(());
96+
}
97+
98+
let stdout = String::from_utf8_lossy(&output.stdout);
99+
let mut removed_count = 0;
100+
101+
for line in stdout.lines() {
102+
if line.starts_with("foc-") {
103+
let remove_output = Command::new("docker")
104+
.args(["rmi", line])
105+
.output()
106+
.map_err(|err| match err.kind() {
107+
ErrorKind::NotFound => docker_not_found_error(),
108+
_ => err.into(),
109+
})?;
110+
111+
if remove_output.status.success() {
112+
removed_count += 1;
113+
}
114+
}
115+
}
116+
117+
if removed_count > 0 {
118+
info!("Removed {} Docker image(s)", removed_count);
119+
} else {
120+
info!("No foc-* Docker images found");
121+
}
122+
123+
Ok(())
124+
}

0 commit comments

Comments
 (0)