Skip to content

Commit 8cff216

Browse files
committed
Implement load_or_prompt_default for configuration loading
1 parent 7a4b5bb commit 8cff216

3 files changed

Lines changed: 38 additions & 2 deletions

File tree

src/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ impl Cli {
210210
}) => {
211211
print_banner();
212212

213-
let config = Config::load(config_path)?;
213+
let config = Config::load_or_prompt_default(config_path)?;
214214
tracing::info!("Starting DNS server...");
215215

216216
// Create blocklist manager

src/config.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use anyhow::Context;
33
use serde::{Deserialize, Serialize};
44
use std::fmt;
55
use std::fs;
6+
use std::io::{self, IsTerminal, Write};
67
use std::net::{IpAddr, SocketAddr};
78
use std::path::Path;
89
use std::str::FromStr;
@@ -372,6 +373,41 @@ impl Config {
372373
.with_context(|| format!("Failed to write config file: {}", path.display()))?;
373374
Ok(())
374375
}
376+
377+
/// Load configuration from `path`, offering to create a default config
378+
/// there on first launch.
379+
///
380+
/// If the file is missing and stdin is an interactive terminal, prompts
381+
/// the user to write out `Config::default()` at `path` (creating parent
382+
/// directories as needed) before loading. Non-interactive sessions and
383+
/// declined prompts fall through to the plain `load` error.
384+
pub fn load_or_prompt_default<P: AsRef<Path>>(path: P) -> Result<Self> {
385+
let path = path.as_ref();
386+
387+
if !path.exists() && io::stdin().is_terminal() {
388+
print!(
389+
"No config file found at {}. Create a default one there now? [Y/n] ",
390+
path.display()
391+
);
392+
io::stdout().flush().ok();
393+
394+
let mut answer = String::new();
395+
io::stdin().read_line(&mut answer)?;
396+
let answer = answer.trim().to_lowercase();
397+
398+
if answer.is_empty() || answer == "y" || answer == "yes" {
399+
if let Some(parent) = path.parent() {
400+
fs::create_dir_all(parent).with_context(|| {
401+
format!("Failed to create config directory: {}", parent.display())
402+
})?;
403+
}
404+
Config::default().save(path)?;
405+
println!("Wrote default configuration to {}", path.display());
406+
}
407+
}
408+
409+
Self::load(path)
410+
}
375411
}
376412

377413
impl Default for Config {

src/tui/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const LOG_CAPACITY: usize = 500;
2323

2424
/// Run the DNS daemon with the interactive dashboard attached
2525
pub async fn run(config_path: &str) -> Result<()> {
26-
let config = Config::load(config_path)?;
26+
let config = Config::load_or_prompt_default(config_path)?;
2727

2828
// Install log capture before anything logs: stdout belongs to the TUI,
2929
// so tracing events are rendered inside the activity panel instead.

0 commit comments

Comments
 (0)