From 85d3e28c858e160167ba637f7146be9b6f7f6113 Mon Sep 17 00:00:00 2001 From: ospab Date: Fri, 15 May 2026 22:25:35 +0300 Subject: [PATCH] feat: implement native public IP autodetection via ip r and interactive cached prompt fallback for server links --- ostp/src/main.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/ostp/src/main.rs b/ostp/src/main.rs index f94915f..9a15c3d 100644 --- a/ostp/src/main.rs +++ b/ostp/src/main.rs @@ -191,6 +191,79 @@ async fn main() -> Result<()> { Ok(()) } +#[allow(dead_code)] +fn is_private_ip(ip: &str) -> bool { + ip.starts_with("10.") + || ip.starts_with("192.168.") + || ip.starts_with("127.") + || (ip.starts_with("172.") && { + let parts: Vec<&str> = ip.split('.').collect(); + if parts.len() >= 2 { + if let Ok(second) = parts[1].parse::() { + (16..=31).contains(&second) + } else { false } + } else { false } + }) +} + +fn detect_local_public_ip() -> Option { + #[cfg(not(target_os = "windows"))] + { + let out = std::process::Command::new("ip") + .args(["-4", "addr", "show", "scope", "global"]) + .output() + .ok()?; + + let text = String::from_utf8_lossy(&out.stdout); + for line in text.lines() { + if let Some(idx) = line.find("inet ") { + let substr = &line[idx + 5..]; + let ip = substr.split(|c: char| c == '/' || c.is_whitespace()).next().unwrap_or(""); + if !ip.is_empty() && !is_private_ip(ip) { + return Some(ip.to_string()); + } + } + } + } + None +} + +fn get_or_ask_public_ip(config_path: &std::path::Path) -> String { + let config_dir = config_path.parent().unwrap_or_else(|| std::path::Path::new(".")); + let cache_path = config_dir.join(".ostp_public_ip"); + + if cache_path.exists() { + if let Ok(cached) = std::fs::read_to_string(&cache_path) { + let ip = cached.trim().to_string(); + if !ip.is_empty() { + return ip; + } + } + } + + if let Some(detected) = detect_local_public_ip() { + println!("[OSTP Core] Auto-detected public network IP: {}", detected); + let _ = std::fs::write(&cache_path, &detected); + return detected; + } + + print!("\n[OSTP Core] Could not automatically detect your Server's Public IP.\n"); + print!(">>> Please enter your Public IP or Domain Name for user links: "); + use std::io::Write; + let _ = std::io::stdout().flush(); + + let mut input = String::new(); + if std::io::stdin().read_line(&mut input).is_ok() { + let ip = input.trim().to_string(); + if !ip.is_empty() { + let _ = std::fs::write(&cache_path, &ip); + return ip; + } + } + + "".to_string() +} + async fn run_app() -> Result<()> { let args = Args::parse(); @@ -262,8 +335,9 @@ async fn run_app() -> Result<()> { if is_server { if let AppMode::Server(s) = dummy.mode { let key = &s.access_keys[0]; + let host = get_or_ask_public_ip(&args.config); println!("\n>>> Handy Client Share Link for your users:"); - println!(" ostp://{}@:50000", key); + println!(" ostp://{}@{}:50000", key, host); } } return Ok(()); @@ -293,7 +367,11 @@ async fn run_app() -> Result<()> { let listen = server_cfg.listen.clone(); let parts: Vec<&str> = listen.split(':').collect(); let port = parts.get(1).unwrap_or(&"50000"); - let host = if parts[0] == "0.0.0.0" { "" } else { parts[0] }; + let host = if parts[0] == "0.0.0.0" { + get_or_ask_public_ip(&args.config) + } else { + parts[0].to_string() + }; println!("\n>>> Ready-to-use OSTP client share links from {:?}:", args.config); for (idx, key) in server_cfg.access_keys.iter().enumerate() {