1use crate::config::{ClientConfig, EnvConfig};
2use crate::shell::{download, get_cache_dir, unzip, SubProcess, DOWNLOAD_SUBDIR};
3use crate::t;
4use anyhow::{Context, Result};
5use cloudpub_common::protocol::message::Message;
6use cloudpub_common::protocol::ServerEndpoint;
7use cloudpub_common::utils::find_free_tcp_port;
8use parking_lot::RwLock;
9use std::collections::HashMap;
10use std::sync::Arc;
11use tokio::sync::mpsc;
12
13#[cfg(unix)]
14use nix::sys::signal::{self, Signal};
15#[cfg(unix)]
16use nix::unistd::Pid;
17
18#[cfg(unix)]
19fn kill_by_pid_file(pid_file: &std::path::Path) {
20 use std::fs;
21 if !pid_file.exists() {
22 return;
23 }
24
25 let pid_str = match fs::read_to_string(pid_file) {
26 Ok(content) => content,
27 Err(e) => {
28 tracing::warn!("Failed to read PID file {}: {}", pid_file.display(), e);
29 return;
30 }
31 };
32
33 let pid_num: i32 = match pid_str.trim().parse() {
34 Ok(pid) => pid,
35 Err(e) => {
36 tracing::warn!(
37 "Failed to parse PID from file {}: {}",
38 pid_file.display(),
39 e
40 );
41 return;
42 }
43 };
44
45 let pid = Pid::from_raw(pid_num);
46
47 match signal::kill(pid, Signal::SIGTERM) {
49 Ok(()) => {
50 tracing::info!("Successfully sent SIGTERM to process {}", pid_num);
51 }
52 Err(nix::errno::Errno::ESRCH) => {
53 tracing::debug!("Process {} not found (already dead)", pid_num);
55 }
56 Err(e) => {
57 tracing::warn!("Failed to kill process {}: {}", pid_num, e);
58 }
59 }
60
61 if let Err(e) = fs::remove_file(pid_file) {
63 tracing::warn!("Failed to remove PID file {}: {}", pid_file.display(), e);
64 }
65}
66
67#[cfg(target_os = "windows")]
68pub const HTTPD_EXE: &str = "httpd.exe";
69#[cfg(unix)]
70pub const HTTPD_EXE: &str = "httpd";
71
72static HTTPD_SETUP_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
77
78pub async fn setup_httpd(
79 config: &Arc<RwLock<ClientConfig>>,
80 command_rx: &mut mpsc::Receiver<Message>,
81 result_tx: &mpsc::Sender<Message>,
82 env: EnvConfig,
83) -> Result<()> {
84 let cache_dir = get_cache_dir(DOWNLOAD_SUBDIR)?;
85 let httpd_dir = get_cache_dir(&env.httpd_dir)?;
86
87 let mut touch = httpd_dir.clone();
88 touch.push("installed.txt");
89
90 let _setup_guard = HTTPD_SETUP_LOCK.lock().await;
91
92 if touch.exists() {
93 return Ok(());
94 }
95
96 let mut httpd = cache_dir.clone();
97 httpd.push(env.httpd.clone());
98
99 download(
100 &crate::t!("downloading-webserver"),
101 config.clone(),
102 format!("{}download/{}", config.read().server, env.httpd).as_str(),
103 &httpd,
104 command_rx,
105 result_tx,
106 )
107 .await
108 .context(crate::t!("error-downloading-webserver"))?;
109
110 unzip(
111 &crate::t!("unpacking-webserver"),
112 &httpd,
113 &httpd_dir,
114 1,
115 result_tx,
116 )
117 .await
118 .context(crate::t!("error-unpacking-webserver"))?;
119
120 #[cfg(target_os = "windows")]
121 {
122 use crate::shell::execute;
123 let mut redist = cache_dir.clone();
124 redist.push(env.redist.clone());
125
126 download(
127 &crate::t!("downloading-vcpp"),
128 config.clone(),
129 format!("{}download/{}", config.read().server, env.redist).as_str(),
130 &redist,
131 command_rx,
132 result_tx,
133 )
134 .await
135 .context(crate::t!("error-downloading-vcpp"))?;
136
137 if let Err(err) = execute(
138 redist,
139 vec![
140 "/install".to_string(),
141 "/quiet".to_string(),
142 "/norestart".to_string(),
143 ],
144 None,
145 Default::default(),
146 Some((crate::t!("installing-vcpp"), result_tx.clone(), 2)),
147 command_rx,
148 )
149 .await
150 {
151 tracing::warn!("{}: {:?}", crate::t!("error-installing-vcpp"), err);
153 }
154 }
155 #[cfg(unix)]
157 {
158 let httpd_exe = httpd_dir.join("bin").join(HTTPD_EXE);
159 use std::os::unix::fs::PermissionsExt;
160 std::fs::set_permissions(&httpd_exe, std::fs::Permissions::from_mode(0o755))
161 .context(crate::t!("error-setting-permissions"))?;
162 }
163
164 std::fs::write(touch, "Delete to reinstall").context(crate::t!("error-creating-marker"))?;
166
167 Ok(())
168}
169
170pub async fn start_httpd(
171 endpoint: &ServerEndpoint,
172 config_template: &str,
173 config_subdir: &str,
174 publish_dir: &str,
175 env: EnvConfig,
176 result_tx: mpsc::Sender<Message>,
177) -> Result<SubProcess> {
178 let httpd_dir = get_cache_dir(&env.httpd_dir)?;
179 let configs_dir = get_cache_dir(config_subdir)?;
180
181 let mut httpd_cfg = configs_dir.clone();
182 httpd_cfg.push(format!("{}.conf", endpoint.guid));
183
184 let mut pid_file = configs_dir.clone();
185 pid_file.push(format!("{}.pid", endpoint.guid));
186
187 let mut lock_file = configs_dir.clone();
188 lock_file.push(format!("{}.lock", endpoint.guid));
189
190 #[cfg(unix)]
192 kill_by_pid_file(&pid_file);
193
194 let port = find_free_tcp_port()
195 .await
196 .context(t!("error-finding-free-port"))?;
197
198 let httpd_config = config_template.replace("[[PUBLISH_DIR]]", publish_dir);
199 let httpd_config = httpd_config.replace("[[SRVROOT]]", httpd_dir.to_str().unwrap());
200 let httpd_config = httpd_config.replace("[[PORT]]", &port.to_string());
201 let httpd_config = httpd_config.replace("[[PID_FILE]]", pid_file.to_str().unwrap());
202 let httpd_config = httpd_config.replace("[[LOCK_FILE]]", lock_file.to_str().unwrap());
203
204 #[cfg(unix)]
205 let httpd_config = httpd_config.replace("[[IS_LINUX]]", "");
206
207 #[cfg(not(unix))]
208 let httpd_config = httpd_config.replace("[[IS_LINUX]]", "#");
209
210 std::fs::write(&httpd_cfg, httpd_config).context(crate::t!("error-writing-httpd-conf"))?;
211
212 let httpd_cfg = httpd_cfg.to_str().unwrap().to_string();
213 let httpd_exe = httpd_dir.join("bin").join(HTTPD_EXE);
214
215 #[allow(unused_mut)]
216 let mut envs = HashMap::<String, String>::new();
217
218 #[cfg(target_os = "macos")]
219 envs.insert(
220 "DYLD_LIBRARY_PATH".to_string(),
221 httpd_dir.join("lib").to_str().unwrap().to_string(),
222 );
223
224 #[cfg(target_os = "linux")]
225 envs.insert(
226 "LD_LIBRARY_PATH".to_string(),
227 httpd_dir.join("lib").to_str().unwrap().to_string(),
228 );
229
230 let server = SubProcess::new(
231 httpd_exe,
232 vec![
233 #[cfg(not(target_os = "windows"))]
234 "-X".to_string(),
235 "-f".to_string(),
236 httpd_cfg,
237 ],
238 None,
239 envs,
240 result_tx,
241 port,
242 );
243 Ok(server)
244}