diff options
| -rw-r--r-- | src/bin/tp.rs | 16 | ||||
| -rw-r--r-- | src/bin/tpd.rs | 24 | ||||
| -rw-r--r-- | src/daemon.rs | 36 |
3 files changed, 65 insertions, 11 deletions
diff --git a/src/bin/tp.rs b/src/bin/tp.rs index 5c4c473..fbfc872 100644 --- a/src/bin/tp.rs +++ b/src/bin/tp.rs @@ -18,7 +18,7 @@ fn default_socket() -> PathBuf { } fn usage() { - eprintln!("usage: tp [-s sock] <command> [args]"); + eprintln!("usage: tp [-s sock] [-v] <command> [args]"); eprintln!(); eprintln!("commands:"); eprintln!(" put [-t ttl] [-p] read stdin, store paste"); @@ -30,6 +30,7 @@ fn usage() { eprintln!(" status show daemon status"); eprintln!(); eprintln!(" -s sock Unix socket path"); + eprintln!(" -v verbose output"); eprintln!(" -t ttl time-to-live (e.g. 24h 30m 3600)"); } @@ -52,6 +53,7 @@ fn main() { let args: Vec<String> = std::env::args().collect(); let mut sock_path = default_socket(); + let mut verbose = false; let mut cmd_start = 1; // Parse global options before command @@ -67,6 +69,10 @@ fn main() { }); cmd_start = i + 1; } + "-v" => { + verbose = true; + cmd_start = i + 1; + } "-h" | "--help" => { usage(); return; @@ -177,6 +183,11 @@ fn main() { sandbox::unveil_lock(); sandbox::do_pledge("stdio unix rpath"); + if verbose { + eprintln!("socket: {}", sock_path.display()); + eprintln!(">> {}", request.trim()); + } + let stream = match UnixStream::connect(&sock_path) { Ok(s) => s, Err(e) => { @@ -202,6 +213,9 @@ fn main() { Ok(l) => l, Err(_) => break, }; + if verbose { + eprintln!("<< {}", line); + } if let Some(data) = line.strip_prefix("OK ") { if is_get { // Decode base58 → raw bytes → stdout diff --git a/src/bin/tpd.rs b/src/bin/tpd.rs index e1ebc7b..c18d88b 100644 --- a/src/bin/tpd.rs +++ b/src/bin/tpd.rs @@ -49,25 +49,18 @@ fn usage() { eprintln!(" -g global NAT (public server)"); eprintln!(" -n no auto-bootstrap (skip DNS SRV)"); eprintln!(" -b host:port bootstrap peer (repeatable)"); + eprintln!(" -v verbose (debug logging)"); eprintln!(" -h show this help"); } fn main() { - env_logger::Builder::from_env( - env_logger::Env::default().default_filter_or("info"), - ) - .format(|buf, record| { - use std::io::Write; - writeln!(buf, "[{}]: {}", record.level(), record.args()) - }) - .init(); - let mut port: u16 = 0; let mut dir = default_dir(); let mut sock: Option<PathBuf> = None; let mut http_port: Option<u16> = None; let mut global = false; let mut no_auto_bootstrap = false; + let mut verbose = false; let mut bootstrap: Vec<String> = Vec::new(); let args: Vec<String> = std::env::args().collect(); @@ -111,6 +104,7 @@ fn main() { } "-g" => global = true, "-n" => no_auto_bootstrap = true, + "-v" => verbose = true, "-b" => { i += 1; if let Some(addr) = args.get(i) { @@ -133,6 +127,16 @@ fn main() { i += 1; } + let default_level = if verbose { "debug" } else { "info" }; + env_logger::Builder::from_env( + env_logger::Env::default().default_filter_or(default_level), + ) + .format(|buf, record| { + use std::io::Write; + writeln!(buf, "[{}]: {}", record.level(), record.args()) + }) + .init(); + let sock_path = sock.unwrap_or_else(|| dir.join("daemon.sock")); // Ensure directories exist @@ -288,7 +292,7 @@ fn main() { }) }); - daemon::run_daemon(&mut node, &store, &rx, &shutdown); + daemon::run_daemon(&mut node, &store, &rx, &shutdown, &bootstrap); let _ = std::fs::remove_file(&sock_path); let _ = handle.join(); diff --git a/src/daemon.rs b/src/daemon.rs index 12757a3..4895a48 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -30,6 +30,9 @@ const REPUBLISH_INTERVAL: Duration = Duration::from_secs(1800); /// How often to persist routing table and state (5 min). const SAVE_INTERVAL: Duration = Duration::from_secs(300); +/// How often to attempt re-join when the routing table is empty (60 s). +const REJOIN_INTERVAL: Duration = Duration::from_secs(60); + /// How often to sync DHT-replicated values to local store (5 s). const SYNC_INTERVAL: Duration = Duration::from_secs(5); @@ -45,11 +48,13 @@ pub fn run_daemon( store: &PasteStore, rx: &mpsc::Receiver<DaemonRequest>, shutdown: &AtomicBool, + bootstrap: &[String], ) { let mut last_gc = Instant::now(); let mut last_republish = Instant::now() - REPUBLISH_INTERVAL; let mut last_save = Instant::now(); let mut last_sync = Instant::now(); + let mut last_rejoin = Instant::now(); log::info!("daemon main loop started"); @@ -65,6 +70,37 @@ pub fn run_daemon( } } + // Re-join bootstrap nodes when the routing table is empty + if node.routing_table_size() == 0 + && !bootstrap.is_empty() + && last_rejoin.elapsed() >= REJOIN_INTERVAL + { + last_rejoin = Instant::now(); + log::warn!("routing table empty, re-joining bootstrap nodes"); + for peer in bootstrap { + let parts: Vec<&str> = peer.rsplitn(2, ':').collect(); + if parts.len() != 2 { + continue; + } + let host = parts[1]; + if let Ok(p) = parts[0].parse::<u16>() { + // Unban bootstrap addresses before re-joining + // so their replies are not silently dropped. + use std::net::ToSocketAddrs; + if let Ok(addrs) = format!("{host}:{p}").to_socket_addrs() { + for addr in addrs { + node.unban(&addr); + } + } + if let Err(e) = node.join(host, p) { + log::warn!("rejoin: failed to join {peer}: {e}"); + } else { + log::info!("rejoin: sent join to {peer}"); + } + } + } + } + if last_sync.elapsed() >= SYNC_INTERVAL { last_sync = Instant::now(); sync_dht_to_store(node, store); |