...
This commit is contained in:
301
_archive/service_manager/src/lib.rs
Normal file
301
_archive/service_manager/src/lib.rs
Normal file
@@ -0,0 +1,301 @@
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ServiceManagerError {
|
||||
#[error("Service '{0}' not found")]
|
||||
ServiceNotFound(String),
|
||||
#[error("Service '{0}' already exists")]
|
||||
ServiceAlreadyExists(String),
|
||||
#[error("Failed to start service '{0}': {1}")]
|
||||
StartFailed(String, String),
|
||||
#[error("Failed to stop service '{0}': {1}")]
|
||||
StopFailed(String, String),
|
||||
#[error("Failed to restart service '{0}': {1}")]
|
||||
RestartFailed(String, String),
|
||||
#[error("Failed to get logs for service '{0}': {1}")]
|
||||
LogsFailed(String, String),
|
||||
#[error("IO error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("Service manager error: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServiceConfig {
|
||||
pub name: String,
|
||||
pub binary_path: String,
|
||||
pub args: Vec<String>,
|
||||
pub working_directory: Option<String>,
|
||||
pub environment: HashMap<String, String>,
|
||||
pub auto_restart: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ServiceStatus {
|
||||
Running,
|
||||
Stopped,
|
||||
Failed,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
pub trait ServiceManager: Send + Sync {
|
||||
/// Check if a service exists
|
||||
fn exists(&self, service_name: &str) -> Result<bool, ServiceManagerError>;
|
||||
|
||||
/// Start a service with the given configuration
|
||||
fn start(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError>;
|
||||
|
||||
/// Start an existing service by name (load existing plist/config)
|
||||
fn start_existing(&self, service_name: &str) -> Result<(), ServiceManagerError>;
|
||||
|
||||
/// Start a service and wait for confirmation that it's running or failed
|
||||
fn start_and_confirm(
|
||||
&self,
|
||||
config: &ServiceConfig,
|
||||
timeout_secs: u64,
|
||||
) -> Result<(), ServiceManagerError>;
|
||||
|
||||
/// Start an existing service and wait for confirmation that it's running or failed
|
||||
fn start_existing_and_confirm(
|
||||
&self,
|
||||
service_name: &str,
|
||||
timeout_secs: u64,
|
||||
) -> Result<(), ServiceManagerError>;
|
||||
|
||||
/// Stop a service by name
|
||||
fn stop(&self, service_name: &str) -> Result<(), ServiceManagerError>;
|
||||
|
||||
/// Restart a service by name
|
||||
fn restart(&self, service_name: &str) -> Result<(), ServiceManagerError>;
|
||||
|
||||
/// Get the status of a service
|
||||
fn status(&self, service_name: &str) -> Result<ServiceStatus, ServiceManagerError>;
|
||||
|
||||
/// Get logs for a service
|
||||
fn logs(&self, service_name: &str, lines: Option<usize>)
|
||||
-> Result<String, ServiceManagerError>;
|
||||
|
||||
/// List all managed services
|
||||
fn list(&self) -> Result<Vec<String>, ServiceManagerError>;
|
||||
|
||||
/// Remove a service configuration (stop if running)
|
||||
fn remove(&self, service_name: &str) -> Result<(), ServiceManagerError>;
|
||||
}
|
||||
|
||||
// Platform-specific implementations
|
||||
#[cfg(target_os = "macos")]
|
||||
mod launchctl;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use launchctl::LaunchctlServiceManager;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod systemd;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use systemd::SystemdServiceManager;
|
||||
|
||||
mod zinit;
|
||||
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
|
||||
/// - On Linux: Uses zinit for service management with systemd fallback
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a Result containing the service manager or an error if initialization fails.
|
||||
/// 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
|
||||
///
|
||||
/// Returns `ServiceManagerError` if:
|
||||
/// - The platform is not supported (Windows, etc.)
|
||||
/// - Service manager initialization fails on all available backends
|
||||
pub fn create_service_manager() -> Result<Box<dyn ServiceManager>, ServiceManagerError> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
Ok(Box::new(LaunchctlServiceManager::new()))
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// 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")))]
|
||||
{
|
||||
Err(ServiceManagerError::Other(
|
||||
"Service manager not implemented for this platform".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a service manager for zinit with a custom socket path
|
||||
///
|
||||
/// This is useful when zinit is running with a non-default socket path
|
||||
pub fn create_zinit_service_manager(
|
||||
socket_path: &str,
|
||||
) -> Result<Box<dyn ServiceManager>, ServiceManagerError> {
|
||||
Ok(Box::new(ZinitServiceManager::new(socket_path)?))
|
||||
}
|
||||
|
||||
/// Create a service manager for systemd (Linux alternative)
|
||||
///
|
||||
/// This creates a systemd-based service manager as an alternative to zinit on Linux
|
||||
#[cfg(target_os = "linux")]
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user