Skip to main content

AirLibrary/Daemon/
mod.rs

1//! # Daemon Lifecycle Management
2//!
3//! This module provides comprehensive daemon lifecycle management for the Air
4//! daemon service, responsible for managing background processes in the Land
5//! code editor ecosystem.
6//!
7//! ## Architecture Overview
8//!
9//! The daemon follows VSCode's daemon architecture pattern:
10//! - Reference: VSCode service management
11//!   (Dependency/Microsoft/Editor/src/vs/base/node/processexitorutility)
12//! - Singleton enforcement through PID file locking
13//! - Platform-native service integration (systemd, launchd, Windows Service)
14//! - Graceful shutdown coordination with Mountain (main editor process)
15//! - Resource cleanup and state persistence across restarts
16//!
17//! ## Core Responsibilities
18//!
19//! 1. **Process Management**
20//!    - PID file creation, validation, and cleanup
21//!    - Checksum-based PID file integrity verification
22//!    - Process existence validation and stale detection
23//!    - Race condition protection for lock acquisition
24//!    - Timeout handling for all async operations
25//!
26//! 2. **Service Installation**
27//!    - systemd service generation and installation (Linux)
28//!    - launchd plist generation and installation (macOS)
29//!    - Windows Service registration (Windows using winsvc)
30//!    - Service validation and health checks
31//!    - Post-installation verification
32//!
33//! 3. **Lifecycle Coordination**
34//!    - Lock acquisition with atomic operations
35//!    - Graceful shutdown signals
36//!    - Resource cleanup on errors
37//!    - State persistence and recovery
38//!
39//! 4. **Platform Integration**
40//!    - Linux: systemd socket activation support
41//!    - macOS: launchd session management
42//!    - Windows: Windows Service API integration
43//!    - Cross-platform log rotation
44//!
45//! ## FUTURE Enhancements
46//!
47//! - [ ] Implement Windows winsvc integration for actual service registration
48//! - [ ] Add systemd socket activation support
49//! - [ ] Implement daemon auto-update notifications
50//! - [ ] Add crash recovery and state restoration
51//! - [ ] Implement daemon health monitoring with metrics
52//! - [ ] Add log rotation for daemon logs
53//! - [ ] Implement daemon upgrade path (in-place hot reload)
54//! - [ ] Add daemon configuration reloading without restart
55//! - [ ] Implement grace period for Mountain shutdown coordination
56//! - [ ] Add daemon sandbox support for security isolation
57//! ## Platform-Specific Considerations
58//!
59//! ### Linux (systemd)
60//! - PID file location: `/var/run/Air.pid`
61//! - Service file: `/etc/systemd/system/Air-daemon.service`
62//! - Requires root privileges for installation
63//! - Supports socket activation and notify-ready
64//!
65//! ### macOS (launchd)
66//! - PID file location: `/tmp/Air.pid`
67//! - Service file: `/Library/LaunchDaemons/Air-daemon.plist`
68//! - Requires root privileges for system daemon
69//! - Supports launchctl unload/start/stop commands
70//!
71//! ### Windows
72//! - PID file location: `C:\ProgramData\Air\Air.pid`
73//! - Service registration via SCManager API
74//! - Requires Administrator privileges
75//! - Uses winsvc crate or similar for service management
76//!
77//! ## Security Considerations
78//!
79//! - PID file protected with checksum to prevent tampering
80//! - Directory creation with secure permissions (0700)
81//! - SUID/SGID not used for security
82//! - User-level isolation for multi-user systems
83//!
84//! ## Error Handling
85//!
86//! All operations return `Result<T>` with comprehensive error types:
87//! - `ServiceUnavailable`: Daemon already running or unavailable
88//! - `FileSystem`: PID file or directory operations failed
89//! - `PermissionDenied`: Insufficient privileges for service operations
90
91use std::{fs, path::PathBuf, sync::Arc, time::Duration};
92
93use tokio::sync::{Mutex, RwLock};
94use sha2::{Digest, Sha256};
95
96use crate::{AirError, Result, dev_log};
97
98/// Daemon lifecycle manager
99#[derive(Debug)]
100pub struct DaemonManager {
101	/// PID file path
102	PidFilePath:PathBuf,
103
104	/// Whether daemon is running
105	IsRunning:Arc<RwLock<bool>>,
106
107	/// Platform-specific daemon info
108	PlatformInfo:PlatformInfo,
109
110	/// Lock for atomic PID file operations (prevents race conditions)
111	PidLock:Arc<Mutex<()>>,
112
113	/// Checksum for PID file integrity verification
114	PidChecksum:Arc<Mutex<Option<String>>>,
115
116	/// Graceful shutdown flag
117	ShutdownRequested:Arc<RwLock<bool>>,
118}
119
120/// Platform-specific daemon information
121#[derive(Debug)]
122pub struct PlatformInfo {
123	/// Platform type
124	pub Platform:Platform,
125
126	/// Service name for system integration
127	pub ServiceName:String,
128
129	/// User under which daemon runs
130	pub RunAsUser:Option<String>,
131}
132
133/// Platform enum
134#[derive(Debug, Clone, PartialEq)]
135pub enum Platform {
136	Linux,
137
138	MacOS,
139
140	Windows,
141
142	Unknown,
143}
144
145/// Exit codes for daemon operations
146#[derive(Debug, Clone)]
147pub enum ExitCode {
148	Success = 0,
149
150	ConfigurationError = 1,
151
152	AlreadyRunning = 2,
153
154	PermissionDenied = 3,
155
156	ServiceError = 4,
157
158	ResourceError = 5,
159
160	NetworkError = 6,
161
162	AuthenticationError = 7,
163
164	FileSystemError = 8,
165
166	InternalError = 9,
167
168	UnknownError = 10,
169}
170
171impl DaemonManager {
172	/// Create a new DaemonManager instance
173	pub fn New(PidFilePath:Option<PathBuf>) -> Result<Self> {
174		let PidFilePath = PidFilePath.unwrap_or_else(|| Self::DefaultPidFilePath());
175
176		let PlatformInfo = Self::DetectPlatformInfo();
177
178		Ok(Self {
179			PidFilePath,
180			IsRunning:Arc::new(RwLock::new(false)),
181			PlatformInfo,
182			PidLock:Arc::new(Mutex::new(())),
183			PidChecksum:Arc::new(Mutex::new(None)),
184			ShutdownRequested:Arc::new(RwLock::new(false)),
185		})
186	}
187
188	/// Get default PID file path based on platform
189	fn DefaultPidFilePath() -> PathBuf {
190		let platform = Self::DetectPlatform();
191
192		match platform {
193			Platform::Linux => PathBuf::from("/var/run/Air.pid"),
194
195			Platform::MacOS => PathBuf::from("/tmp/Air.pid"),
196
197			Platform::Windows => PathBuf::from("C:\\ProgramData\\Air\\Air.pid"),
198
199			Platform::Unknown => PathBuf::from("./Air.pid"),
200		}
201	}
202
203	/// Detect current platform
204	fn DetectPlatform() -> Platform {
205		if cfg!(target_os = "linux") {
206			Platform::Linux
207		} else if cfg!(target_os = "macos") {
208			Platform::MacOS
209		} else if cfg!(target_os = "windows") {
210			Platform::Windows
211		} else {
212			Platform::Unknown
213		}
214	}
215
216	/// Detect platform-specific information
217	fn DetectPlatformInfo() -> PlatformInfo {
218		let platform = Self::DetectPlatform();
219
220		let ServiceName = "Air-daemon".to_string();
221
222		// Get current user
223		let RunAsUser = std::env::var("USER").ok().or_else(|| std::env::var("USERNAME").ok());
224
225		PlatformInfo { Platform:platform, ServiceName, RunAsUser }
226	}
227
228	/// Acquire daemon lock to ensure single instance
229	/// This method provides comprehensive defensive coding with:
230	/// - Race condition protection through mutex locking
231	/// - PID file checksum verification
232	/// - Process validation checks
233	/// - Atomic operations with rollback on failure
234	/// - Timeout handling
235	pub async fn AcquireLock(&self) -> Result<()> {
236		dev_log!("daemon", "[Daemon] Acquiring daemon lock...");
237
238		// Acquire lock to prevent race conditions
239		tokio::select! {
240
241			_ = tokio::time::timeout(Duration::from_secs(30), self.PidLock.lock()) => {
242
243				let _lock_guard = self.PidLock.lock().await;
244			},
245
246			_ = tokio::time::sleep(Duration::from_secs(30)) => {
247
248				return Err(AirError::Internal(
249					"Timeout acquiring PID lock".to_string()
250				));
251			}
252		}
253
254		let _lock = self.PidLock.lock().await;
255
256		// Check if shutdown has been requested
257		if *self.ShutdownRequested.read().await {
258			return Err(AirError::ServiceUnavailable(
259				"Shutdown requested, cannot acquire lock".to_string(),
260			));
261		}
262
263		// Check if PID file exists and process is running with validation
264		if self.IsAlreadyRunning().await? {
265			return Err(AirError::ServiceUnavailable("Air daemon is already running".to_string()));
266		}
267
268		// Create PID directory with secure permissions if it doesn't exist
269		let TempDir = PathBuf::from(format!("{}.tmp", self.PidFilePath.display()));
270
271		if let Some(parent) = self.PidFilePath.parent() {
272			fs::create_dir_all(parent)
273				.map_err(|e| AirError::FileSystem(format!("Failed to create PID directory: {}", e)))?;
274
275			// Set secure permissions on directory (user only)
276			#[cfg(unix)]
277			{
278				use std::os::unix::fs::PermissionsExt;
279
280				let perms = fs::Permissions::from_mode(0o700);
281
282				fs::set_permissions(parent, perms)
283					.map_err(|e| AirError::FileSystem(format!("Failed to set directory permissions: {}", e)))?;
284			}
285		}
286
287		// Generate PID content with checksum for validation
288		let pid = std::process::id();
289
290		let timestamp = std::time::SystemTime::now()
291			.duration_since(std::time::UNIX_EPOCH)
292			.unwrap()
293			.as_secs();
294
295		let PidContent = format!("{}|{}", pid, timestamp);
296
297		// Calculate checksum for integrity verification
298		let mut hasher = Sha256::new();
299
300		hasher.update(PidContent.as_bytes());
301
302		// sha2 0.11: `Digest::finalize()` output dropped its `LowerHex` impl
303		// (moved onto `hybrid_array::Array`). `hex::encode` produces the same
304		// lowercase-hex string as the former `format!("{:x}", …)` and keeps
305		// the PID-file checksum payload byte-identical.
306		let checksum = hex::encode(hasher.finalize());
307
308		// Write to temporary file first (atomic operation)
309		let TempFileContent = format!("{}|CHECKSUM:{}", PidContent, checksum);
310
311		fs::write(&TempDir, &TempFileContent)
312			.map_err(|e| AirError::FileSystem(format!("Failed to write temporary PID file: {}", e)))?;
313
314		// Atomic rename to avoid partial writes
315		#[cfg(unix)]
316		fs::rename(&TempDir, &self.PidFilePath).map_err(|e| {
317			// Rollback: clean up temp file on failure
318			let _ = fs::remove_file(&TempDir);
319			AirError::FileSystem(format!("Failed to rename PID file: {}", e))
320		})?;
321
322		#[cfg(not(unix))]
323		fs::rename(&TempDir, &self.PidFilePath).map_err(|e| {
324			let _ = fs::remove_file(&TempDir);
325			AirError::FileSystem(format!("Failed to rename PID file: {}", e))
326		})?;
327
328		// Store checksum for later validation
329		*self.PidChecksum.lock().await = Some(checksum);
330
331		// Set running state
332		*self.IsRunning.write().await = true;
333
334		// Set secure permissions on PID file
335		#[cfg(unix)]
336		{
337			use std::os::unix::fs::PermissionsExt;
338
339			let perms = fs::Permissions::from_mode(0o600);
340
341			if let Err(e) = fs::set_permissions(&self.PidFilePath, perms) {
342				dev_log!("daemon", "warn: [Daemon] Failed to set PID file permissions: {}", e);
343			}
344		}
345
346		dev_log!("daemon", "[Daemon] Daemon lock acquired (PID: {})", pid);
347
348		Ok(())
349	}
350
351	/// Check if daemon is already running
352	/// Performs comprehensive validation including:
353	/// - PID file existence check
354	/// - Checksum verification
355	/// - Process existence validation
356	/// - Stale PID file cleanup
357	pub async fn IsAlreadyRunning(&self) -> Result<bool> {
358		if !self.PidFilePath.exists() {
359			dev_log!("daemon", "[Daemon] PID file does not exist");
360
361			return Ok(false);
362		}
363
364		// Read PID from file
365		let PidContent = fs::read_to_string(&self.PidFilePath)
366			.map_err(|e| AirError::FileSystem(format!("Failed to read PID file: {}", e)))?;
367
368		// Parse PID content with checksum
369		let parts:Vec<&str> = PidContent.split('|').collect();
370
371		if parts.len() < 2 {
372			dev_log!("daemon", "warn: [Daemon] Invalid PID file format, treating as stale");
373
374			self.CleanupStalePidFile().await?;
375
376			return Ok(false);
377		}
378
379		let pid:u32 = parts[0].trim().parse().map_err(|e| {
380			dev_log!("daemon", "warn: [Daemon] Invalid PID in file: {}", e);
381			AirError::FileSystem("Invalid PID file content".to_string())
382		})?;
383
384		// Verify checksum if present
385		if parts.len() >= 3 && parts[1].starts_with("CHECKSUM:") {
386			let StoredChecksum = &parts[1][9..]; // Remove "CHECKSUM:" prefix
387			let CurrentChecksum = self.PidChecksum.lock().await;
388
389			if let Some(ref cksum) = *CurrentChecksum {
390				if cksum != StoredChecksum {
391					dev_log!("daemon", "warn: [Daemon] PID file checksum mismatch, file may be corrupted"); // Don't automatically delete - could be a different daemon instance
392					return Ok(true);
393				}
394			}
395		}
396
397		// Check if process exists with validation
398		let IsRunning = Self::ValidateProcess(pid);
399
400		if !IsRunning {
401			// Clean up stale PID file with validation
402			dev_log!("daemon", "warn: [Daemon] Detected stale PID file for PID {}", pid);
403
404			self.CleanupStalePidFile().await?;
405		}
406
407		Ok(IsRunning)
408	}
409
410	/// Validate that a process with the given PID is running
411	/// Performs thorough process validation and existence checks
412	fn ValidateProcess(pid:u32) -> bool {
413		#[cfg(unix)]
414		{
415			use std::process::Command;
416
417			let output = Command::new("ps").arg("-p").arg(pid.to_string()).output();
418
419			match output {
420				Ok(output) => {
421					if output.status.success() {
422						let stdout = String::from_utf8_lossy(&output.stdout);
423
424						// Validate it's actually an Air daemon process
425						stdout
426							.lines()
427							.skip(1)
428							.any(|line| line.contains("Air") || line.contains("daemon"))
429					} else {
430						false
431					}
432				},
433
434				Err(e) => {
435					dev_log!("daemon", "error: [Daemon] Failed to check process status: {}", e);
436
437					false
438				},
439			}
440		}
441
442		#[cfg(windows)]
443		{
444			use std::process::Command;
445
446			let output = Command::new("tasklist")
447				.arg("/FI")
448				.arg(format!("PID eq {}", pid))
449				.arg("/FO")
450				.arg("CSV")
451				.output();
452
453			match output {
454				Ok(output) => {
455					if output.status.success() {
456						let stdout = String::from_utf8_lossy(&output.stdout);
457
458						stdout.lines().any(|line| {
459							line.contains(&pid.to_string()) && (line.contains("Air") || line.contains("daemon"))
460						})
461					} else {
462						false
463					}
464				},
465
466				Err(e) => {
467					dev_log!("daemon", "error: [Daemon] Failed to check process status: {}", e);
468
469					false
470				},
471			}
472		}
473	}
474
475	/// Cleanup stale PID file with validation and error handling
476	async fn CleanupStalePidFile(&self) -> Result<()> {
477		if !self.PidFilePath.exists() {
478			return Ok(());
479		}
480
481		// Verify the file is actually stale before deleting
482		let content = fs::read_to_string(&self.PidFilePath)
483			.map_err(|e| {
484				dev_log!("daemon", "warn: [Daemon] Cannot verify stale PID file: {}", e);
485				return false;
486			})
487			.ok();
488
489		if let Some(content) = content {
490			if content.starts_with(|c:char| c.is_numeric()) {
491				// Clean up the stale PID file
492				if let Err(e) = fs::remove_file(&self.PidFilePath) {
493					dev_log!("daemon", "warn: [Daemon] Failed to remove stale PID file: {}", e);
494
495					return Err(AirError::FileSystem(format!("Failed to remove stale PID file: {}", e)));
496				}
497
498				dev_log!("daemon", "[Daemon] Cleaned up stale PID file");
499			}
500		}
501
502		Ok(())
503	}
504
505	/// Release daemon lock with proper cleanup and rollback
506	/// Ensures all resources are properly cleaned up even on failure
507	pub async fn ReleaseLock(&self) -> Result<()> {
508		dev_log!("daemon", "[Daemon] Releasing daemon lock...");
509
510		// Acquire lock for atomic cleanup
511		let _lock = self.PidLock.lock().await;
512
513		// Set running state before cleanup
514		*self.IsRunning.write().await = false;
515
516		// Clear checksum
517		*self.PidChecksum.lock().await = None;
518
519		// Remove PID file with validation
520		if self.PidFilePath.exists() {
521			match fs::remove_file(&self.PidFilePath) {
522				Ok(_) => {
523					dev_log!("daemon", "[Daemon] PID file removed successfully");
524				},
525
526				Err(e) => {
527					dev_log!("daemon", "error: [Daemon] Failed to remove PID file: {}", e); // Don't fail entire operation if PID file cleanup fails
528					return Err(AirError::FileSystem(format!("Failed to remove PID file: {}", e)));
529				},
530			}
531		}
532
533		// Try to clean up any temporary files
534		let TempDir = PathBuf::from(format!("{}.tmp", self.PidFilePath.display()));
535
536		if TempDir.exists() {
537			let _ = fs::remove_file(&TempDir);
538		}
539
540		dev_log!("daemon", "[Daemon] Daemon lock released");
541
542		Ok(())
543	}
544
545	/// Check if daemon is running
546	pub async fn IsRunning(&self) -> bool { *self.IsRunning.read().await }
547
548	/// Request graceful shutdown
549	pub async fn RequestShutdown(&self) -> Result<()> {
550		dev_log!("daemon", "[Daemon] Requesting graceful shutdown...");
551
552		*self.ShutdownRequested.write().await = true;
553		Ok(())
554	}
555
556	/// Clear shutdown request (for restart scenarios)
557	pub async fn ClearShutdownRequest(&self) -> Result<()> {
558		dev_log!("daemon", "[Daemon] Clearing shutdown request");
559
560		*self.ShutdownRequested.write().await = false;
561		Ok(())
562	}
563
564	/// Check if shutdown has been requested
565	pub async fn IsShutdownRequested(&self) -> bool { *self.ShutdownRequested.read().await }
566
567	/// Get daemon status with comprehensive health information
568	pub async fn GetStatus(&self) -> Result<DaemonStatus> {
569		let IsRunning = self.IsRunning().await;
570
571		let PidFileExists = self.PidFilePath.exists();
572
573		let pid = if PidFileExists {
574			fs::read_to_string(&self.PidFilePath)
575				.ok()
576				.and_then(|content| content.split('|').next().and_then(|s| s.trim().parse().ok()))
577		} else {
578			None
579		};
580
581		Ok(DaemonStatus {
582			IsRunning,
583			PidFileExists,
584			Pid:pid,
585			Platform:self.PlatformInfo.Platform.clone(),
586			ServiceName:self.PlatformInfo.ServiceName.clone(),
587			ShutdownRequested:self.IsShutdownRequested().await,
588		})
589	}
590
591	/// Generate system service file for installation
592	pub fn GenerateServiceFile(&self) -> Result<String> {
593		match self.PlatformInfo.Platform {
594			Platform::Linux => self.GenerateSystemdService(),
595
596			Platform::MacOS => self.GenerateLaunchdService(),
597
598			#[cfg(target_os = "windows")]
599			Platform::Windows => self.GenerateWindowsService(),
600
601			#[cfg(not(target_os = "windows"))]
602			Platform::Windows => {
603				Err(AirError::ServiceUnavailable(
604					"Windows service generation not available on this platform".to_string(),
605				))
606			},
607
608			Platform::Unknown => {
609				Err(AirError::ServiceUnavailable(
610					"Unknown platform, cannot generate service file".to_string(),
611				))
612			},
613		}
614	}
615
616	/// Generate systemd service file with comprehensive configuration
617	fn GenerateSystemdService(&self) -> Result<String> {
618		let ExePath = std::env::current_exe()
619			.map_err(|e| AirError::FileSystem(format!("Failed to get executable path: {}", e)))?;
620
621		let user = self.PlatformInfo.RunAsUser.as_deref().unwrap_or("root");
622
623		let group = self.PlatformInfo.RunAsUser.as_deref().unwrap_or("root");
624
625		let ServiceContent = format!(
626			r#"[Unit]
627Description=Air Daemon - Background service for Land code editor
628Documentation=man:Air(1)
629After=network-online.target
630Wants=network-online.target
631StartLimitIntervalSec=0
632
633[Service]
634Type=notify
635NotifyAccess=all
636ExecStart={}
637ExecStop=/bin/kill -s TERM $MAINPID
638Restart=always
639RestartSec=5
640StartLimitBurst=3
641User={}
642Group={}
643Environment=RUST_LOG=info
644Environment=DAEMON_MODE=systemd
645Nice=-5
646LimitNOFILE=65536
647LimitNPROC=4096
648
649# Security hardening
650NoNewPrivileges=true
651PrivateTmp=true
652ProtectSystem=strict
653ProtectHome=true
654ReadWritePaths=/var/log/Air /var/run/Air
655RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
656RestrictRealtime=true
657
658[Install]
659WantedBy=multi-user.target
660"#,
661			ExePath.display(),
662			user,
663			group
664		);
665
666		Ok(ServiceContent)
667	}
668
669	/// Generate launchd service file with comprehensive configuration
670	fn GenerateLaunchdService(&self) -> Result<String> {
671		let ExePath = std::env::current_exe()
672			.map(|p| p.display().to_string())
673			.unwrap_or_else(|_| "/usr/local/bin/Air".to_string());
674
675		let ServiceName = &self.PlatformInfo.ServiceName;
676
677		let user = self.PlatformInfo.RunAsUser.as_deref().unwrap_or("root");
678
679		let ServiceContent = format!(
680			r#"<?xml version="1.0" encoding="UTF-8"?>
681<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
682<plist version="1.0">
683<dict>
684    <key>Label</key>
685    <string>{}</string>
686    
687    <key>ProgramArguments</key>
688    <array>
689        <string>{}</string>
690        <string>--daemon</string>
691        <string>--mode=launchd</string>
692    </array>
693    
694    <key>RunAtLoad</key>
695    <true/>
696    
697    <key>KeepAlive</key>
698    <dict>
699        <key>SuccessfulExit</key>
700        <false/>
701        <key>Crashed</key>
702        <true/>
703    </dict>
704    
705    <key>ThrottleInterval</key>
706    <integer>5</integer>
707    
708    <key>UserName</key>
709    <string>{}</string>
710    
711    <key>StandardOutPath</key>
712    <string>/var/log/Air/daemon.log</string>
713    
714    <key>StandardErrorPath</key>
715    <string>/var/log/Air/daemon.err</string>
716    
717    <key>WorkingDirectory</key>
718    <string>/var/lib/Air</string>
719    
720    <key>ProcessType</key>
721    <string>Background</string>
722    
723    <key>Nice</key>
724    <integer>-5</integer>
725    
726    <key>SoftResourceLimits</key>
727    <dict>
728        <key>NumberOfFiles</key>
729        <integer>65536</integer>
730    </dict>
731    
732    <key>HardResourceLimits</key>
733    <dict>
734        <key>NumberOfFiles</key>
735        <integer>65536</integer>
736    </dict>
737    
738    <key>EnvironmentVariables</key>
739    <dict>
740        <key>RUST_LOG</key>
741        <string>info</string>
742        <key>DAEMON_MODE</key>
743        <string>launchd</string>
744    </dict>
745</dict>
746</plist>
747"#,
748			ServiceName, ExePath, user
749		);
750
751		Ok(ServiceContent)
752	}
753
754	/// Generate Windows service configuration file
755	///
756	/// Note: For production use with actual Windows service registration,
757	/// integrate with the winsvc crate or windows-rs API.
758	/// This method generates a configuration file compatible with winsvc.
759	#[cfg(target_os = "windows")]
760	fn GenerateWindowsService(&self) -> Result<String> {
761		let ExePath = std::env::current_exe()
762			.map(|p| p.display().to_string())
763			.unwrap_or_else(|_| "C:\\Program Files\\Air\\Air.exe".to_string());
764
765		let ServiceName = &self.PlatformInfo.ServiceName;
766
767		let DisplayName = "Air Daemon Service";
768
769		let Description = "Background service for Land code editor";
770
771		// Generate winsvc-compatible XML configuration
772		let ServiceContent = format!(
773			r#"<service>
774		  <id>{}</id>
775		  <name>{}</name>
776		  <description>{}</description>
777		  <executable>{}</executable>
778    
779    <arguments>--daemon --mode=windows</arguments>
780    
781    <startmode>Automatic</startmode>
782    <delayedAutoStart>true</delayedAutoStart>
783    
784    <log mode="roll">
785        <sizeThreshold>10240</sizeThreshold>
786        <keepFiles>8</keepFiles>
787    </log>
788    
789    <onfailure action="restart" delay="10 sec"/>
790    <onfailure action="restart" delay="20 sec"/>
791    <onfailure action="restart" delay="60 sec"/>
792    
793    <resetfailure>1 hour</resetfailure>
794    
795    <depend>EventLog</depend>
796    <depend>TcpIp</depend>
797    
798    <serviceaccount>
799        <domain>.</domain>
800        <user>LocalSystem</user>
801        <password></password>
802        <allowservicelogon>true</allowservicelogon>
803    </serviceaccount>
804    
805    <workingdirectory>C:\Program Files\Air</workingdirectory>
806    
807    <env name="RUST_LOG" value="info"/>
808    <env name="DAEMON_MODE" value="windows"/>
809</service>
810"#,
811			ServiceName, DisplayName, Description, ExePath
812		);
813
814		Ok(ServiceContent)
815	}
816
817	/// Install daemon as system service with validation
818	pub async fn InstallService(&self) -> Result<()> {
819		dev_log!("daemon", "[Daemon] Installing system service...");
820
821		match self.PlatformInfo.Platform {
822			Platform::Linux => self.InstallSystemdService().await,
823
824			Platform::MacOS => self.InstallLaunchdService().await,
825
826			#[cfg(target_os = "windows")]
827			Platform::Windows => self.InstallWindowsService().await,
828
829			#[cfg(not(target_os = "windows"))]
830			Platform::Windows => {
831				Err(AirError::ServiceUnavailable(
832					"Windows service installation not available on this platform".to_string(),
833				))
834			},
835
836			Platform::Unknown => {
837				Err(AirError::ServiceUnavailable(
838					"Unknown platform, cannot install service".to_string(),
839				))
840			},
841		}
842	}
843
844	/// Install systemd service with validation
845	async fn InstallSystemdService(&self) -> Result<()> {
846		let ServiceFileContent = self.GenerateSystemdService()?;
847
848		let ServiceFilePath = format!("/etc/systemd/system/{}.service", self.PlatformInfo.ServiceName);
849
850		// Create temporary file for atomic write
851		let TempPath = format!("{}.tmp", ServiceFilePath);
852
853		// Validate service content
854		if !ServiceFileContent.contains("[Unit]") || !ServiceFileContent.contains("[Service]") {
855			return Err(AirError::Configuration("Generated service file is invalid".to_string()));
856		}
857
858		// Write to temporary file first
859		fs::write(&TempPath, &ServiceFileContent)
860			.map_err(|e| AirError::FileSystem(format!("Failed to write temporary service file: {}", e)))?;
861
862		// Atomic rename
863		#[cfg(unix)]
864		fs::rename(&TempPath, &ServiceFilePath).map_err(|e| {
865			let _ = fs::remove_file(&TempPath);
866			AirError::FileSystem(format!("Failed to rename service file: {}", e))
867		})?;
868
869		#[cfg(not(unix))]
870		fs::rename(&TempPath, &ServiceFilePath).map_err(|e| {
871			let _ = fs::remove_file(&TempPath);
872			AirError::FileSystem(format!("Failed to rename service file: {}", e))
873		})?;
874
875		// Set proper permissions
876		#[cfg(unix)]
877		{
878			use std::os::unix::fs::PermissionsExt;
879
880			let perms = fs::Permissions::from_mode(0o644);
881
882			fs::set_permissions(&ServiceFilePath, perms)
883				.map_err(|e| {
884					dev_log!("daemon", "error: [Daemon] Failed to set service file permissions: {}", e);
885				})
886				.ok();
887		}
888
889		dev_log!("daemon", "[Daemon] Systemd service installed at {}", ServiceFilePath);
890
891		// Run daemon-reload to notify systemd
892		let _ = tokio::process::Command::new("systemctl").args(["daemon-reload"]).output().await;
893
894		Ok(())
895	}
896
897	/// Install launchd service with validation
898	async fn InstallLaunchdService(&self) -> Result<()> {
899		let ServiceFileContent = self.GenerateLaunchdService()?;
900
901		let ServiceFilePath = format!("/Library/LaunchDaemons/{}.plist", self.PlatformInfo.ServiceName);
902
903		// Create temporary file for atomic write
904		let TempPath = format!("{}.tmp", ServiceFilePath);
905
906		// Validate plist content
907		if !ServiceFileContent.contains("<?xml") || !ServiceFileContent.contains("<!DOCTYPE plist") {
908			return Err(AirError::Configuration("Generated plist file is invalid".to_string()));
909		}
910
911		// Write to temporary file first
912		fs::write(&TempPath, &ServiceFileContent)
913			.map_err(|e| AirError::FileSystem(format!("Failed to write temporary plist file: {}", e)))?;
914
915		// Atomic rename
916		#[cfg(unix)]
917		fs::rename(&TempPath, &ServiceFilePath).map_err(|e| {
918			let _ = fs::remove_file(&TempPath);
919			AirError::FileSystem(format!("Failed to rename plist file: {}", e))
920		})?;
921
922		#[cfg(not(unix))]
923		fs::rename(&TempPath, &ServiceFilePath).map_err(|e| {
924			let _ = fs::remove_file(&TempPath);
925			AirError::FileSystem(format!("Failed to rename plist file: {}", e))
926		})?;
927
928		// Set proper permissions
929		#[cfg(unix)]
930		{
931			use std::os::unix::fs::PermissionsExt;
932
933			let perms = fs::Permissions::from_mode(0o644);
934
935			fs::set_permissions(&ServiceFilePath, perms)
936				.map_err(|e| {
937					dev_log!("daemon", "error: [Daemon] Failed to set plist file permissions: {}", e);
938				})
939				.ok();
940		}
941
942		dev_log!("daemon", "[Daemon] Launchd service installed at {}", ServiceFilePath);
943
944		// No need to load immediately - launchd will pick it up automatically
945		// User can run: sudo launchctl load -w /Library/LaunchDaemons/Air-daemon.plist
946
947		Ok(())
948	}
949
950	/// Install Windows service
951	///
952	/// Note: For production use, integrate with the winsvc crate or windows-rs
953	/// API to perform actual Windows service registration via the Service
954	/// Control Manager (SCM). This method writes a configuration file that can
955	/// be used with winsvc.
956	#[cfg(target_os = "windows")]
957	async fn InstallWindowsService(&self) -> Result<()> {
958		let ServiceFileContent = self.GenerateWindowsService()?;
959
960		let ServiceDir = "C:\\ProgramData\\Air";
961
962		let ServiceFilePath = format!("{}\\{}.xml", ServiceDir, self.PlatformInfo.ServiceName);
963
964		// Create directory if it doesn't exist
965		fs::create_dir_all(&ServiceDir)
966			.map_err(|e| AirError::FileSystem(format!("Failed to create service directory: {}", e)))?;
967
968		// Create temporary file for atomic write
969		let TempPath = format!("{}.tmp", ServiceFilePath);
970
971		// Validate service content
972		if !ServiceFileContent.contains("<service>") {
973			return Err(AirError::Configuration("Generated service file is invalid".to_string()));
974		}
975
976		// Write to temporary file first
977		fs::write(&TempPath, &ServiceFileContent)
978			.map_err(|e| AirError::FileSystem(format!("Failed to write temporary service file: {}", e)))?;
979
980		// Atomic rename
981		fs::rename(&TempPath, &ServiceFilePath).map_err(|e| {
982			let _ = fs::remove_file(&TempPath);
983			AirError::FileSystem(format!("Failed to rename service file: {}", e))
984		})?;
985
986		dev_log!(
987			"daemon",
988			"[Daemon] Windows service configuration written to {}",
989			ServiceFilePath
990		);
991
992		dev_log!("daemon", "[Daemon] To register the service, run:");
993
994		dev_log!(
995			"daemon",
996			"[Daemon]   sc create AirDaemon binPath= \"{}\" DisplayName= \"Air Daemon\"",
997			std::env::current_exe().unwrap_or_else(|_| "air.exe".into()).display()
998		);
999
1000		dev_log!("daemon", "[Daemon]   sc config AirDaemon start= auto");
1001
1002		dev_log!("daemon", "[Daemon]   sc start AirDaemon");
1003
1004		Ok(())
1005	}
1006
1007	/// Uninstall system service with proper coordination
1008	pub async fn UninstallService(&self) -> Result<()> {
1009		dev_log!("daemon", "[Daemon] Uninstalling system service...");
1010
1011		match self.PlatformInfo.Platform {
1012			Platform::Linux => self.UninstallSystemdService().await,
1013
1014			Platform::MacOS => self.UninstallLaunchdService().await,
1015
1016			#[cfg(target_os = "windows")]
1017			Platform::Windows => self.UninstallWindowsService().await,
1018
1019			#[cfg(not(target_os = "windows"))]
1020			Platform::Windows => {
1021				Err(AirError::ServiceUnavailable(
1022					"Windows service uninstallation not available on this platform".to_string(),
1023				))
1024			},
1025
1026			Platform::Unknown => {
1027				Err(AirError::ServiceUnavailable(
1028					"Unknown platform, cannot uninstall service".to_string(),
1029				))
1030			},
1031		}
1032	}
1033
1034	/// Uninstall systemd service with proper coordination
1035	async fn UninstallSystemdService(&self) -> Result<()> {
1036		let ServiceFilePath = format!("/etc/systemd/system/{}.service", self.PlatformInfo.ServiceName);
1037
1038		// Stop service first if running
1039		let _ = tokio::process::Command::new("systemctl")
1040			.args(["stop", &self.PlatformInfo.ServiceName])
1041			.output()
1042			.await;
1043
1044		// Disable service
1045		let _ = tokio::process::Command::new("systemctl")
1046			.args(["disable", &self.PlatformInfo.ServiceName])
1047			.output()
1048			.await;
1049
1050		// Remove service file
1051		if fs::remove_file(&ServiceFilePath).is_ok() {
1052			dev_log!("daemon", "[Daemon] Systemd service file removed");
1053		} else {
1054			dev_log!("daemon", "warn: [Daemon] Service file {} not found", ServiceFilePath);
1055		}
1056
1057		// Reload systemd
1058		let _ = tokio::process::Command::new("systemctl").args(["daemon-reload"]).output().await;
1059
1060		dev_log!("daemon", "[Daemon] Systemd service uninstalled");
1061
1062		Ok(())
1063	}
1064
1065	/// Uninstall launchd service with proper coordination
1066	async fn UninstallLaunchdService(&self) -> Result<()> {
1067		let ServiceFilePath = format!("/Library/LaunchDaemons/{}.plist", self.PlatformInfo.ServiceName);
1068
1069		// Unload service first
1070		let _ = tokio::process::Command::new("launchctl")
1071			.args(["unload", "-w", &ServiceFilePath])
1072			.output()
1073			.await;
1074
1075		// Remove service file
1076		if fs::remove_file(&ServiceFilePath).is_ok() {
1077			dev_log!("daemon", "[Daemon] Launchd service file removed");
1078		} else {
1079			dev_log!("daemon", "warn: [Daemon] Service file {} not found", ServiceFilePath);
1080		}
1081
1082		dev_log!("daemon", "[Daemon] Launchd service uninstalled");
1083
1084		Ok(())
1085	}
1086
1087	/// Uninstall Windows service
1088	///
1089	/// Note: For production use, integrate with the winsvc crate or windows-rs
1090	/// API to properly stop and remove the Windows service via the Service
1091	/// Control Manager (SCM).
1092	#[cfg(target_os = "windows")]
1093	async fn UninstallWindowsService(&self) -> Result<()> {
1094		let ServiceFilePath = format!("C:\\ProgramData\\Air\\{}.xml", self.PlatformInfo.ServiceName);
1095
1096		// Remove the configuration file
1097		if fs::remove_file(&ServiceFilePath).is_ok() {
1098			dev_log!("daemon", "[Daemon] Windows service configuration removed");
1099		} else {
1100			dev_log!("daemon", "warn: [Daemon] Service file {} not found", ServiceFilePath);
1101		}
1102
1103		dev_log!("daemon", "[Daemon] To unregister the service, run:");
1104
1105		dev_log!("daemon", "[Daemon]   sc stop AirDaemon");
1106
1107		dev_log!("daemon", "[Daemon]   sc delete AirDaemon");
1108
1109		Ok(())
1110	}
1111}
1112
1113/// Daemon status information
1114#[derive(Debug, Clone)]
1115pub struct DaemonStatus {
1116	pub IsRunning:bool,
1117
1118	pub PidFileExists:bool,
1119
1120	pub Pid:Option<u32>,
1121
1122	pub Platform:Platform,
1123
1124	pub ServiceName:String,
1125
1126	pub ShutdownRequested:bool,
1127}
1128
1129impl DaemonStatus {
1130	/// Get human-readable status description
1131	pub fn status_description(&self) -> String {
1132		if self.IsRunning {
1133			format!("Running (PID: {})", self.Pid.unwrap_or(0))
1134		} else if self.PidFileExists {
1135			"Stale PID file exists".to_string()
1136		} else {
1137			"Not running".to_string()
1138		}
1139	}
1140}
1141
1142impl From<ExitCode> for i32 {
1143	fn from(code:ExitCode) -> i32 { code as i32 }
1144}