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
115 let metrics_free_port = if let Some(port) = metrics_port {
116 Some(port)
117 } else if options.enable_metrics_server {
118 Some(service_control.get_available_port()?)
119 } else {
120 None
121 };
122
123 let node_free_port = if let Some(port) = node_port {
124 Some(port)
125 } else {
126 Some(service_control.get_available_port()?)
127 };
128
129 let rpc_socket_addr = if let Some(addr) = options.rpc_address {
130 SocketAddr::new(IpAddr::V4(addr), rpc_free_port)
131 } else {
132 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), rpc_free_port)
133 };
134
135 let service_name = format!("antnode{node_number}");
136 let service_data_dir_path = options.service_data_dir_path.join(service_name.clone());
137 let service_antnode_path = service_data_dir_path.join(antnode_file_name.clone());
138
139 let default_log_dir_path = get_user_antnode_data_dir()?;
144 let service_log_dir_path =
145 if options.user_mode && options.service_log_dir_path == default_log_dir_path {
146 options
147 .service_log_dir_path
148 .join(service_name.clone())
149 .join("logs")
150 } else {
151 options.service_log_dir_path.join(service_name.clone())
152 };
153
154 if let Some(user) = &options.user {
155 debug!("Creating data_dir and log_dirs with user {user}");
156 create_owned_dir(service_data_dir_path.clone(), user)?;
157 create_owned_dir(service_log_dir_path.clone(), user)?;
158 } else {
159 debug!("Creating data_dir and log_dirs without user");
160 std::fs::create_dir_all(service_data_dir_path.clone())?;
161 std::fs::create_dir_all(service_log_dir_path.clone())?;
162 }
163
164 debug!("Copying antnode binary to {service_antnode_path:?}");
165 std::fs::copy(
166 options.antnode_src_path.clone(),
167 service_antnode_path.clone(),
168 )?;
169
170 if options.auto_set_nat_flags {
171 let nat_status = node_registry.nat_status.read().await;
172
173 match nat_status.as_ref() {
174 Some(NatDetectionStatus::Public) => {
175 options.no_upnp = true; options.relay = false;
177 }
178 Some(NatDetectionStatus::UPnP) => {
179 options.no_upnp = false;
180 options.relay = false;
181 }
182 Some(NatDetectionStatus::Private) => {
183 options.no_upnp = true;
184 options.relay = true;
185 }
186 None => {
187 options.no_upnp = true;
189 options.relay = true;
190 debug!("NAT status not set; defaulting to no_upnp=true and relay=true");
191 }
192 }
193
194 debug!(
195 "Auto-setting NAT flags: no_upnp={}, relay={}",
196 options.no_upnp, options.relay
197 );
198 }
199
200 let install_ctx = InstallNodeServiceCtxBuilder {
201 alpha: options.alpha,
202 antnode_path: service_antnode_path.clone(),
203 autostart: options.auto_restart,
204 data_dir_path: service_data_dir_path.clone(),
205 env_variables: options.env_variables.clone(),
206 evm_network: options.evm_network.clone(),
207 init_peers_config: options.init_peers_config.clone(),
208 log_dir_path: service_log_dir_path.clone(),
209 log_format: options.log_format,
210 max_archived_log_files: options.max_archived_log_files,
211 max_log_files: options.max_log_files,
212 metrics_port: metrics_free_port,
213 name: service_name.clone(),
214 network_id: options.network_id,
215 no_upnp: options.no_upnp,
216 node_ip: options.node_ip,
217 node_port: node_free_port,
218 relay: options.relay,
219 restart_policy: options.restart_policy,
220 rewards_address: options.rewards_address,
221 rpc_socket_addr,
222 service_user: options.user.clone(),
223 stop_on_upgrade: true,
224 write_older_cache_files: options.write_older_cache_files,
225 }
226 .build()?;
227
228 match service_control.install(install_ctx, options.user_mode) {
229 Ok(()) => {
230 info!("Successfully added service {service_name}");
231 added_service_data.push((
232 service_name.clone(),
233 service_antnode_path.to_string_lossy().into_owned(),
234 service_data_dir_path.to_string_lossy().into_owned(),
235 service_log_dir_path.to_string_lossy().into_owned(),
236 rpc_socket_addr,
237 ));
238
239 node_registry
240 .push_node(NodeServiceData {
241 alpha: options.alpha,
242 antnode_path: service_antnode_path,
243 auto_restart: options.auto_restart,
244 connected_peers: None,
245 data_dir_path: service_data_dir_path.clone(),
246 evm_network: options.evm_network.clone(),
247 relay: options.relay,
248 initial_peers_config: options.init_peers_config.clone(),
249 listen_addr: None,
250 log_dir_path: service_log_dir_path.clone(),
251 log_format: options.log_format,
252 max_archived_log_files: options.max_archived_log_files,
253 max_log_files: options.max_log_files,
254 metrics_port: metrics_free_port,
255 network_id: options.network_id,
256 node_ip: options.node_ip,
257 node_port: node_free_port,
258 number: node_number,
259 rewards_address: options.rewards_address,
260 reward_balance: None,
261 rpc_socket_addr,
262 peer_id: None,
263 pid: None,
264 schema_version: NODE_SERVICE_DATA_SCHEMA_LATEST,
265 service_name,
266 status: ServiceStatus::Added,
267 no_upnp: options.no_upnp,
268 user: options.user.clone(),
269 user_mode: options.user_mode,
270 version: options.version.clone(),
271 write_older_cache_files: options.write_older_cache_files,
272 })
273 .await;
274 node_registry.save().await?;
277 }
278 Err(e) => {
279 error!("Failed to add service {service_name}: {e}");
280 failed_service_data.push((service_name.clone(), e.to_string()));
281 }
282 }
283
284 node_number += 1;
285 node_port = increment_port_option(node_port);
286 metrics_port = increment_port_option(metrics_port);
287 rpc_port = increment_port_option(rpc_port);
288 }
289
290 if options.delete_antnode_src {
291 debug!("Deleting antnode binary file");
292 std::fs::remove_file(options.antnode_src_path)?;
293 }
294
295 if !added_service_data.is_empty() {
296 info!("Added {} services", added_service_data.len());
297 } else if !failed_service_data.is_empty() {
298 error!("Failed to add {} service(s)", failed_service_data.len());
299 }
300
301 if !added_service_data.is_empty() && verbosity != VerbosityLevel::Minimal {
302 println!("Services Added:");
303 for install in added_service_data.iter() {
304 println!(" {} {}", "✓".green(), install.0);
305 println!(" - Antnode path: {}", install.1);
306 println!(" - Data path: {}", install.2);
307 println!(" - Log path: {}", install.3);
308 println!(" - RPC port: {}", install.4);
309 }
310 println!("[!] Note: newly added services have not been started");
311 }
312
313 if !failed_service_data.is_empty() {
314 if verbosity != VerbosityLevel::Minimal {
315 println!("Failed to add {} service(s):", failed_service_data.len());
316 for failed in failed_service_data.iter() {
317 println!("{} {}: {}", "✕".red(), failed.0, failed.1);
318 }
319 }
320 return Err(eyre!("Failed to add one or more services")
321 .suggestion("However, any services that were successfully added will be usable."));
322 }
323
324 let added_services_names = added_service_data
325 .into_iter()
326 .map(|(name, ..)| name)
327 .collect();
328
329 Ok(added_services_names)
330}
331
332pub async fn add_daemon(
336 options: AddDaemonServiceOptions,
337 node_registry: NodeRegistryManager,
338 service_control: &dyn ServiceControl,
339) -> Result<()> {
340 if node_registry.daemon.read().await.is_some() {
341 error!("A antctld service has already been created");
342 return Err(eyre!("A antctld service has already been created"));
343 }
344
345 debug!(
346 "Copying daemon binary file to {:?}",
347 options.daemon_install_bin_path
348 );
349 std::fs::copy(
350 options.daemon_src_bin_path.clone(),
351 options.daemon_install_bin_path.clone(),
352 )?;
353
354 let install_ctx = ServiceInstallCtx {
355 args: vec![
356 OsString::from("--port"),
357 OsString::from(options.port.to_string()),
358 OsString::from("--address"),
359 OsString::from(options.address.to_string()),
360 ],
361 autostart: true,
362 contents: None,
363 environment: options.env_variables,
364 label: DAEMON_SERVICE_NAME.parse()?,
365 program: options.daemon_install_bin_path.clone(),
366 restart_policy: service_manager::RestartPolicy::OnSuccess { delay_secs: None },
367 username: Some(options.user),
368 working_directory: None,
369 };
370
371 match service_control.install(install_ctx, false) {
372 Ok(()) => {
373 let daemon = DaemonServiceData {
374 daemon_path: options.daemon_install_bin_path.clone(),
375 endpoint: Some(SocketAddr::new(IpAddr::V4(options.address), options.port)),
376 pid: None,
377 service_name: DAEMON_SERVICE_NAME.to_string(),
378 status: ServiceStatus::Added,
379 version: options.version,
380 };
381
382 node_registry.insert_daemon(daemon).await;
383 info!("Daemon service has been added successfully");
384 println!("Daemon service added {}", "✓".green());
385 println!("[!] Note: the service has not been started");
386 node_registry.save().await?;
387 std::fs::remove_file(options.daemon_src_bin_path)?;
388 Ok(())
389 }
390 Err(e) => {
391 error!("Failed to add daemon service: {e}");
392 println!("Failed to add daemon service: {e}");
393 Err(e.into())
394 }
395 }
396}