1use std::net::SocketAddr;
4use std::path::Path;
5use std::sync::{Arc, Mutex};
6
7use rns_crypto::identity::Identity;
8use rns_crypto::Rng;
9
10use crate::api;
11use crate::args::Args;
12use crate::{bridge, config, encode, server, state};
13
14pub fn run(args: Args) {
15 if args.has("help") {
16 print_help();
17 return;
18 }
19
20 if args.has("version") {
21 println!("rns-ctl {}", env!("FULL_VERSION"));
22 return;
23 }
24
25 let log_level = match args.verbosity {
27 0 => "info",
28 1 => "debug",
29 _ => "trace",
30 };
31 if std::env::var("RUST_LOG").is_err() {
32 std::env::set_var(
33 "RUST_LOG",
34 format!(
35 "rns_ctl={},rns_net={},rns_hooks={}",
36 log_level, log_level, log_level
37 ),
38 );
39 }
40 env_logger::init();
41
42 let mut cfg = config::from_args_and_env(&args);
43
44 if cfg.auth_token.is_none() && !cfg.disable_auth {
46 let mut token_bytes = [0u8; 24];
47 rns_crypto::OsRng.fill_bytes(&mut token_bytes);
48 let token = encode::to_hex(&token_bytes);
49 log::info!("Generated auth token: {}", token);
50 println!("Auth token: {}", token);
51 cfg.auth_token = Some(token);
52 }
53
54 let shared_state = Arc::new(std::sync::RwLock::new(state::CtlState::new()));
56 let ws_broadcast: state::WsBroadcast = Arc::new(Mutex::new(Vec::new()));
57
58 let callbacks = Box::new(bridge::CtlCallbacks::new(
60 shared_state.clone(),
61 ws_broadcast.clone(),
62 ));
63
64 let config_path = cfg.config_path.as_deref().map(Path::new);
66
67 log::info!("Starting RNS node...");
69 let node = if cfg.daemon_mode {
70 log::info!("Connecting as shared client (daemon mode)");
71 rns_net::RnsNode::connect_shared_from_config(config_path, callbacks)
72 } else {
73 rns_net::RnsNode::from_config(config_path, callbacks)
74 };
75
76 let node = match node {
77 Ok(n) => n,
78 Err(e) => {
79 log::error!("Failed to start node: {}", e);
80 std::process::exit(1);
81 }
82 };
83
84 let config_dir = rns_net::storage::resolve_config_dir(config_path);
86 let paths = rns_net::storage::ensure_storage_dirs(&config_dir).ok();
87 let identity: Option<Identity> = paths
88 .as_ref()
89 .and_then(|p| rns_net::storage::load_or_create_identity(&p.identities).ok());
90
91 {
93 let mut s = shared_state.write().unwrap();
94 if let Some(ref id) = identity {
95 s.identity_hash = Some(*id.hash());
96 if let Some(prv) = id.get_private_key() {
98 s.identity = Some(Identity::from_private_key(&prv));
99 }
100 }
101 }
102
103 let node_handle: api::NodeHandle = Arc::new(Mutex::new(Some(node)));
105 let node_for_shutdown = node_handle.clone();
106
107 {
109 let mut s = shared_state.write().unwrap();
110 s.node_handle = Some(node_handle.clone());
111 }
112
113 let shutdown_flag = Arc::new(std::sync::atomic::AtomicBool::new(false));
115 let shutdown_flag_handler = shutdown_flag.clone();
116
117 ctrlc_handler(move || {
118 if shutdown_flag_handler.swap(true, std::sync::atomic::Ordering::SeqCst) {
119 std::process::exit(1);
121 }
122 log::info!("Shutting down...");
123 if let Some(node) = node_for_shutdown.lock().unwrap().take() {
124 node.shutdown();
125 }
126 std::process::exit(0);
127 });
128
129 #[cfg(feature = "tls")]
131 let tls_config = {
132 match (&cfg.tls_cert, &cfg.tls_key) {
133 (Some(cert), Some(key)) => match crate::tls::load_tls_config(cert, key) {
134 Ok(config) => {
135 log::info!("TLS enabled with cert={} key={}", cert, key);
136 Some(config)
137 }
138 Err(e) => {
139 log::error!("Failed to load TLS config: {}", e);
140 std::process::exit(1);
141 }
142 },
143 (Some(_), None) | (None, Some(_)) => {
144 log::error!("Both --tls-cert and --tls-key must be provided together");
145 std::process::exit(1);
146 }
147 (None, None) => None,
148 }
149 };
150
151 #[cfg(not(feature = "tls"))]
152 {
153 if cfg.tls_cert.is_some() || cfg.tls_key.is_some() {
154 log::error!(
155 "TLS options require the 'tls' feature. Rebuild with: cargo build --features tls"
156 );
157 std::process::exit(1);
158 }
159 }
160
161 let ctx = Arc::new(server::ServerContext {
163 node: node_handle,
164 state: shared_state,
165 ws_broadcast,
166 config: cfg,
167 #[cfg(feature = "tls")]
168 tls_config,
169 });
170
171 let addr: SocketAddr = format!("{}:{}", ctx.config.host, ctx.config.port)
172 .parse()
173 .unwrap_or_else(|_| {
174 log::error!("Invalid bind address");
175 std::process::exit(1);
176 });
177
178 if let Err(e) = server::run_server(addr, ctx) {
180 log::error!("Server error: {}", e);
181 std::process::exit(1);
182 }
183}
184
185fn ctrlc_handler<F: FnOnce() + Send + 'static>(handler: F) {
187 let handler = Mutex::new(Some(handler));
188 libc_signal(move || {
189 if let Some(f) = handler.lock().unwrap().take() {
190 f();
191 }
192 });
193}
194
195fn libc_signal<F: FnMut() + Send + 'static>(mut callback: F) {
197 std::thread::Builder::new()
198 .name("signal-handler".into())
199 .spawn(move || {
200 use std::sync::atomic::{AtomicBool, Ordering};
201 static SIGNALED: AtomicBool = AtomicBool::new(false);
202
203 #[cfg(unix)]
204 {
205 extern "C" fn sig_handler(_: i32) {
206 SIGNALED.store(true, std::sync::atomic::Ordering::SeqCst);
207 }
208 unsafe {
209 libc_ffi::signal(libc_ffi::SIGINT, sig_handler as *const () as usize);
210 }
211 }
212
213 loop {
214 std::thread::sleep(std::time::Duration::from_millis(100));
215 if SIGNALED.swap(false, Ordering::SeqCst) {
216 callback();
217 break;
218 }
219 }
220 })
221 .ok();
222}
223
224#[cfg(unix)]
225mod libc_ffi {
226 extern "C" {
227 pub fn signal(sig: i32, handler: usize) -> usize;
228 }
229 pub const SIGINT: i32 = 2;
230}
231
232fn print_help() {
233 println!(
234 "rns-ctl http - HTTP/WebSocket control interface for Reticulum
235
236USAGE:
237 rns-ctl http [OPTIONS]
238
239OPTIONS:
240 -c, --config PATH Path to RNS config directory
241 -p, --port PORT HTTP port (default: 8080, env: RNSCTL_HTTP_PORT)
242 -H, --host HOST Bind host (default: 127.0.0.1, env: RNSCTL_HOST)
243 -t, --token TOKEN Auth bearer token (env: RNSCTL_AUTH_TOKEN)
244 -d, --daemon Connect as client to running rnsd
245 --disable-auth Disable authentication
246 --tls-cert PATH TLS certificate file (env: RNSCTL_TLS_CERT, requires 'tls' feature)
247 --tls-key PATH TLS private key file (env: RNSCTL_TLS_KEY, requires 'tls' feature)
248 -v Increase verbosity (repeat for more)
249 -h, --help Show this help
250 --version Show version"
251 );
252}