pispas_elevator/
service.rs

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, // run as System
65        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// ===== FUNCIONES PRINCIPALES =====
90
91#[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    // LaunchDaemon va en /Library/LaunchDaemons/ (requiere sudo)
99    let plist_path = std::path::PathBuf::from("/Library/LaunchDaemons")
100        .join(format!("{}.plist", service_name));
101
102    // Escribir el archivo plist (requiere sudo)
103    fs::write(&plist_path, plist_content)?;
104    tracing::info!("Created LaunchDaemon plist: {}", plist_path.display());
105
106    // Establecer permisos correctos
107    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    // Bootstrap como daemon del sistema
116    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    // Habilitar para auto-start
127    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    // Para instalación/registro usa la función de limpieza completa
143    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    // Parar el servicio si está ejecutándose
153    let _ = stop_service();
154
155    // Usar LaunchAgent path
156    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    // Descargar el servicio (sin sudo)
160    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    // También limpiar cualquier daemon residual (por si acaso)
175    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// ===== FUNCIONES DE CONTROL =====
191
192#[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);  // ✅ system domain
196
197    // Verificar si ya está corriendo
198    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    // Kickstart para forzar inicio
208    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);  // ✅ system domain
226
227    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// ===== FUNCIONES DE INSTALACIÓN =====
254
255#[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    // 1. MATAR todos los procesos pispas
269    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    // 2. ELIMINAR todos los plists existentes
278    let service_name = sharing::SERVICE_NAME;
279
280    // LaunchDaemons (nivel sistema)
281    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    // LaunchAgents nivel sistema
286    let system_agent_plist = format!("/Library/LaunchAgents/{}.plist", service_name);
287    let _ = Command::new("sudo").args(&["rm", "-f", &system_agent_plist]).output();
288
289    // LaunchAgents de usuario
290    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    // En el paso 3, cambia esto:
301    let _ = Command::new("sudo").args(&["launchctl", "remove", service_name]).output();
302    let _ = Command::new("launchctl").args(&["remove", service_name]).output();
303
304    // Por esto:
305    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    // 4. Crear el symlink del binario si no existe
312    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    // 5. Registrar el nuevo servicio como LaunchDaemon
325    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                // Construir la ruta dinámicamente
337                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// Para Linux (systemd)
386#[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    // Escribir el archivo de servicio
396    fs::write(&service_path, service_content)?;
397
398    // Establecer permisos correctos
399    let mut perms = fs::metadata(&service_path)?.permissions();
400    perms.set_mode(0o644);
401    fs::set_permissions(&service_path, perms)?;
402
403    // Recargar systemd y habilitar el servicio
404    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    // Parar y deshabilitar el servicio
428    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    // Eliminar el archivo de servicio
437    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    // Recargar systemd
443    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}