Skip to content

Commit 361ca7d

Browse files
committed
add config file for fnn-cli
1 parent 338fe68 commit 361ca7d

File tree

5 files changed

+177
-23
lines changed

5 files changed

+177
-23
lines changed

Cargo.lock

Lines changed: 54 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/fiber-cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ rustyline = { version = "15", features = ["derive"] }
1919
anyhow = "1.0"
2020
colored = "2"
2121
indicatif = "0.17"
22+
toml = "0.8"
2223
fiber-json-types = { path = "../fiber-json-types" , features = ["cch", "watchtower"]}
2324
ckb-jsonrpc-types = "1"
2425
ratatui = "0.29"

crates/fiber-cli/README.md

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,37 @@ fnn-cli -u http://127.0.0.1:8227 --tui
4141
| `--color` | | `auto` | Color output: `auto`, `always`, or `never` |
4242
| `--no-banner` | | | Suppress the banner in interactive mode |
4343
| `--tui` | | | Launch the Terminal User Interface dashboard |
44+
| `--config` | | `~/.fnn/config.toml` | Path to config file |
45+
46+
### Configuration Priority
47+
48+
Configuration values are resolved in the following priority order (highest to lowest):
49+
50+
```
51+
CLI flag > Environment variable > Config file > Default value
52+
```
53+
54+
### Configuration File
55+
56+
Create `~/.fnn/config.toml` to store default values:
57+
58+
```toml
59+
url = "http://192.168.1.100:8227"
60+
auth_token = "your-token-here"
61+
output_format = "yaml"
62+
```
63+
64+
Supported config options:
65+
- `url` - RPC endpoint URL (without `http://` prefix is also accepted)
66+
- `auth_token` - Bearer token for RPC authentication
67+
- `output_format` - Output format (`yaml` or `json`)
68+
69+
### Environment Variables
70+
71+
| Variable | Description |
72+
|----------|-------------|
73+
| `FNN_RPC_URL` | RPC endpoint URL |
74+
| `FNN_AUTH_TOKEN` | Bearer token for RPC authentication |
4475

4576
The auth token can also be set via the `FNN_AUTH_TOKEN` environment variable.
4677

@@ -89,7 +120,7 @@ FNN> exit
89120

90121
Features:
91122
- **Tab completion** for commands, subcommands, and `--flags`
92-
- **Command history** persisted to `~/.fnn_cli_history`
123+
- **Command history** persisted to `~/.fnn/history`
93124
- **Colored output** with syntax highlighting for JSON/YAML responses
94125
- **Shell-like quoting** — supports single quotes, double quotes, and backslash escaping
95126

@@ -246,7 +277,7 @@ The TUI provides a real-time overview of your Fiber node with 7 tabs:
246277
| 4 | **Peers** | List/connect/disconnect peers with search |
247278
| 5 | **Invoices** | Create/list/lookup/cancel/parse invoices |
248279
| 6 | **Graph** | Browse network graph nodes and channels |
249-
| 7 | **Logs** | Persistent activity log (saved to `~/.fnn-cli/tui.log`) |
280+
| 7 | **Logs** | Persistent activity log (saved to `~/.fnn/tui.log`) |
250281

251282
### Keyboard Shortcuts
252283

@@ -287,7 +318,7 @@ The TUI provides a real-time overview of your Fiber node with 7 tabs:
287318
- **Flash messages** -- success/error feedback appears in the footer after mutations (open channel, send payment, etc.) and fades after 5 seconds.
288319
- **Detail popups** -- press `Enter` on any item to see full details with word-wrapped values. Press `y` inside the popup to copy individual fields.
289320
- **Confirmation dialogs** -- destructive actions (shutdown/abandon channel, disconnect peer) require confirmation.
290-
- **Persistent logs** -- all activity is logged to `~/.fnn-cli/tui.log` and survives across sessions (last 2000 entries).
321+
- **Persistent logs** -- all activity is logged to `~/.fnn/tui.log` and survives across sessions (last 2000 entries).
291322
- **Network topology** -- the Dashboard tab shows an adjacency-list view of your node's peer connections and channel states.
292323

293324
## Getting Help

crates/fiber-cli/src/main.rs

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,29 @@ use rustyline::{Context, Editor, Helper};
3434
const FNN_CLI_VERSION: &str = env!("CARGO_PKG_VERSION");
3535
const DEFAULT_RPC_URL: &str = "http://127.0.0.1:8227";
3636

37+
#[derive(Debug, Default, serde::Deserialize)]
38+
struct ConfigFile {
39+
url: Option<String>,
40+
auth_token: Option<String>,
41+
output_format: Option<String>,
42+
}
43+
44+
fn default_config_dir() -> std::path::PathBuf {
45+
dirs_home_inner()
46+
.map(|h| h.join(".fnn"))
47+
.unwrap_or_else(|| std::path::PathBuf::from(".fnn"))
48+
}
49+
50+
fn default_config_path() -> std::path::PathBuf {
51+
default_config_dir().join("config.toml")
52+
}
53+
54+
fn load_config_file(path: &std::path::Path) -> Result<ConfigFile, anyhow::Error> {
55+
let content = std::fs::read_to_string(path)?;
56+
let config: ConfigFile = toml::from_str(&content)?;
57+
Ok(config)
58+
}
59+
3760
const BANNER_LINES: &[&str] = &[
3861
r" ╔═╗╦╔╗ ╔═╗╦═╗ ╔╗╔╔═╗╔╦╗╦ ╦╔═╗╦═╗╦╔═ ",
3962
r" ╠╣ ║╠╩╗║╣ ╠╦╝ ║║║║╣ ║ ║║║║ ║╠╦╝╠╩╗ ",
@@ -56,13 +79,19 @@ fn build_cli() -> Command {
5679
.about("Fiber Network Node CLI - interactive command-line interface for FNN")
5780
.version(FNN_CLI_VERSION)
5881
.styles(cli_styles())
82+
.arg(
83+
Arg::new("config")
84+
.long("config")
85+
.global(true)
86+
.value_name("FILE")
87+
.help("Path to config file (default: ~/.fnn/config.toml)"),
88+
)
5989
.arg(
6090
Arg::new("url")
6191
.long("url")
6292
.short('u')
6393
.global(true)
64-
.default_value(DEFAULT_RPC_URL)
65-
.help("The RPC endpoint URL"),
94+
.help("The RPC endpoint URL (default: http://127.0.0.1:8227, can also set via FNN_RPC_URL env or config file)"),
6695
)
6796
.arg(
6897
Arg::new("raw_data")
@@ -76,8 +105,7 @@ fn build_cli() -> Command {
76105
.long("output-format")
77106
.short('o')
78107
.global(true)
79-
.default_value("yaml")
80-
.help("Output format: json or yaml"),
108+
.help("Output format: json or yaml (default: yaml, can also set via config file)"),
81109
)
82110
.arg(
83111
Arg::new("no_banner")
@@ -404,7 +432,7 @@ async fn run_interactive(
404432
.build();
405433
let mut rl = Editor::with_config(config)?;
406434
rl.set_helper(Some(helper));
407-
let history_path = dirs_home().join(".fnn_cli_history");
435+
let history_path = default_config_dir().join("history");
408436
let _ = rl.load_history(&history_path);
409437

410438
loop {
@@ -513,10 +541,6 @@ fn shell_words(input: &str) -> Result<Vec<String>> {
513541
Ok(words)
514542
}
515543

516-
fn dirs_home() -> std::path::PathBuf {
517-
dirs_home_inner().unwrap_or_else(|| std::path::PathBuf::from("."))
518-
}
519-
520544
fn dirs_home_inner() -> Option<std::path::PathBuf> {
521545
#[cfg(target_os = "windows")]
522546
{
@@ -533,14 +557,61 @@ async fn main() -> Result<()> {
533557
let cli = build_cli();
534558
let matches = cli.get_matches();
535559

536-
let url = matches.get_one::<String>("url").unwrap().clone();
560+
// Load config file if exists (default: ~/.fnn/config.toml)
561+
// Priority: CLI arg > env > config file > default
562+
let config_file_path = matches
563+
.get_one::<String>("config")
564+
.map(std::path::PathBuf::from)
565+
.unwrap_or_else(default_config_path);
566+
567+
let config_file = match load_config_file(&config_file_path) {
568+
Ok(cfg) => Some(cfg),
569+
Err(e)
570+
if e.downcast_ref::<std::io::Error>()
571+
.map(|e| e.kind() == std::io::ErrorKind::NotFound)
572+
.unwrap_or(false) =>
573+
{
574+
// Config file not found - this is fine, use defaults
575+
None
576+
}
577+
Err(e) => {
578+
eprintln!("[config] Failed to load from {:?}: {}", config_file_path, e);
579+
None
580+
}
581+
};
582+
583+
// Resolve URL: CLI > env > config file > default
584+
let url = matches
585+
.get_one::<String>("url")
586+
.cloned()
587+
.or_else(|| std::env::var("FNN_RPC_URL").ok())
588+
.or_else(|| config_file.as_ref()?.url.clone())
589+
.unwrap_or_else(|| DEFAULT_RPC_URL.to_string());
590+
591+
// Ensure URL has http:// prefix
592+
let url = if url.starts_with("http://") || url.starts_with("https://") {
593+
url
594+
} else {
595+
format!("http://{}", url)
596+
};
597+
537598
let raw_data = matches.get_flag("raw_data");
538-
let output_format = matches.get_one::<String>("output_format").unwrap().clone();
599+
600+
// Resolve output_format: CLI > config file > default
601+
let output_format = matches
602+
.get_one::<String>("output_format")
603+
.cloned()
604+
.or_else(|| config_file.as_ref().and_then(|c| c.output_format.clone()))
605+
.unwrap_or_else(|| "yaml".to_string());
606+
539607
let no_banner = matches.get_flag("no_banner");
540-
let color_mode = matches.get_one::<String>("color").unwrap().clone();
608+
let color_mode = matches.get_one::<String>("color").cloned().unwrap();
541609

542-
// Resolve auth token: --auth-token > --auth-token-file > FNN_AUTH_TOKEN env
543-
let auth_token = match matches.get_one::<String>("auth_token").cloned() {
610+
// Resolve auth token: CLI > env > config file
611+
// Priority: --auth-token > --auth-token-file > FNN_AUTH_TOKEN env > config file
612+
let auth_token_from_cli = matches.get_one::<String>("auth_token").cloned();
613+
let auth_token_from_file = config_file.as_ref().and_then(|c| c.auth_token.clone());
614+
let auth_token = match auth_token_from_cli {
544615
Some(token) => Some(token),
545616
None => match matches.get_one::<String>("auth_token_file") {
546617
Some(path) => {
@@ -549,7 +620,7 @@ async fn main() -> Result<()> {
549620
})?;
550621
Some(content)
551622
}
552-
None => None,
623+
None => auth_token_from_file,
553624
},
554625
};
555626

crates/fiber-cli/src/tui/tabs/logs.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,13 @@ impl LogsTab {
7171
tab
7272
}
7373

74-
/// Resolve the log file path: `~/.fnn-cli/tui.log`.
74+
/// Resolve the log file path: `~/.fnn/tui.log`.
7575
fn resolve_log_path() -> Option<PathBuf> {
7676
let home = std::env::var("HOME")
7777
.ok()
7878
.map(PathBuf::from)
7979
.or_else(|| std::env::var("USERPROFILE").ok().map(PathBuf::from))?;
80-
let dir = home.join(".fnn-cli");
80+
let dir = home.join(".fnn");
8181
if !dir.exists() {
8282
fs::create_dir_all(&dir).ok()?;
8383
}

0 commit comments

Comments
 (0)