commonware_deployer/ec2/
utils.rs1use crate::ec2::Error;
4use tokio::{
5 process::Command,
6 time::{sleep, Duration},
7};
8use tracing::warn;
9
10pub const MAX_SSH_ATTEMPTS: usize = 30;
12
13pub const MAX_POLL_ATTEMPTS: usize = 30;
15
16pub const RETRY_INTERVAL: Duration = Duration::from_secs(10);
18
19pub const DEPLOYER_PROTOCOL: &str = "tcp";
21
22pub const DEPLOYER_MIN_PORT: i32 = 0;
24
25pub const DEPLOYER_MAX_PORT: i32 = 65535;
27
28pub async fn get_public_ip() -> Result<String, Error> {
30 let result = reqwest::get("https://ipv4.icanhazip.com")
32 .await?
33 .text()
34 .await?
35 .trim()
36 .to_string();
37 Ok(result)
38}
39
40pub async fn rsync_file(
42 key_file: &str,
43 local_path: &str,
44 ip: &str,
45 remote_path: &str,
46) -> Result<(), Error> {
47 for _ in 0..MAX_SSH_ATTEMPTS {
48 let output = Command::new("rsync")
49 .arg("-az")
50 .arg("-e")
51 .arg(format!(
52 "ssh -i {key_file} -o ServerAliveInterval=600 -o StrictHostKeyChecking=no"
53 ))
54 .arg(local_path)
55 .arg(format!("ubuntu@{ip}:{remote_path}"))
56 .output()
57 .await?;
58 if output.status.success() {
59 return Ok(());
60 }
61 warn!(error = ?String::from_utf8_lossy(&output.stderr), "SCP failed");
62 sleep(RETRY_INTERVAL).await;
63 }
64 Err(Error::ScpFailed)
65}
66
67pub async fn ssh_execute(key_file: &str, ip: &str, command: &str) -> Result<(), Error> {
69 for _ in 0..MAX_SSH_ATTEMPTS {
70 let output = Command::new("ssh")
71 .arg("-i")
72 .arg(key_file)
73 .arg("-o")
74 .arg("ServerAliveInterval=600")
75 .arg("-o")
76 .arg("StrictHostKeyChecking=no")
77 .arg(format!("ubuntu@{ip}"))
78 .arg(command)
79 .output()
80 .await?;
81 if output.status.success() {
82 return Ok(());
83 }
84 warn!(error = ?String::from_utf8_lossy(&output.stderr), "SSH failed");
85 sleep(RETRY_INTERVAL).await;
86 }
87 Err(Error::SshFailed)
88}
89
90pub async fn poll_service_active(key_file: &str, ip: &str, service: &str) -> Result<(), Error> {
92 for _ in 0..MAX_POLL_ATTEMPTS {
93 let output = Command::new("ssh")
94 .arg("-i")
95 .arg(key_file)
96 .arg("-o")
97 .arg("ServerAliveInterval=600")
98 .arg("-o")
99 .arg("StrictHostKeyChecking=no")
100 .arg(format!("ubuntu@{ip}"))
101 .arg(format!("systemctl is-active {service}"))
102 .output()
103 .await?;
104 let parsed = String::from_utf8_lossy(&output.stdout);
105 let parsed = parsed.trim();
106 if parsed == "active" {
107 return Ok(());
108 }
109 if service == "binary" && parsed == "failed" {
110 warn!(service, "service failed to start (check logs and update)");
111 return Ok(());
112 }
113 warn!(error = ?String::from_utf8_lossy(&output.stderr), service, "active status check failed");
114 sleep(RETRY_INTERVAL).await;
115 }
116 Err(Error::ServiceTimeout(ip.to_string(), service.to_string()))
117}
118
119pub async fn poll_service_inactive(key_file: &str, ip: &str, service: &str) -> Result<(), Error> {
121 for _ in 0..MAX_POLL_ATTEMPTS {
122 let output = Command::new("ssh")
123 .arg("-i")
124 .arg(key_file)
125 .arg("-o")
126 .arg("ServerAliveInterval=600")
127 .arg("-o")
128 .arg("StrictHostKeyChecking=no")
129 .arg(format!("ubuntu@{ip}"))
130 .arg(format!("systemctl is-active {service}"))
131 .output()
132 .await?;
133 let parsed = String::from_utf8_lossy(&output.stdout);
134 let parsed = parsed.trim();
135 if parsed == "inactive" {
136 return Ok(());
137 }
138 if service == "binary" && parsed == "failed" {
139 warn!(service, "service was never active");
140 return Ok(());
141 }
142 warn!(error = ?String::from_utf8_lossy(&output.stderr), service, "inactive status check failed");
143 sleep(RETRY_INTERVAL).await;
144 }
145 Err(Error::ServiceTimeout(ip.to_string(), service.to_string()))
146}
147
148pub async fn enable_bbr(key_file: &str, ip: &str, bbr_conf_local_path: &str) -> Result<(), Error> {
150 rsync_file(
151 key_file,
152 bbr_conf_local_path,
153 ip,
154 "/home/ubuntu/99-bbr.conf",
155 )
156 .await?;
157 ssh_execute(
158 key_file,
159 ip,
160 "sudo mv /home/ubuntu/99-bbr.conf /etc/sysctl.d/99-bbr.conf",
161 )
162 .await?;
163 ssh_execute(key_file, ip, "sudo sysctl -p /etc/sysctl.d/99-bbr.conf").await?;
164 Ok(())
165}
166
167pub fn exact_cidr(ip: &str) -> String {
169 format!("{ip}/32")
170}