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
72pub async fn setup_httpd(
73 config: &Arc<RwLock<ClientConfig>>,
74 command_rx: &mut mpsc::Receiver<Message>,
75 result_tx: &mpsc::Sender<Message>,
76 env: EnvConfig,
77) -> Result<()> {
78 let cache_dir = get_cache_dir(DOWNLOAD_SUBDIR)?;
79 let httpd_dir = get_cache_dir(&env.httpd_dir)?;
80
81 let mut touch = httpd_dir.clone();
82 touch.push("installed.txt");
83
84 if touch.exists() {
85 return Ok(());
86 }
87
88 let mut httpd = cache_dir.clone();
89 httpd.push(env.httpd.clone());
90
91 download(
92 &crate::t!("downloading-webserver"),
93 config.clone(),
94 format!("{}download/{}", config.read().server, env.httpd).as_str(),
95 &httpd,
96 command_rx,
97 result_tx,
98 )
99 .await
100 .context(crate::t!("error-downloading-webserver"))?;
101
102 unzip(
103 &crate::t!("unpacking-webserver"),
104 &httpd,
105 &httpd_dir,
106 1,
107 result_tx,
108 )
109 .await
110 .context(crate::t!("error-unpacking-webserver"))?;
111
112 #[cfg(target_os = "windows")]
113 {
114 use crate::shell::execute;
115 let mut redist = cache_dir.clone();
116 redist.push(env.redist.clone());
117
118 download(
119 &crate::t!("downloading-vcpp"),
120 config.clone(),
121 format!("{}download/{}", config.read().server, env.redist).as_str(),
122 &redist,
123 command_rx,
124 result_tx,
125 )
126 .await
127 .context(crate::t!("error-downloading-vcpp"))?;
128
129 if let Err(err) = execute(
130 redist,
131 vec![
132 "/install".to_string(),
133 "/quiet".to_string(),
134 "/norestart".to_string(),
135 ],
136 None,
137 Default::default(),
138 Some((crate::t!("installing-vcpp"), result_tx.clone(), 2)),
139 command_rx,
140 )
141 .await
142 {
143 tracing::warn!("{}: {:?}", crate::t!("error-installing-vcpp"), err);
145 }
146 }
147 #[cfg(unix)]
149 {
150 let httpd_exe = httpd_dir.join("bin").join(HTTPD_EXE);
151 use std::os::unix::fs::PermissionsExt;
152 std::fs::set_permissions(&httpd_exe, std::fs::Permissions::from_mode(0o755))
153 .context(crate::t!("error-setting-permissions"))?;
154 }
155
156 std::fs::write(touch, "Delete to reinstall").context(crate::t!("error-creating-marker"))?;
158
159 Ok(())
160}
161
162pub async fn start_httpd(
163 endpoint: &ServerEndpoint,
164 config_template: &str,
165 config_subdir: &str,
166 publish_dir: &str,
167 env: EnvConfig,
168 result_tx: mpsc::Sender<Message>,
169) -> Result<SubProcess> {
170 let httpd_dir = get_cache_dir(&env.httpd_dir)?;
171 let configs_dir = get_cache_dir(config_subdir)?;
172
173 let mut httpd_cfg = configs_dir.clone();
174 httpd_cfg.push(format!("{}.conf", endpoint.guid));
175
176 let mut pid_file = configs_dir.clone();
177 pid_file.push(format!("{}.pid", endpoint.guid));
178
179 let mut lock_file = configs_dir.clone();
180 lock_file.push(format!("{}.lock", endpoint.guid));
181
182 #[cfg(unix)]
184 kill_by_pid_file(&pid_file);
185
186 let port = find_free_tcp_port()
187 .await
188 .context(t!("error-finding-free-port"))?;
189
190 let httpd_config = config_template.replace("[[PUBLISH_DIR]]", publish_dir);
191 let httpd_config = httpd_config.replace("[[SRVROOT]]", httpd_dir.to_str().unwrap());
192 let httpd_config = httpd_config.replace("[[PORT]]", &port.to_string());
193 let httpd_config = httpd_config.replace("[[PID_FILE]]", pid_file.to_str().unwrap());
194 let httpd_config = httpd_config.replace("[[LOCK_FILE]]", lock_file.to_str().unwrap());
195
196 #[cfg(unix)]
197 let httpd_config = httpd_config.replace("[[IS_LINUX]]", "");
198
199 #[cfg(not(unix))]
200 let httpd_config = httpd_config.replace("[[IS_LINUX]]", "#");
201
202 std::fs::write(&httpd_cfg, httpd_config).context(crate::t!("error-writing-httpd-conf"))?;
203
204 let httpd_cfg = httpd_cfg.to_str().unwrap().to_string();
205 let httpd_exe = httpd_dir.join("bin").join(HTTPD_EXE);
206
207 #[allow(unused_mut)]
208 let mut envs = HashMap::<String, String>::new();
209
210 #[cfg(target_os = "macos")]
211 envs.insert(
212 "DYLD_LIBRARY_PATH".to_string(),
213 httpd_dir.join("lib").to_str().unwrap().to_string(),
214 );
215
216 #[cfg(target_os = "linux")]
217 envs.insert(
218 "LD_LIBRARY_PATH".to_string(),
219 httpd_dir.join("lib").to_str().unwrap().to_string(),
220 );
221
222 let server = SubProcess::new(
223 httpd_exe,
224 vec![
225 #[cfg(not(target_os = "windows"))]
226 "-X".to_string(),
227 "-f".to_string(),
228 httpd_cfg,
229 ],
230 None,
231 envs,
232 result_tx,
233 port,
234 );
235 Ok(server)
236}