1#[cfg(target_os = "windows")]
2use {
3 windows_service::{
4 service::{
5 ServiceAccess, ServiceErrorControl, ServiceInfo, ServiceStartType, ServiceType,
6 },
7 service_manager::{ServiceManager, ServiceManagerAccess},
8 },std::{
9 ffi::OsString,
10 thread::sleep
11 }
12};
13
14use easy_trace::instruments::tracing;
15#[cfg(not(target_os = "windows"))]
16use
17{ users::get_user_by_uid,
18 users::os::unix::UserExt
19};
20#[cfg(target_os = "windows")]
21pub fn start_service() -> sharing::PisPasResult<()> {
22 let manager = ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT)?;
23 let service = manager.open_service(sharing::SERVICE_NAME, ServiceAccess::START)?;
24 service.start(&[""])?;
25 Ok(())
26}
27
28#[cfg(target_os = "windows")]
29pub fn stop_service() -> sharing::PisPasResult<()> {
30 let manager = ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT)?;
31 let service = manager.open_service(sharing::SERVICE_NAME, ServiceAccess::STOP)?;
32 service.stop()?;
33 Ok(())
34}
35
36#[cfg(target_os = "windows")]
37pub fn unregister_service() -> sharing::PisPasResult<()> {
38 match sharing::natives::api::stop_pispas_modules() {
39 Ok(_) => tracing::info!("Pispas modules stopped successfully"),
40 Err(e) => tracing::error!("Error stopping pispas modules: {}", e),
41 }
42 sleep(std::time::Duration::from_secs(1));
43 let manager = ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT)?;
44 let service = manager.open_service(sharing::SERVICE_NAME, ServiceAccess::DELETE)?;
45 service.delete()?;
46 Ok(())
47}
48#[cfg(windows)]
49pub fn register_service() -> sharing::PisPasResult<()> {
50 let manager_access = ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE;
51 let service_manager = ServiceManager::local_computer(None::<&str>, manager_access)?;
52
53 let service_binary_path = sharing::paths::bin_dir().join(sharing::SERVICE_NAME_EXE);
54
55 let service_info = ServiceInfo {
56 name: OsString::from(sharing::SERVICE_NAME),
57 display_name: OsString::from(sharing::SERVICE_NAME),
58 service_type: ServiceType::OWN_PROCESS,
59 start_type: ServiceStartType::AutoStart,
60 error_control: ServiceErrorControl::Normal,
61 executable_path: service_binary_path,
62 launch_arguments: vec![],
63 dependencies: vec![],
64 account_name: None, account_password: None,
66 };
67 match service_manager.open_service(
68 sharing::SERVICE_NAME,
69 ServiceAccess::CHANGE_CONFIG,
70 ) {
71 Ok(service) => {
72 service.change_config(&service_info)?;
73 tracing::info!("Service {} update", sharing::SERVICE_NAME);
74 sleep(std::time::Duration::from_secs(1));
75 }
76 Err(e) => {
77 let service = service_manager.create_service(&service_info, ServiceAccess::CHANGE_CONFIG)?;
78 service.set_description(sharing::SERVICE_NAME)?;
79 sleep(std::time::Duration::from_secs(1));
80 tracing::info!("Service {} does not exist error {}", sharing::SERVICE_NAME, e);
81 match service.start(&[""]) {
82 Ok(_) => tracing::info!("Service {} started", sharing::SERVICE_NAME),
83 Err(e) => tracing::error!("Service {} start error {}", sharing::SERVICE_NAME, e),
84 }
85 }
86 }
87 Ok(())
88}
89#[cfg(target_os = "macos")]
92pub fn register_service_as_daemon() -> sharing::PisPasResult<()> {
93 use std::fs;
94
95 let service_name = sharing::SERVICE_NAME;
96 let plist_content = create_launch_daemon_plist()?;
97
98 let plist_path = std::path::PathBuf::from("/Library/LaunchDaemons")
100 .join(format!("{}.plist", service_name));
101
102 fs::write(&plist_path, plist_content)?;
104 tracing::info!("Created LaunchDaemon plist: {}", plist_path.display());
105
106 std::process::Command::new("chown")
108 .args(&["root:wheel", &plist_path.to_string_lossy()])
109 .output()?;
110
111 std::process::Command::new("chmod")
112 .args(&["644", &plist_path.to_string_lossy()])
113 .output()?;
114
115 let bootstrap_output = std::process::Command::new("launchctl")
117 .args(&["bootstrap", "system", &plist_path.to_string_lossy()])
118 .output()?;
119
120 if !bootstrap_output.status.success() {
121 let error = String::from_utf8_lossy(&bootstrap_output.stderr);
122 tracing::error!("Failed to bootstrap daemon: {}", error);
123 return Err(anyhow::anyhow!("Failed to bootstrap daemon: {}", error));
124 }
125
126 let service_target = format!("system/{}", service_name);
128 let enable_output = std::process::Command::new("launchctl")
129 .args(&["enable", &service_target])
130 .output()?;
131
132 if !enable_output.status.success() {
133 let error = String::from_utf8_lossy(&enable_output.stderr);
134 tracing::warn!("Failed to enable daemon: {}", error);
135 }
136
137 tracing::info!("Service {} bootstrapped and enabled as daemon", service_name);
138 Ok(())
139}
140#[cfg(target_os = "macos")]
141pub fn register_service() -> sharing::PisPasResult<()> {
142 register_service_as_daemon()
144}
145
146#[cfg(target_os = "macos")]
147pub fn unregister_service() -> sharing::PisPasResult<()> {
148 let service_name = sharing::SERVICE_NAME;
149
150 tracing::info!("Unregistering service: {}", service_name);
151
152 let _ = stop_service();
154
155 let home_dir = dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Cannot get home directory"))?;
157 let plist_path = home_dir.join("Library/LaunchAgents").join(format!("{}.plist", service_name));
158
159 if plist_path.exists() {
161 let output = std::process::Command::new("launchctl")
162 .args(&["unload", &plist_path.to_string_lossy()])
163 .output()?;
164
165 if !output.status.success() {
166 let error = String::from_utf8_lossy(&output.stderr);
167 tracing::warn!("Failed to unload service: {}", error);
168 }
169
170 std::fs::remove_file(&plist_path)?;
171 tracing::info!("Service plist removed: {}", plist_path.display());
172 }
173
174 let daemon_plist = format!("/Library/LaunchDaemons/{}.plist", service_name);
176 if std::path::Path::new(&daemon_plist).exists() {
177 let _ = std::process::Command::new("sudo")
178 .args(&["launchctl", "unload", &daemon_plist])
179 .output();
180 let _ = std::process::Command::new("sudo")
181 .args(&["rm", "-f", &daemon_plist])
182 .output();
183 tracing::info!("Cleaned up legacy daemon plist");
184 }
185
186 tracing::info!("Service {} unregistered successfully", service_name);
187 Ok(())
188}
189
190#[cfg(target_os = "macos")]
193pub fn start_service() -> sharing::PisPasResult<()> {
194 let service_name = sharing::SERVICE_NAME;
195 let service_target = format!("system/{}", service_name); let print_output = std::process::Command::new("launchctl")
199 .args(&["print", &service_target])
200 .output()?;
201
202 if print_output.status.success() {
203 tracing::info!("Service {} is already loaded", service_name);
204 return Ok(());
205 }
206
207 let output = std::process::Command::new("launchctl")
209 .args(&["kickstart", &service_target])
210 .output()?;
211
212 if !output.status.success() {
213 let error = String::from_utf8_lossy(&output.stderr);
214 tracing::error!("Failed to start service: {}", error);
215 return Err(anyhow::anyhow!("Failed to start service: {}", error));
216 }
217
218 tracing::info!("Service {} started successfully", service_name);
219 Ok(())
220}
221
222#[cfg(target_os = "macos")]
223pub fn stop_service() -> sharing::PisPasResult<()> {
224 let service_name = sharing::SERVICE_NAME;
225 let service_target = format!("system/{}", service_name); let output = std::process::Command::new("launchctl")
228 .args(&["kill", "SIGTERM", &service_target])
229 .output()?;
230
231 if !output.status.success() {
232 let error = String::from_utf8_lossy(&output.stderr);
233 tracing::warn!("Failed to stop service: {}", error);
234 }
235
236 tracing::info!("Service {} stopped", service_name);
237 Ok(())
238}
239
240
241#[cfg(target_os = "macos")]
242pub fn restart_service() -> sharing::PisPasResult<()> {
243 tracing::info!("Restarting service...");
244
245 let _ = stop_service();
246 std::thread::sleep(std::time::Duration::from_secs(1));
247 start_service()?;
248
249 tracing::info!("Service restarted successfully");
250 Ok(())
251}
252
253#[cfg(target_os = "macos")]
256pub fn install_service() -> sharing::PisPasResult<()> {
257 tracing::info!("Installing pispas service on macOS...");
258 clean_and_register_service()
259}
260
261#[cfg(target_os = "macos")]
262pub fn clean_and_register_service() -> sharing::PisPasResult<()> {
263 use std::fs;
264 use std::process::Command;
265
266 tracing::info!("Cleaning existing pispas services...");
267
268 let _ = Command::new("sudo").args(&["pkill", "-f", "pispas"]).output();
270 let _ = Command::new("sudo").args(&["pkill", "-f", "pispas-modules"]).output();
271 let _ = Command::new("sudo").args(&["pkill", "-f", "pispas-service"]).output();
272 let _ = Command::new("sudo").args(&["pkill", "-f", "pispas-tray-app"]).output();
273
274
275 std::thread::sleep(std::time::Duration::from_secs(2));
276
277 let service_name = sharing::SERVICE_NAME;
279
280 let daemon_plist = format!("/Library/LaunchDaemons/{}.plist", service_name);
282 let _ = Command::new("sudo").args(&["launchctl", "unload", &daemon_plist]).output();
283 let _ = Command::new("sudo").args(&["rm", "-f", &daemon_plist]).output();
284
285 let system_agent_plist = format!("/Library/LaunchAgents/{}.plist", service_name);
287 let _ = Command::new("sudo").args(&["rm", "-f", &system_agent_plist]).output();
288
289 let home_dir = dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Cannot get home directory"))?;
291 let user_agent_plist = home_dir.join("Library/LaunchAgents").join(format!("{}.plist", service_name));
292
293 if user_agent_plist.exists() {
294 let _ = Command::new("launchctl")
295 .args(&["unload", &user_agent_plist.to_string_lossy()])
296 .output();
297 let _ = fs::remove_file(&user_agent_plist);
298 }
299
300 let _ = Command::new("sudo").args(&["launchctl", "remove", service_name]).output();
302 let _ = Command::new("launchctl").args(&["remove", service_name]).output();
303
304 let uid = unsafe { libc::getuid() };
306 let domain = format!("gui/{}", uid);
307 let _ = Command::new("launchctl").args(&["bootout", &domain, &user_agent_plist.to_string_lossy()]).output();
308
309 tracing::info!("Cleanup completed. Registering new service...");
310
311 let service_binary = sharing::paths::bin_dir().join(sharing::SERVICE_NAME_EXE);
313 if !service_binary.exists() {
314 let modules_binary = sharing::paths::bin_dir().join("pispas-modules");
315 if modules_binary.exists() {
316 std::os::unix::fs::symlink(&modules_binary, &service_binary)?;
317 tracing::info!("Created symlink: {} -> {}",
318 service_binary.display(), modules_binary.display());
319 } else {
320 return Err(anyhow::anyhow!("Neither pispas-service nor pispas-modules binary found"));
321 }
322 }
323
324 register_service_as_daemon()?;
326
327 Ok(())
328}
329
330#[cfg(target_os = "macos")]
331fn create_launch_daemon_plist() -> sharing::PisPasResult<String> {
332 match sharing::natives::api::get_first_logged_user() {
333 Ok((_username, uid)) => {
334 if let Some(user) = get_user_by_uid(uid) {
335
336 let service_binary_path = format!(
338 "{}/.config/pispas/{}",
339 user.home_dir().display(),
340 sharing::SERVICE_NAME_EXE
341 );
342
343 let working_directory = format!(
344 "{}/.config/pispas",
345 user.home_dir().display()
346 );
347
348 let plist_content = format!(r#"<?xml version="1.0" encoding="UTF-8"?>
349 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
350 <plist version="1.0">
351 <dict>
352 <key>Label</key>
353 <string>{}</string>
354 <key>ProgramArguments</key>
355 <array>
356 <string>{}</string>
357 </array>
358 <key>RunAtLoad</key>
359 <true/>
360 <key>KeepAlive</key>
361 <true/>
362 <key>StandardOutPath</key>
363 <string>/var/log/pispas/pispas-service.out.log</string>
364 <key>StandardErrorPath</key>
365 <string>/var/log/pispas/pispas-service.err.log</string>
366 <key>WorkingDirectory</key>
367 <string>{}</string>
368 </dict>
369 </plist>"#,
370 sharing::SERVICE_NAME,
371 service_binary_path,
372 working_directory
373 );
374
375 return Ok(plist_content);
376 }
377 }
378 Err(e) => {
379 tracing::error!("Error getting first logged user: {}", e);
380 return Err(anyhow::anyhow!("Failed to get first logged user"));
381 }
382 }
383 Err(anyhow::anyhow!("Failed to create LaunchDaemon plist"))
384}
385#[cfg(target_os = "linux")]
387pub fn register_service() -> sharing::PisPasResult<()> {
388 use std::fs;
389 use std::os::unix::fs::PermissionsExt;
390
391 let service_name = sharing::SERVICE_NAME;
392 let service_content = create_systemd_service()?;
393 let service_path = format!("/etc/systemd/system/{}.service", service_name);
394
395 fs::write(&service_path, service_content)?;
397
398 let mut perms = fs::metadata(&service_path)?.permissions();
400 perms.set_mode(0o644);
401 fs::set_permissions(&service_path, perms)?;
402
403 std::process::Command::new("systemctl")
405 .args(&["daemon-reload"])
406 .output()?;
407
408 let output = std::process::Command::new("systemctl")
409 .args(&["enable", service_name])
410 .output()?;
411
412 if !output.status.success() {
413 let error = String::from_utf8_lossy(&output.stderr);
414 tracing::error!("Failed to enable service: {}", error);
415 return Err(anyhow::anyhow!("Failed to enable service: {}", error));
416 }
417
418 tracing::info!("Service {} registered successfully", service_name);
419 Ok(())
420}
421
422#[cfg(target_os = "linux")]
423pub fn unregister_service() -> sharing::PisPasResult<()> {
424 let service_name = sharing::SERVICE_NAME;
425 let service_path = format!("/etc/systemd/system/{}.service", service_name);
426
427 std::process::Command::new("systemctl")
429 .args(&["stop", service_name])
430 .output()?;
431
432 std::process::Command::new("systemctl")
433 .args(&["disable", service_name])
434 .output()?;
435
436 if std::path::Path::new(&service_path).exists() {
438 std::fs::remove_file(&service_path)?;
439 tracing::info!("Service file removed: {}", service_path);
440 }
441
442 std::process::Command::new("systemctl")
444 .args(&["daemon-reload"])
445 .output()?;
446
447 tracing::info!("Service {} unregistered successfully", service_name);
448 Ok(())
449}
450
451#[cfg(target_os = "linux")]
452pub fn start_service() -> sharing::PisPasResult<()> {
453 let service_name = sharing::SERVICE_NAME;
454
455 let output = std::process::Command::new("systemctl")
456 .args(&["start", service_name])
457 .output()?;
458
459 if !output.status.success() {
460 let error = String::from_utf8_lossy(&output.stderr);
461 tracing::error!("Failed to start service: {}", error);
462 return Err(anyhow::anyhow!("Failed to start service: {}", error));
463 }
464
465 tracing::info!("Service {} started successfully", service_name);
466 Ok(())
467}
468
469#[cfg(target_os = "linux")]
470pub fn stop_service() -> sharing::PisPasResult<()> {
471 let service_name = sharing::SERVICE_NAME;
472
473 let output = std::process::Command::new("systemctl")
474 .args(&["stop", service_name])
475 .output()?;
476
477 if !output.status.success() {
478 let error = String::from_utf8_lossy(&output.stderr);
479 tracing::warn!("Failed to stop service (might not be running): {}", error);
480 }
481
482 tracing::info!("Service {} stopped", service_name);
483 Ok(())
484}
485
486#[cfg(target_os = "linux")]
487fn create_systemd_service() -> sharing::PisPasResult<String> {
488 let service_binary_path = sharing::paths::bin_dir().join(sharing::SERVICE_NAME_EXE);
489 let service_name = sharing::SERVICE_NAME;
490
491 let service_content = format!(r#"[Unit]
492Description={}
493After=network.target
494
495[Service]
496Type=simple
497ExecStart={} run
498Restart=always
499RestartSec=5
500User=root
501WorkingDirectory={}
502
503[Install]
504WantedBy=multi-user.target
505"#,
506 service_name,
507 service_binary_path.display(),
508 sharing::paths::bin_dir().display()
509 );
510
511 Ok(service_content)
512}