ant_node_manager/add_services/
mod.rs1pub mod config;
9#[cfg(test)]
10mod tests;
11
12use self::config::{AddDaemonServiceOptions, AddNodeServiceOptions, InstallNodeServiceCtxBuilder};
13use crate::{
14 DAEMON_SERVICE_NAME, VerbosityLevel,
15 config::{create_owned_dir, get_user_antnode_data_dir},
16 helpers::{check_port_availability, get_start_port_if_applicable, increment_port_option},
17};
18use ant_service_management::{
19 DaemonServiceData, NatDetectionStatus, NodeRegistryManager, NodeServiceData, ServiceStatus,
20 control::ServiceControl, node::NODE_SERVICE_DATA_SCHEMA_LATEST,
21};
22use color_eyre::{Help, Result, eyre::eyre};
23use colored::Colorize;
24use service_manager::ServiceInstallCtx;
25use std::{
26 ffi::OsString,
27 net::{IpAddr, Ipv4Addr, SocketAddr},
28};
29
30pub async fn add_node(
39 mut options: AddNodeServiceOptions,
40 node_registry: NodeRegistryManager,
41 service_control: &dyn ServiceControl,
42 verbosity: VerbosityLevel,
43) -> Result<Vec<String>> {
44 if options.init_peers_config.first {
45 if let Some(count) = options.count
46 && count > 1
47 {
48 error!("A genesis node can only be added as a single node");
49 return Err(eyre!("A genesis node can only be added as a single node"));
50 }
51
52 let mut genesis_node_exists = false;
53 for node in node_registry.nodes.read().await.iter() {
54 if node.read().await.initial_peers_config.first {
55 genesis_node_exists = true;
56 break;
57 }
58 }
59
60 if genesis_node_exists {
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).await?;
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).await?;
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).await?;
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.environment_variables.write().await = options.env_variables.clone();
93 node_registry.save().await?;
94 }
95
96 let mut added_service_data = vec![];
97 let mut failed_service_data = vec![];
98
99 let current_node_count = node_registry.nodes.read().await.len() as u16;
100 let target_node_count = current_node_count + options.count.unwrap_or(1);
101
102 let mut node_number = current_node_count + 1;
103 let mut node_port = get_start_port_if_applicable(options.node_port);
104 let mut metrics_port = get_start_port_if_applicable(options.metrics_port);
105 let mut rpc_port = get_start_port_if_applicable(options.rpc_port);
106
107 while node_number <= target_node_count {
108 trace!("Adding node with node_number {node_number}");
109 let rpc_free_port = if let Some(port) = rpc_port {
110 port
111 } else {
112 service_control.get_available_port()?
113 };
114 let metrics_free_port = if let Some(port) = metrics_port {
115 Some(port)
116 } else if options.enable_metrics_server {
117 Some(service_control.get_available_port()?)
118 } else {
119 None
120 };
121
122 let rpc_socket_addr = if let Some(addr) = options.rpc_address {
123 SocketAddr::new(IpAddr::V4(addr), rpc_free_port)
124 } else {
125 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), rpc_free_port)
126 };
127
128 let service_name = format!("antnode{node_number}");
129 let service_data_dir_path = options.service_data_dir_path.join(service_name.clone());
130 let service_antnode_path = service_data_dir_path.join(antnode_file_name.clone());
131
132 let default_log_dir_path = get_user_antnode_data_dir()?;
137 let service_log_dir_path =
138 if options.user_mode && options.service_log_dir_path == default_log_dir_path {
139 options
140 .service_log_dir_path
141 .join(service_name.clone())
142 .join("logs")
143 } else {
144 options.service_log_dir_path.join(service_name.clone())
145 };
146
147 if let Some(user) = &options.user {
148 debug!("Creating data_dir and log_dirs with user {user}");
149 create_owned_dir(service_data_dir_path.clone(), user)?;
150 create_owned_dir(service_log_dir_path.clone(), user)?;
151 } else {
152 debug!("Creating data_dir and log_dirs without user");
153 std::fs::create_dir_all(service_data_dir_path.clone())?;
154 std::fs::create_dir_all(service_log_dir_path.clone())?;
155 }
156
157 debug!("Copying antnode binary to {service_antnode_path:?}");
158 std::fs::copy(
159 options.antnode_src_path.clone(),
160 service_antnode_path.clone(),
161 )?;
162
163 if options.auto_set_nat_flags {
164 let nat_status = node_registry.nat_status.read().await;
165
166 match nat_status.as_ref() {
167 Some(NatDetectionStatus::Public) => {
168 options.no_upnp = true; options.relay = false;
170 }
171 Some(NatDetectionStatus::UPnP) => {
172 options.no_upnp = false;
173 options.relay = false;
174 }
175 Some(NatDetectionStatus::Private) => {
176 options.no_upnp = true;
177 options.relay = true;
178 }
179 None => {
180 options.no_upnp = true;
182 options.relay = true;
183 debug!("NAT status not set; defaulting to no_upnp=true and relay=true");
184 }
185 }
186
187 debug!(
188 "Auto-setting NAT flags: no_upnp={}, relay={}",
189 options.no_upnp, options.relay
190 );
191 }
192
193 let install_ctx = InstallNodeServiceCtxBuilder {
194 alpha: options.alpha,
195 autostart: options.auto_restart,
196 data_dir_path: service_data_dir_path.clone(),
197 env_variables: options.env_variables.clone(),
198 evm_network: options.evm_network.clone(),
199 relay: options.relay,
200 log_dir_path: service_log_dir_path.clone(),
201 log_format: options.log_format,
202 max_archived_log_files: options.max_archived_log_files,
203 max_log_files: options.max_log_files,
204 metrics_port: metrics_free_port,
205 name: service_name.clone(),
206 network_id: options.network_id,
207 node_ip: options.node_ip,
208 node_port,
209 init_peers_config: options.init_peers_config.clone(),
210 rewards_address: options.rewards_address,
211 rpc_socket_addr,
212 antnode_path: service_antnode_path.clone(),
213 service_user: options.user.clone(),
214 no_upnp: options.no_upnp,
215 write_older_cache_files: options.write_older_cache_files,
216 }
217 .build()?;
218
219 match service_control.install(install_ctx, options.user_mode) {
220 Ok(()) => {
221 info!("Successfully added service {service_name}");
222 added_service_data.push((
223 service_name.clone(),
224 service_antnode_path.to_string_lossy().into_owned(),
225 service_data_dir_path.to_string_lossy().into_owned(),
226 service_log_dir_path.to_string_lossy().into_owned(),
227 rpc_socket_addr,
228 ));
229
230 node_registry
231 .push_node(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 write_older_cache_files: options.write_older_cache_files,
263 })
264 .await;
265 node_registry.save().await?;
268 }
269 Err(e) => {
270 error!("Failed to add service {service_name}: {e}");
271 failed_service_data.push((service_name.clone(), e.to_string()));
272 }
273 }
274
275 node_number += 1;
276 node_port = increment_port_option(node_port);
277 metrics_port = increment_port_option(metrics_port);
278 rpc_port = increment_port_option(rpc_port);
279 }
280
281 if options.delete_antnode_src {
282 debug!("Deleting antnode binary file");
283 std::fs::remove_file(options.antnode_src_path)?;
284 }
285
286 if !added_service_data.is_empty() {
287 info!("Added {} services", added_service_data.len());
288 } else if !failed_service_data.is_empty() {
289 error!("Failed to add {} service(s)", failed_service_data.len());
290 }
291
292 if !added_service_data.is_empty() && verbosity != VerbosityLevel::Minimal {
293 println!("Services Added:");
294 for install in added_service_data.iter() {
295 println!(" {} {}", "✓".green(), install.0);
296 println!(" - Antnode path: {}", install.1);
297 println!(" - Data path: {}", install.2);
298 println!(" - Log path: {}", install.3);
299 println!(" - RPC port: {}", install.4);
300 }
301 println!("[!] Note: newly added services have not been started");
302 }
303
304 if !failed_service_data.is_empty() {
305 if verbosity != VerbosityLevel::Minimal {
306 println!("Failed to add {} service(s):", failed_service_data.len());
307 for failed in failed_service_data.iter() {
308 println!("{} {}: {}", "✕".red(), failed.0, failed.1);
309 }
310 }
311 return Err(eyre!("Failed to add one or more services")
312 .suggestion("However, any services that were successfully added will be usable."));
313 }
314
315 let added_services_names = added_service_data
316 .into_iter()
317 .map(|(name, ..)| name)
318 .collect();
319
320 Ok(added_services_names)
321}
322
323pub async fn add_daemon(
327 options: AddDaemonServiceOptions,
328 node_registry: NodeRegistryManager,
329 service_control: &dyn ServiceControl,
330) -> Result<()> {
331 if node_registry.daemon.read().await.is_some() {
332 error!("A antctld service has already been created");
333 return Err(eyre!("A antctld service has already been created"));
334 }
335
336 debug!(
337 "Copying daemon binary file to {:?}",
338 options.daemon_install_bin_path
339 );
340 std::fs::copy(
341 options.daemon_src_bin_path.clone(),
342 options.daemon_install_bin_path.clone(),
343 )?;
344
345 let install_ctx = ServiceInstallCtx {
346 args: vec![
347 OsString::from("--port"),
348 OsString::from(options.port.to_string()),
349 OsString::from("--address"),
350 OsString::from(options.address.to_string()),
351 ],
352 autostart: true,
353 contents: None,
354 environment: options.env_variables,
355 label: DAEMON_SERVICE_NAME.parse()?,
356 program: options.daemon_install_bin_path.clone(),
357 username: Some(options.user),
358 working_directory: None,
359 disable_restart_on_failure: false,
360 };
361
362 match service_control.install(install_ctx, false) {
363 Ok(()) => {
364 let daemon = DaemonServiceData {
365 daemon_path: options.daemon_install_bin_path.clone(),
366 endpoint: Some(SocketAddr::new(IpAddr::V4(options.address), options.port)),
367 pid: None,
368 service_name: DAEMON_SERVICE_NAME.to_string(),
369 status: ServiceStatus::Added,
370 version: options.version,
371 };
372
373 node_registry.insert_daemon(daemon).await;
374 info!("Daemon service has been added successfully");
375 println!("Daemon service added {}", "✓".green());
376 println!("[!] Note: the service has not been started");
377 node_registry.save().await?;
378 std::fs::remove_file(options.daemon_src_bin_path)?;
379 Ok(())
380 }
381 Err(e) => {
382 error!("Failed to add daemon service: {e}");
383 println!("Failed to add daemon service: {e}");
384 Err(e.into())
385 }
386 }
387}