Commit Diff


commit - 24c3402c9d27fc26bd3afe8c05276f52338514f8
commit + 4214189961d59b9de15e221a9c4b229c0d646e78
blob - 5c4c473954d38d0fa3522989b5154891ebf285f6
blob + fbfc872224480a36a65421ebed91b54ff53d5630
--- src/bin/tp.rs
+++ 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
blob - e1ebc7b34d8abe1f842c306dbb5307671f5be518
blob + c18d88b92d7fc71bcd98c7178eb35a5cd059ff41
--- src/bin/tpd.rs
+++ 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();
blob - 12757a31f72beda55941bba8bae207bd57e02626
blob + 4895a487d322f683df8f0146b20f1f655eb86421
--- src/daemon.rs
+++ src/daemon.rs
@@ -30,6 +30,9 @@ const REPUBLISH_INTERVAL: Duration = Duration::from_se
 /// 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);