1pub mod config;
9#[cfg(test)]
10mod tests;
11
12use self::config::{
13 AddAuditorServiceOptions, AddDaemonServiceOptions, AddFaucetServiceOptions,
14 AddNodeServiceOptions, InstallAuditorServiceCtxBuilder, InstallFaucetServiceCtxBuilder,
15 InstallNodeServiceCtxBuilder,
16};
17use crate::{
18 config::{create_owned_dir, get_user_antnode_data_dir},
19 helpers::{check_port_availability, get_start_port_if_applicable, increment_port_option},
20 VerbosityLevel, DAEMON_SERVICE_NAME,
21};
22use ant_service_management::{
23 auditor::AuditorServiceData, control::ServiceControl, DaemonServiceData, FaucetServiceData,
24 NatDetectionStatus, NodeRegistry, NodeServiceData, ServiceStatus,
25};
26use color_eyre::{
27 eyre::{eyre, OptionExt},
28 Help, Result,
29};
30use colored::Colorize;
31use service_manager::ServiceInstallCtx;
32use std::{
33 ffi::OsString,
34 net::{IpAddr, Ipv4Addr, SocketAddr},
35};
36
37pub async fn add_node(
46 mut options: AddNodeServiceOptions,
47 node_registry: &mut NodeRegistry,
48 service_control: &dyn ServiceControl,
49 verbosity: VerbosityLevel,
50) -> Result<Vec<String>> {
51 if options.init_peers_config.first {
52 if let Some(count) = options.count {
53 if count > 1 {
54 error!("A genesis node can only be added as a single node");
55 return Err(eyre!("A genesis node can only be added as a single node"));
56 }
57 }
58
59 let genesis_node = node_registry.nodes.iter().find(|n| n.peers_args.first);
60 if genesis_node.is_some() {
61 error!("A genesis node already exists");
62 return Err(eyre!("A genesis node already exists"));
63 }
64 }
65
66 if let Some(port_option) = &options.node_port {
67 port_option.validate(options.count.unwrap_or(1))?;
68 check_port_availability(port_option, &node_registry.nodes)?;
69 }
70
71 if let Some(port_option) = &options.metrics_port {
72 port_option.validate(options.count.unwrap_or(1))?;
73 check_port_availability(port_option, &node_registry.nodes)?;
74 }
75
76 if let Some(port_option) = &options.rpc_port {
77 port_option.validate(options.count.unwrap_or(1))?;
78 check_port_availability(port_option, &node_registry.nodes)?;
79 }
80
81 let antnode_file_name = options
82 .antnode_src_path
83 .file_name()
84 .ok_or_else(|| {
85 error!("Could not get filename from the antnode download path");
86 eyre!("Could not get filename from the antnode download path")
87 })?
88 .to_string_lossy()
89 .to_string();
90
91 if options.env_variables.is_some() {
92 node_registry
93 .environment_variables
94 .clone_from(&options.env_variables);
95 node_registry.save()?;
96 }
97
98 let mut added_service_data = vec![];
99 let mut failed_service_data = vec![];
100
101 let current_node_count = node_registry.nodes.len() as u16;
102 let target_node_count = current_node_count + options.count.unwrap_or(1);
103
104 let mut node_number = current_node_count + 1;
105 let mut node_port = get_start_port_if_applicable(options.node_port);
106 let mut metrics_port = get_start_port_if_applicable(options.metrics_port);
107 let mut rpc_port = get_start_port_if_applicable(options.rpc_port);
108
109 while node_number <= target_node_count {
110 trace!("Adding node with node_number {node_number}");
111 let rpc_free_port = if let Some(port) = rpc_port {
112 port
113 } else {
114 service_control.get_available_port()?
115 };
116 let metrics_free_port = if let Some(port) = metrics_port {
117 Some(port)
118 } else if options.enable_metrics_server {
119 Some(service_control.get_available_port()?)
120 } else {
121 None
122 };
123
124 let rpc_socket_addr = if let Some(addr) = options.rpc_address {
125 SocketAddr::new(IpAddr::V4(addr), rpc_free_port)
126 } else {
127 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), rpc_free_port)
128 };
129
130 let service_name = format!("antnode{node_number}");
131 let service_data_dir_path = options.service_data_dir_path.join(service_name.clone());
132 let service_antnode_path = service_data_dir_path.join(antnode_file_name.clone());
133
134 let default_log_dir_path = get_user_antnode_data_dir()?;
139 let service_log_dir_path =
140 if options.user_mode && options.service_log_dir_path == default_log_dir_path {
141 options
142 .service_log_dir_path
143 .join(service_name.clone())
144 .join("logs")
145 } else {
146 options.service_log_dir_path.join(service_name.clone())
147 };
148
149 if let Some(user) = &options.user {
150 debug!("Creating data_dir and log_dirs with user {user}");
151 create_owned_dir(service_data_dir_path.clone(), user)?;
152 create_owned_dir(service_log_dir_path.clone(), user)?;
153 } else {
154 debug!("Creating data_dir and log_dirs without user");
155 std::fs::create_dir_all(service_data_dir_path.clone())?;
156 std::fs::create_dir_all(service_log_dir_path.clone())?;
157 }
158
159 debug!("Copying antnode binary to {service_antnode_path:?}");
160 std::fs::copy(
161 options.antnode_src_path.clone(),
162 service_antnode_path.clone(),
163 )?;
164
165 if options.auto_set_nat_flags {
166 let nat_status = node_registry
167 .nat_status
168 .clone()
169 .ok_or_eyre("NAT status has not been set. Run 'nat-detection' first")?;
170
171 match nat_status {
172 NatDetectionStatus::Public => {
173 options.no_upnp = true; options.relay = false;
175 }
176 NatDetectionStatus::UPnP => {
177 options.no_upnp = false;
178 options.relay = false;
179 }
180 NatDetectionStatus::Private => {
181 options.no_upnp = true;
182 options.relay = true;
183 }
184 }
185 debug!(
186 "Auto-setting NAT flags: upnp={}, relay={}",
187 !options.no_upnp, options.relay
188 );
189 }
190
191 let install_ctx = InstallNodeServiceCtxBuilder {
192 autostart: options.auto_restart,
193 data_dir_path: service_data_dir_path.clone(),
194 env_variables: options.env_variables.clone(),
195 evm_network: options.evm_network.clone(),
196 home_network: options.relay,
197 log_dir_path: service_log_dir_path.clone(),
198 log_format: options.log_format,
199 max_archived_log_files: options.max_archived_log_files,
200 max_log_files: options.max_log_files,
201 metrics_port: metrics_free_port,
202 name: service_name.clone(),
203 network_id: options.network_id,
204 node_ip: options.node_ip,
205 node_port,
206 init_peers_config: options.init_peers_config.clone(),
207 rewards_address: options.rewards_address,
208 rpc_socket_addr,
209 antnode_path: service_antnode_path.clone(),
210 service_user: options.user.clone(),
211 upnp: !options.no_upnp,
212 }
213 .build()?;
214
215 match service_control.install(install_ctx, options.user_mode) {
216 Ok(()) => {
217 info!("Successfully added service {service_name}");
218 added_service_data.push((
219 service_name.clone(),
220 service_antnode_path.to_string_lossy().into_owned(),
221 service_data_dir_path.to_string_lossy().into_owned(),
222 service_log_dir_path.to_string_lossy().into_owned(),
223 rpc_socket_addr,
224 ));
225
226 node_registry.nodes.push(NodeServiceData {
227 antnode_path: service_antnode_path,
228 auto_restart: options.auto_restart,
229 connected_peers: None,
230 data_dir_path: service_data_dir_path.clone(),
231 evm_network: options.evm_network.clone(),
232 home_network: options.relay,
233 listen_addr: None,
234 log_dir_path: service_log_dir_path.clone(),
235 log_format: options.log_format,
236 max_archived_log_files: options.max_archived_log_files,
237 max_log_files: options.max_log_files,
238 metrics_port: metrics_free_port,
239 network_id: options.network_id,
240 node_ip: options.node_ip,
241 node_port,
242 number: node_number,
243 rewards_address: options.rewards_address,
244 reward_balance: None,
245 rpc_socket_addr,
246 peer_id: None,
247 peers_args: options.init_peers_config.clone(),
248 pid: None,
249 service_name,
250 status: ServiceStatus::Added,
251 upnp: !options.no_upnp,
252 user: options.user.clone(),
253 user_mode: options.user_mode,
254 version: options.version.clone(),
255 });
256 node_registry.save()?;
259 }
260 Err(e) => {
261 error!("Failed to add service {service_name}: {e}");
262 failed_service_data.push((service_name.clone(), e.to_string()));
263 }
264 }
265
266 node_number += 1;
267 node_port = increment_port_option(node_port);
268 metrics_port = increment_port_option(metrics_port);
269 rpc_port = increment_port_option(rpc_port);
270 }
271
272 if options.delete_antnode_src {
273 debug!("Deleting antnode binary file");
274 std::fs::remove_file(options.antnode_src_path)?;
275 }
276
277 if !added_service_data.is_empty() {
278 info!("Added {} services", added_service_data.len());
279 } else if !failed_service_data.is_empty() {
280 error!("Failed to add {} service(s)", failed_service_data.len());
281 }
282
283 if !added_service_data.is_empty() && verbosity != VerbosityLevel::Minimal {
284 println!("Services Added:");
285 for install in added_service_data.iter() {
286 println!(" {} {}", "✓".green(), install.0);
287 println!(" - Antnode path: {}", install.1);
288 println!(" - Data path: {}", install.2);
289 println!(" - Log path: {}", install.3);
290 println!(" - RPC port: {}", install.4);
291 }
292 println!("[!] Note: newly added services have not been started");
293 }
294
295 if !failed_service_data.is_empty() {
296 if verbosity != VerbosityLevel::Minimal {
297 println!("Failed to add {} service(s):", failed_service_data.len());
298 for failed in failed_service_data.iter() {
299 println!("{} {}: {}", "✕".red(), failed.0, failed.1);
300 }
301 }
302 return Err(eyre!("Failed to add one or more services")
303 .suggestion("However, any services that were successfully added will be usable."));
304 }
305
306 let added_services_names = added_service_data
307 .into_iter()
308 .map(|(name, ..)| name)
309 .collect();
310
311 Ok(added_services_names)
312}
313
314pub fn add_auditor(
321 install_options: AddAuditorServiceOptions,
322 node_registry: &mut NodeRegistry,
323 service_control: &dyn ServiceControl,
324 verbosity: VerbosityLevel,
325) -> Result<()> {
326 if node_registry.auditor.is_some() {
327 error!("An Auditor service has already been created");
328 return Err(eyre!("An Auditor service has already been created"));
329 }
330
331 debug!(
332 "Creating log directory at {:?} as user {:?}",
333 install_options.service_log_dir_path, install_options.user
334 );
335 create_owned_dir(
336 install_options.service_log_dir_path.clone(),
337 &install_options.user,
338 )?;
339
340 debug!(
341 "Copying auditor binary file to {:?}",
342 install_options.auditor_install_bin_path
343 );
344 std::fs::copy(
345 install_options.auditor_src_bin_path.clone(),
346 install_options.auditor_install_bin_path.clone(),
347 )?;
348
349 let install_ctx = InstallAuditorServiceCtxBuilder {
350 auditor_path: install_options.auditor_install_bin_path.clone(),
351 beta_encryption_key: install_options.beta_encryption_key.clone(),
352 env_variables: install_options.env_variables.clone(),
353 log_dir_path: install_options.service_log_dir_path.clone(),
354 name: "auditor".to_string(),
355 service_user: install_options.user.clone(),
356 }
357 .build()?;
358
359 match service_control.install(install_ctx, false) {
360 Ok(()) => {
361 node_registry.auditor = Some(AuditorServiceData {
362 auditor_path: install_options.auditor_install_bin_path.clone(),
363 log_dir_path: install_options.service_log_dir_path.clone(),
364 pid: None,
365 service_name: "auditor".to_string(),
366 status: ServiceStatus::Added,
367 user: install_options.user.clone(),
368 version: install_options.version,
369 });
370 info!("Auditor service has been added successfully");
371 println!("Auditor service added {}", "✓".green());
372 if verbosity != VerbosityLevel::Minimal {
373 println!(
374 " - Bin path: {}",
375 install_options.auditor_install_bin_path.to_string_lossy()
376 );
377 println!(
378 " - Log path: {}",
379 install_options.service_log_dir_path.to_string_lossy()
380 );
381 }
382 println!("[!] Note: the service has not been started");
383 debug!("Removing auditor binary file");
384 std::fs::remove_file(install_options.auditor_src_bin_path)?;
385 node_registry.save()?;
386 Ok(())
387 }
388 Err(e) => {
389 error!("Failed to add auditor service: {e}");
390 println!("Failed to add auditor service: {e}");
391 Err(e.into())
392 }
393 }
394}
395
396pub fn add_daemon(
400 options: AddDaemonServiceOptions,
401 node_registry: &mut NodeRegistry,
402 service_control: &dyn ServiceControl,
403) -> Result<()> {
404 if node_registry.daemon.is_some() {
405 error!("A antctld service has already been created");
406 return Err(eyre!("A antctld service has already been created"));
407 }
408
409 debug!(
410 "Copying daemon binary file to {:?}",
411 options.daemon_install_bin_path
412 );
413 std::fs::copy(
414 options.daemon_src_bin_path.clone(),
415 options.daemon_install_bin_path.clone(),
416 )?;
417
418 let install_ctx = ServiceInstallCtx {
419 args: vec![
420 OsString::from("--port"),
421 OsString::from(options.port.to_string()),
422 OsString::from("--address"),
423 OsString::from(options.address.to_string()),
424 ],
425 autostart: true,
426 contents: None,
427 environment: options.env_variables,
428 label: DAEMON_SERVICE_NAME.parse()?,
429 program: options.daemon_install_bin_path.clone(),
430 username: Some(options.user),
431 working_directory: None,
432 disable_restart_on_failure: false,
433 };
434
435 match service_control.install(install_ctx, false) {
436 Ok(()) => {
437 let daemon = DaemonServiceData {
438 daemon_path: options.daemon_install_bin_path.clone(),
439 endpoint: Some(SocketAddr::new(IpAddr::V4(options.address), options.port)),
440 pid: None,
441 service_name: DAEMON_SERVICE_NAME.to_string(),
442 status: ServiceStatus::Added,
443 version: options.version,
444 };
445 node_registry.daemon = Some(daemon);
446 info!("Daemon service has been added successfully");
447 println!("Daemon service added {}", "✓".green());
448 println!("[!] Note: the service has not been started");
449 node_registry.save()?;
450 std::fs::remove_file(options.daemon_src_bin_path)?;
451 Ok(())
452 }
453 Err(e) => {
454 error!("Failed to add daemon service: {e}");
455 println!("Failed to add daemon service: {e}");
456 Err(e.into())
457 }
458 }
459}
460
461pub fn add_faucet(
468 install_options: AddFaucetServiceOptions,
469 node_registry: &mut NodeRegistry,
470 service_control: &dyn ServiceControl,
471 verbosity: VerbosityLevel,
472) -> Result<()> {
473 if node_registry.faucet.is_some() {
474 error!("A faucet service has already been created");
475 return Err(eyre!("A faucet service has already been created"));
476 }
477
478 debug!(
479 "Creating log directory at {:?} as user {:?}",
480 install_options.service_log_dir_path, install_options.user
481 );
482 create_owned_dir(
483 install_options.service_log_dir_path.clone(),
484 &install_options.user,
485 )?;
486 debug!(
487 "Copying faucet binary file to {:?}",
488 install_options.faucet_install_bin_path
489 );
490 std::fs::copy(
491 install_options.faucet_src_bin_path.clone(),
492 install_options.faucet_install_bin_path.clone(),
493 )?;
494
495 let install_ctx = InstallFaucetServiceCtxBuilder {
496 env_variables: install_options.env_variables.clone(),
497 faucet_path: install_options.faucet_install_bin_path.clone(),
498 local: install_options.local,
499 log_dir_path: install_options.service_log_dir_path.clone(),
500 name: "faucet".to_string(),
501 service_user: install_options.user.clone(),
502 }
503 .build()?;
504
505 match service_control.install(install_ctx, false) {
506 Ok(()) => {
507 node_registry.faucet = Some(FaucetServiceData {
508 faucet_path: install_options.faucet_install_bin_path.clone(),
509 local: false,
510 log_dir_path: install_options.service_log_dir_path.clone(),
511 pid: None,
512 service_name: "faucet".to_string(),
513 status: ServiceStatus::Added,
514 user: install_options.user.clone(),
515 version: install_options.version,
516 });
517 info!("Faucet service has been added successfully");
518 println!("Faucet service added {}", "✓".green());
519 if verbosity != VerbosityLevel::Minimal {
520 println!(
521 " - Bin path: {}",
522 install_options.faucet_install_bin_path.to_string_lossy()
523 );
524 println!(
525 " - Data path: {}",
526 install_options.service_data_dir_path.to_string_lossy()
527 );
528 println!(
529 " - Log path: {}",
530 install_options.service_log_dir_path.to_string_lossy()
531 );
532 }
533 println!("[!] Note: the service has not been started");
534 std::fs::remove_file(install_options.faucet_src_bin_path)?;
535 node_registry.save()?;
536 Ok(())
537 }
538 Err(e) => {
539 error!("Failed to add faucet service: {e}");
540 println!("Failed to add faucet service: {e}");
541 Err(e.into())
542 }
543 }
544}