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