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