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.
This commit is contained in:
Mahmoud-Emad 2025-07-02 16:37:27 +03:00
parent 352e846410
commit 502e345f91
8 changed files with 234 additions and 20 deletions

3
.gitignore vendored
View File

@ -62,4 +62,5 @@ docusaurus.config.ts
sidebars.ts
tsconfig.json
Cargo.toml.bak
Cargo.toml.bak
for_augment

View File

@ -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:

View File

@ -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"

View File

@ -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.

View File

@ -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) => {

View File

@ -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,

View File

@ -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");
}

View File

@ -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<String> {
// 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<Box<dyn ServiceManager>, 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<dyn ServiceManager> {
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"
);
}
}