From 502e345f916667494ca85bf903060d1d0d49b87f Mon Sep 17 00:00:00 2001 From: Mahmoud-Emad Date: Wed, 2 Jul 2025 16:37:27 +0300 Subject: [PATCH] feat: Enhance service manager with zinit socket discovery and systemd fallback - Improve Linux support by automatically discovering zinit sockets using environment variables and common paths. - Add fallback to systemd if no zinit server is detected. - Enhance README with detailed instructions for zinit usage, including custom socket path configuration. - Add example demonstrating zinit socket discovery. - Add logging to show socket discovery process. - Add unit tests for service manager creation and socket discovery. --- .gitignore | 3 +- examples/service_manager/basic_usage.rhai | 8 +- service_manager/Cargo.toml | 1 + service_manager/README.md | 23 ++- service_manager/examples/service_spaghetti.rs | 3 + service_manager/examples/simple_service.rs | 3 + .../examples/socket_discovery_test.rs | 47 +++++ service_manager/src/lib.rs | 166 ++++++++++++++++-- 8 files changed, 234 insertions(+), 20 deletions(-) create mode 100644 service_manager/examples/socket_discovery_test.rs diff --git a/.gitignore b/.gitignore index b895c18..6b25cc5 100644 --- a/.gitignore +++ b/.gitignore @@ -62,4 +62,5 @@ docusaurus.config.ts sidebars.ts tsconfig.json -Cargo.toml.bak \ No newline at end of file +Cargo.toml.bak +for_augment \ No newline at end of file diff --git a/examples/service_manager/basic_usage.rhai b/examples/service_manager/basic_usage.rhai index 78a4155..afef756 100644 --- a/examples/service_manager/basic_usage.rhai +++ b/examples/service_manager/basic_usage.rhai @@ -1,13 +1,17 @@ // Basic Service Manager Usage Example // // This example demonstrates the basic API of the service manager. -// It works on both macOS (launchctl) and Linux (zinit). +// It works on both macOS (launchctl) and Linux (zinit/systemd). // // Prerequisites: // -// Linux: Make sure zinit is running: +// Linux: The service manager will automatically discover running zinit servers +// or fall back to systemd. To use zinit, start it with: // zinit -s /tmp/zinit.sock init // +// You can also specify a custom socket path: +// export ZINIT_SOCKET_PATH=/your/custom/path/zinit.sock +// // macOS: No additional setup required (uses launchctl). // // Usage: diff --git a/service_manager/Cargo.toml b/service_manager/Cargo.toml index 6dc6435..ac4b2b8 100644 --- a/service_manager/Cargo.toml +++ b/service_manager/Cargo.toml @@ -36,6 +36,7 @@ rhai = ["dep:rhai"] tokio-test = "0.4" rhai = { workspace = true } tempfile = { workspace = true } +env_logger = "0.10" [[test]] name = "zinit_integration_tests" diff --git a/service_manager/README.md b/service_manager/README.md index ecb2f90..e6c8c2a 100644 --- a/service_manager/README.md +++ b/service_manager/README.md @@ -159,15 +159,34 @@ herodo your_service_script.rhai ## Prerequisites -### Linux (zinit) +### Linux (zinit/systemd) -Make sure zinit is installed and running: +The service manager automatically discovers running zinit servers and falls back to systemd if none are found. + +**For zinit (recommended):** ```bash # Start zinit with default socket zinit -s /tmp/zinit.sock init + +# Or with a custom socket path +zinit -s /var/run/zinit.sock init ``` +**Socket Discovery:** +The service manager will automatically find running zinit servers by checking: +1. `ZINIT_SOCKET_PATH` environment variable (if set) +2. Common socket locations: `/var/run/zinit.sock`, `/tmp/zinit.sock`, `/run/zinit.sock`, `./zinit.sock` + +**Custom socket path:** +```bash +# Set custom socket path +export ZINIT_SOCKET_PATH=/your/custom/path/zinit.sock +``` + +**Systemd fallback:** +If no zinit server is detected, the service manager automatically falls back to systemd. + ### macOS (launchctl) No additional setup required - uses the built-in launchctl system. diff --git a/service_manager/examples/service_spaghetti.rs b/service_manager/examples/service_spaghetti.rs index 3655fa2..3685bee 100644 --- a/service_manager/examples/service_spaghetti.rs +++ b/service_manager/examples/service_spaghetti.rs @@ -10,6 +10,9 @@ use std::thread; use std::time::Duration; fn main() { + // Initialize logging to see socket discovery in action + env_logger::init(); + let manager = match create_service_manager() { Ok(manager) => manager, Err(e) => { diff --git a/service_manager/examples/simple_service.rs b/service_manager/examples/simple_service.rs index 7336e15..74e4723 100644 --- a/service_manager/examples/simple_service.rs +++ b/service_manager/examples/simple_service.rs @@ -4,6 +4,9 @@ use std::thread; use std::time::Duration; fn main() { + // Initialize logging to see socket discovery in action + env_logger::init(); + // 1. Create a service manager for the current platform let manager = match create_service_manager() { Ok(manager) => manager, diff --git a/service_manager/examples/socket_discovery_test.rs b/service_manager/examples/socket_discovery_test.rs new file mode 100644 index 0000000..e098657 --- /dev/null +++ b/service_manager/examples/socket_discovery_test.rs @@ -0,0 +1,47 @@ +//! Socket Discovery Test +//! +//! This example demonstrates the zinit socket discovery functionality. +//! It shows how the service manager finds available zinit sockets. + +use sal_service_manager::create_service_manager; + +fn main() { + // Initialize logging to see socket discovery in action + env_logger::init(); + + println!("=== Zinit Socket Discovery Test ==="); + println!("This test demonstrates how the service manager discovers zinit sockets."); + println!(); + + // Test environment variable + if let Ok(socket_path) = std::env::var("ZINIT_SOCKET_PATH") { + println!("🔍 ZINIT_SOCKET_PATH environment variable set to: {}", socket_path); + } else { + println!("🔍 ZINIT_SOCKET_PATH environment variable not set"); + } + println!(); + + println!("🚀 Creating service manager..."); + match create_service_manager() { + Ok(_manager) => { + println!("✅ Service manager created successfully!"); + + #[cfg(target_os = "macos")] + println!("📱 Platform: macOS - Using launchctl"); + + #[cfg(target_os = "linux")] + println!("🐧 Platform: Linux - Check logs above for socket discovery details"); + } + Err(e) => { + println!("❌ Failed to create service manager: {}", e); + } + } + + println!(); + println!("=== Test Complete ==="); + println!(); + println!("To test zinit socket discovery on Linux:"); + println!("1. Start zinit: zinit -s /tmp/zinit.sock init"); + println!("2. Run with logging: RUST_LOG=debug cargo run --example socket_discovery_test -p sal-service-manager"); + println!("3. Or set custom path: ZINIT_SOCKET_PATH=/custom/path.sock RUST_LOG=debug cargo run --example socket_discovery_test -p sal-service-manager"); +} diff --git a/service_manager/src/lib.rs b/service_manager/src/lib.rs index 76dfc0e..9f9d7d1 100644 --- a/service_manager/src/lib.rs +++ b/service_manager/src/lib.rs @@ -100,6 +100,89 @@ pub use zinit::ZinitServiceManager; #[cfg(feature = "rhai")] pub mod rhai; +/// Discover available zinit socket paths +/// +/// This function checks for zinit sockets in the following order: +/// 1. Environment variable ZINIT_SOCKET_PATH (if set) +/// 2. Common socket locations with connectivity testing +/// +/// # Returns +/// +/// Returns the first working socket path found, or None if no working zinit server is detected. +#[cfg(target_os = "linux")] +fn discover_zinit_socket() -> Option { + // First check environment variable + if let Ok(env_socket_path) = std::env::var("ZINIT_SOCKET_PATH") { + log::debug!("Checking ZINIT_SOCKET_PATH: {}", env_socket_path); + if test_zinit_socket(&env_socket_path) { + log::info!( + "Using zinit socket from ZINIT_SOCKET_PATH: {}", + env_socket_path + ); + return Some(env_socket_path); + } else { + log::warn!( + "ZINIT_SOCKET_PATH specified but socket is not accessible: {}", + env_socket_path + ); + } + } + + // Try common socket locations + let common_paths = [ + "/var/run/zinit.sock", + "/tmp/zinit.sock", + "/run/zinit.sock", + "./zinit.sock", + ]; + + log::debug!("Discovering zinit socket from common locations..."); + for path in &common_paths { + log::debug!("Testing socket path: {}", path); + if test_zinit_socket(path) { + log::info!("Found working zinit socket at: {}", path); + return Some(path.to_string()); + } + } + + log::debug!("No working zinit socket found"); + None +} + +/// Test if a zinit socket is accessible and responsive +/// +/// This function attempts to create a ZinitServiceManager and perform a basic +/// connectivity test by listing services. +#[cfg(target_os = "linux")] +fn test_zinit_socket(socket_path: &str) -> bool { + // Check if socket file exists first + if !std::path::Path::new(socket_path).exists() { + log::debug!("Socket file does not exist: {}", socket_path); + return false; + } + + // Try to create a manager and test basic connectivity + match ZinitServiceManager::new(socket_path) { + Ok(manager) => { + // Test basic connectivity by trying to list services + match manager.list() { + Ok(_) => { + log::debug!("Socket {} is responsive", socket_path); + true + } + Err(e) => { + log::debug!("Socket {} exists but not responsive: {}", socket_path, e); + false + } + } + } + Err(e) => { + log::debug!("Failed to create manager for socket {}: {}", socket_path, e); + false + } + } +} + /// Create a service manager appropriate for the current platform /// /// - On macOS: Uses launchctl for service management @@ -108,7 +191,12 @@ pub mod rhai; /// # Returns /// /// Returns a Result containing the service manager or an error if initialization fails. -/// On Linux, if zinit is not available, it will automatically fall back to systemd. +/// On Linux, it first tries to discover a working zinit socket. If no zinit server is found, +/// it will fall back to systemd. +/// +/// # Environment Variables +/// +/// - `ZINIT_SOCKET_PATH`: Specifies the zinit socket path (Linux only) /// /// # Errors /// @@ -122,22 +210,28 @@ pub fn create_service_manager() -> Result, ServiceManage } #[cfg(target_os = "linux")] { - // Try zinit first (preferred), fall back to systemd if it fails - let socket_path = "/tmp/zinit.sock"; - match ZinitServiceManager::new(socket_path) { - Ok(zinit_manager) => { - log::debug!("Using zinit service manager"); - Ok(Box::new(zinit_manager)) - } - Err(zinit_error) => { - log::warn!( - "Zinit service manager failed, falling back to systemd: {}", - zinit_error - ); - // Fallback to systemd - Ok(Box::new(SystemdServiceManager::new())) + // Try to discover a working zinit socket + if let Some(socket_path) = discover_zinit_socket() { + match ZinitServiceManager::new(&socket_path) { + Ok(zinit_manager) => { + log::info!("Using zinit service manager with socket: {}", socket_path); + return Ok(Box::new(zinit_manager)); + } + Err(zinit_error) => { + log::warn!( + "Failed to create zinit manager for discovered socket {}: {}", + socket_path, + zinit_error + ); + } } + } else { + log::info!("No running zinit server detected. To use zinit, start it with: zinit -s /tmp/zinit.sock init"); } + + // Fallback to systemd + log::info!("Falling back to systemd service manager"); + Ok(Box::new(SystemdServiceManager::new())) } #[cfg(not(any(target_os = "macos", target_os = "linux")))] { @@ -163,3 +257,45 @@ pub fn create_zinit_service_manager( pub fn create_systemd_service_manager() -> Box { Box::new(SystemdServiceManager::new()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_service_manager() { + // This test ensures the service manager can be created without panicking + let result = create_service_manager(); + assert!(result.is_ok(), "Service manager creation should succeed"); + } + + #[cfg(target_os = "linux")] + #[test] + fn test_socket_discovery_with_env_var() { + // Test that environment variable is respected + std::env::set_var("ZINIT_SOCKET_PATH", "/test/path.sock"); + + // The discover function should check the env var first + // Since the socket doesn't exist, it should return None, but we can't test + // the actual discovery logic without a real socket + + std::env::remove_var("ZINIT_SOCKET_PATH"); + } + + #[cfg(target_os = "linux")] + #[test] + fn test_socket_discovery_without_env_var() { + // Ensure env var is not set + std::env::remove_var("ZINIT_SOCKET_PATH"); + + // The discover function should try common paths + // Since no zinit is running, it should return None + let result = discover_zinit_socket(); + + // This is expected to be None in test environment + assert!( + result.is_none(), + "Should return None when no zinit server is running" + ); + } +}