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

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