aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/bin/tp.rs16
-rw-r--r--src/bin/tpd.rs24
-rw-r--r--src/daemon.rs36
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);