From 9865e601d79d8b74678b1e3db278b085bbb6bda4 Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Mon, 20 Oct 2025 22:29:22 +0200 Subject: [PATCH] Remove _archive directory causing build conflicts --- _archive/service_manager/Cargo.toml | 46 -- _archive/service_manager/README.md | 198 ------- _archive/service_manager/examples/README.md | 47 -- .../examples/service_spaghetti.rs | 109 ---- .../examples/simple_service.rs | 110 ---- .../examples/socket_discovery_test.rs | 47 -- _archive/service_manager/src/launchctl.rs | 492 ------------------ _archive/service_manager/src/lib.rs | 319 ------------ .../service_manager/src/process_manager.rs | 371 ------------- _archive/service_manager/src/rhai.rs | 256 --------- _archive/service_manager/src/systemd.rs | 434 --------------- _archive/service_manager/src/tmux_manager.rs | 404 -------------- _archive/service_manager/src/zinit.rs | 379 -------------- .../service_manager/tests/factory_tests.rs | 243 --------- .../tests/rhai/service_lifecycle.rhai | 177 ------- .../tests/rhai/service_manager_basic.rhai | 218 -------- .../tests/rhai_integration_tests.rs | 252 --------- .../tests/zinit_integration_tests.rs | 317 ----------- 18 files changed, 4419 deletions(-) delete mode 100644 _archive/service_manager/Cargo.toml delete mode 100644 _archive/service_manager/README.md delete mode 100644 _archive/service_manager/examples/README.md delete mode 100644 _archive/service_manager/examples/service_spaghetti.rs delete mode 100644 _archive/service_manager/examples/simple_service.rs delete mode 100644 _archive/service_manager/examples/socket_discovery_test.rs delete mode 100644 _archive/service_manager/src/launchctl.rs delete mode 100644 _archive/service_manager/src/lib.rs delete mode 100644 _archive/service_manager/src/process_manager.rs delete mode 100644 _archive/service_manager/src/rhai.rs delete mode 100644 _archive/service_manager/src/systemd.rs delete mode 100644 _archive/service_manager/src/tmux_manager.rs delete mode 100644 _archive/service_manager/src/zinit.rs delete mode 100644 _archive/service_manager/tests/factory_tests.rs delete mode 100644 _archive/service_manager/tests/rhai/service_lifecycle.rhai delete mode 100644 _archive/service_manager/tests/rhai/service_manager_basic.rhai delete mode 100644 _archive/service_manager/tests/rhai_integration_tests.rs delete mode 100644 _archive/service_manager/tests/zinit_integration_tests.rs diff --git a/_archive/service_manager/Cargo.toml b/_archive/service_manager/Cargo.toml deleted file mode 100644 index d284e5a..0000000 --- a/_archive/service_manager/Cargo.toml +++ /dev/null @@ -1,46 +0,0 @@ -[package] -name = "sal-service-manager" -version = "0.1.0" -edition = "2021" -authors = ["PlanetFirst "] -description = "SAL Service Manager - Cross-platform service management for dynamic worker deployment" -repository = "https://git.threefold.info/herocode/sal" -license = "Apache-2.0" - -[dependencies] -# Use workspace dependencies for consistency -thiserror = "1.0" -tokio = { workspace = true, features = ["process", "time", "sync"] } -log = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -futures = { workspace = true } -once_cell = { workspace = true } -# Use base zinit-client instead of SAL wrapper -zinit-client = { version = "0.4.0" } -# Optional Rhai integration -rhai = { workspace = true, optional = true } -# Process manager dependencies -async-trait = "0.1" -chrono = "0.4" - - -[target.'cfg(target_os = "macos")'.dependencies] -# macOS-specific dependencies for launchctl -plist = "1.6" - -[features] -default = ["zinit"] -zinit = [] -rhai = ["dep:rhai"] - -# Enable zinit feature for tests -[dev-dependencies] -tokio-test = "0.4" -rhai = { workspace = true } -tempfile = { workspace = true } -env_logger = "0.10" - -[[test]] -name = "zinit_integration_tests" -required-features = ["zinit"] diff --git a/_archive/service_manager/README.md b/_archive/service_manager/README.md deleted file mode 100644 index e6c8c2a..0000000 --- a/_archive/service_manager/README.md +++ /dev/null @@ -1,198 +0,0 @@ -# SAL Service Manager - -[![Crates.io](https://img.shields.io/crates/v/sal-service-manager.svg)](https://crates.io/crates/sal-service-manager) -[![Documentation](https://docs.rs/sal-service-manager/badge.svg)](https://docs.rs/sal-service-manager) - -A cross-platform service management library for the System Abstraction Layer (SAL). This crate provides a unified interface for managing system services across different platforms, enabling dynamic deployment of workers and services. - -## Features - -- **Cross-platform service management** - Unified API across macOS and Linux -- **Dynamic worker deployment** - Perfect for circle workers and on-demand services -- **Platform-specific implementations**: - - **macOS**: Uses `launchctl` with plist management - - **Linux**: Uses `zinit` for lightweight service management (systemd also available) -- **Complete lifecycle management** - Start, stop, restart, status monitoring, and log retrieval -- **Service configuration** - Environment variables, working directories, auto-restart -- **Production-ready** - Comprehensive error handling and resource management - -## Usage - -Add this to your `Cargo.toml`: - -```toml -[dependencies] -sal-service-manager = "0.1.0" -``` - -Or use it as part of the SAL ecosystem: - -```toml -[dependencies] -sal = { version = "0.1.0", features = ["service_manager"] } -``` - -## Primary Use Case: Dynamic Circle Worker Management - -This service manager was designed specifically for dynamic deployment of circle workers in freezone environments. When a new resident registers, you can instantly launch a dedicated circle worker: - -```rust,no_run -use sal_service_manager::{create_service_manager, ServiceConfig}; -use std::collections::HashMap; - -// New resident registration triggers worker creation -fn deploy_circle_worker(resident_id: &str) -> Result<(), Box> { - let manager = create_service_manager(); - - let mut env = HashMap::new(); - env.insert("RESIDENT_ID".to_string(), resident_id.to_string()); - env.insert("WORKER_TYPE".to_string(), "circle".to_string()); - - let config = ServiceConfig { - name: format!("circle-worker-{}", resident_id), - binary_path: "/usr/bin/circle-worker".to_string(), - args: vec!["--resident".to_string(), resident_id.to_string()], - working_directory: Some("/var/lib/circle-workers".to_string()), - environment: env, - auto_restart: true, - }; - - // Deploy the worker - manager.start(&config)?; - println!("āœ… Circle worker deployed for resident: {}", resident_id); - - Ok(()) -} -``` - -## Basic Usage Example - -Here is an example of the core service management API: - -```rust,no_run -use sal_service_manager::{create_service_manager, ServiceConfig}; -use std::collections::HashMap; - -fn main() -> Result<(), Box> { - let service_manager = create_service_manager(); - - let config = ServiceConfig { - name: "my-service".to_string(), - binary_path: "/usr/local/bin/my-service-executable".to_string(), - args: vec!["--config".to_string(), "/etc/my-service.conf".to_string()], - working_directory: Some("/var/tmp".to_string()), - environment: HashMap::new(), - auto_restart: true, - }; - - // Start a new service - service_manager.start(&config)?; - - // Get the status of the service - let status = service_manager.status("my-service")?; - println!("Service status: {:?}", status); - - // Stop the service - service_manager.stop("my-service")?; - - Ok(()) -} -``` - -## Examples - -Comprehensive examples are available in the SAL examples directory: - -### Circle Worker Manager Example - -The primary use case - dynamically launching circle workers for new freezone residents: - -```bash -# Run the circle worker management example -herodo examples/service_manager/circle_worker_manager.rhai -``` - -This example demonstrates: -- Creating service configurations for circle workers -- Complete service lifecycle management -- Error handling and status monitoring -- Service cleanup and removal - -### Basic Usage Example - -A simpler example showing the core API: - -```bash -# Run the basic usage example -herodo examples/service_manager/basic_usage.rhai -``` - -See `examples/service_manager/README.md` for detailed documentation. - -## Testing - -Run the test suite: - -```bash -cargo test -p sal-service-manager -``` - -For Rhai integration tests: - -```bash -cargo test -p sal-service-manager --features rhai -``` - -### Testing with Herodo - -To test the service manager with real Rhai scripts using herodo, first build herodo: - -```bash -./build_herodo.sh -``` - -Then run Rhai scripts that use the service manager: - -```bash -herodo your_service_script.rhai -``` - -## Prerequisites - -### Linux (zinit/systemd) - -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. - -## Platform Support - -- **macOS**: Full support using `launchctl` for service management -- **Linux**: Full support using `zinit` for service management (systemd also available as alternative) -- **Windows**: Not currently supported diff --git a/_archive/service_manager/examples/README.md b/_archive/service_manager/examples/README.md deleted file mode 100644 index 7c755fa..0000000 --- a/_archive/service_manager/examples/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Service Manager Examples - -This directory contains examples demonstrating the usage of the `sal-service-manager` crate. - -## Running Examples - -To run any example, use the following command structure from the `service_manager` crate's root directory: - -```sh -cargo run --example -``` - ---- - -### 1. `simple_service` - -This example demonstrates the ideal, clean lifecycle of a service using the separated `create` and `start` steps. - -**Behavior:** -1. Creates a new service definition. -2. Starts the newly created service. -3. Checks its status to confirm it's running. -4. Stops the service. -5. Checks its status again to confirm it's stopped. -6. Removes the service definition. - -**Run it:** -```sh -cargo run --example simple_service -``` - -### 2. `service_spaghetti` - -This example demonstrates how the service manager handles "messy" or improper sequences of operations, showcasing its error handling and robustness. - -**Behavior:** -1. Creates a service. -2. Starts the service. -3. Tries to start the **same service again** (which should fail as it's already running). -4. Removes the service **without stopping it first** (the manager should handle this gracefully). -5. Tries to stop the **already removed** service (which should fail). -6. Tries to remove the service **again** (which should also fail). - -**Run it:** -```sh -cargo run --example service_spaghetti -``` diff --git a/_archive/service_manager/examples/service_spaghetti.rs b/_archive/service_manager/examples/service_spaghetti.rs deleted file mode 100644 index 3685bee..0000000 --- a/_archive/service_manager/examples/service_spaghetti.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! service_spaghetti - An example of messy service management. -//! -//! This example demonstrates how the service manager behaves when commands -//! are issued in a less-than-ideal order, such as starting a service that's -//! already running or removing a service that hasn't been stopped. - -use sal_service_manager::{create_service_manager, ServiceConfig}; -use std::collections::HashMap; -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) => { - eprintln!("Error: Failed to create service manager: {}", e); - return; - } - }; - let service_name = "com.herocode.examples.spaghetti"; - - let service_config = ServiceConfig { - name: service_name.to_string(), - binary_path: "/bin/sh".to_string(), - args: vec![ - "-c".to_string(), - "while true; do echo 'Spaghetti service is running...'; sleep 5; done".to_string(), - ], - working_directory: None, - environment: HashMap::new(), - auto_restart: false, - }; - - println!("--- Service Spaghetti Example ---"); - println!("This example demonstrates messy, error-prone service management."); - - // Cleanup from previous runs to ensure a clean slate - if let Ok(true) = manager.exists(service_name) { - println!( - "\nService '{}' found from a previous run. Cleaning up first.", - service_name - ); - let _ = manager.stop(service_name); - let _ = manager.remove(service_name); - println!("Cleanup complete."); - } - - // 1. Start the service (creates and starts in one step) - println!("\n1. Starting the service for the first time..."); - match manager.start(&service_config) { - Ok(()) => println!(" -> Success: Service '{}' started.", service_name), - Err(e) => { - eprintln!( - " -> Error: Failed to start service: {}. Halting example.", - e - ); - return; - } - } - - thread::sleep(Duration::from_secs(2)); - - // 2. Try to start the service again while it's already running - println!("\n2. Trying to start the *same service* again..."); - match manager.start(&service_config) { - Ok(()) => println!(" -> Unexpected Success: Service started again."), - Err(e) => eprintln!( - " -> Expected Error: {}. The manager should detect it is already running.", - e - ), - } - - // 3. Let it run for a bit - println!("\n3. Letting the service run for 5 seconds..."); - thread::sleep(Duration::from_secs(5)); - - // 4. Remove the service without stopping it first - // The `remove` function is designed to stop the service if it's running. - println!("\n4. Removing the service without explicitly stopping it first..."); - match manager.remove(service_name) { - Ok(()) => println!(" -> Success: Service was stopped and removed."), - Err(e) => eprintln!(" -> Error: Failed to remove service: {}", e), - } - - // 5. Try to stop the service after it has been removed - println!("\n5. Trying to stop the service that was just removed..."); - match manager.stop(service_name) { - Ok(()) => println!(" -> Unexpected Success: Stopped a removed service."), - Err(e) => eprintln!( - " -> Expected Error: {}. The manager knows the service is gone.", - e - ), - } - - // 6. Try to remove the service again - println!("\n6. Trying to remove the service again..."); - match manager.remove(service_name) { - Ok(()) => println!(" -> Unexpected Success: Removed a non-existent service."), - Err(e) => eprintln!( - " -> Expected Error: {}. The manager correctly reports it's not found.", - e - ), - } - - println!("\n--- Spaghetti Example Finished ---"); -} diff --git a/_archive/service_manager/examples/simple_service.rs b/_archive/service_manager/examples/simple_service.rs deleted file mode 100644 index 74e4723..0000000 --- a/_archive/service_manager/examples/simple_service.rs +++ /dev/null @@ -1,110 +0,0 @@ -use sal_service_manager::{create_service_manager, ServiceConfig}; -use std::collections::HashMap; -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, - Err(e) => { - eprintln!("Error: Failed to create service manager: {}", e); - return; - } - }; - - // 2. Define the configuration for our new service - let service_name = "com.herocode.examples.simpleservice"; - let service_config = ServiceConfig { - name: service_name.to_string(), - // A simple command that runs in a loop - binary_path: "/bin/sh".to_string(), - args: vec![ - "-c".to_string(), - "while true; do echo 'Simple service is running...'; date; sleep 5; done".to_string(), - ], - working_directory: None, - environment: HashMap::new(), - auto_restart: false, - }; - - println!("--- Service Manager Example ---"); - - // Cleanup from previous runs, if necessary - if let Ok(true) = manager.exists(service_name) { - println!( - "Service '{}' already exists. Cleaning up before starting.", - service_name - ); - if let Err(e) = manager.stop(service_name) { - println!( - "Note: could not stop existing service (it might not be running): {}", - e - ); - } - if let Err(e) = manager.remove(service_name) { - eprintln!("Error: failed to remove existing service: {}", e); - return; - } - println!("Cleanup complete."); - } - - // 3. Start the service (creates and starts in one step) - println!("\n1. Starting service: '{}'", service_name); - match manager.start(&service_config) { - Ok(()) => println!("Service '{}' started successfully.", service_name), - Err(e) => { - eprintln!("Error: Failed to start service '{}': {}", service_name, e); - return; - } - } - - // Give it a moment to run - println!("\nWaiting for 2 seconds for the service to initialize..."); - thread::sleep(Duration::from_secs(2)); - - // 4. Check the status of the service - println!("\n2. Checking service status..."); - match manager.status(service_name) { - Ok(status) => println!("Service status: {:?}", status), - Err(e) => eprintln!( - "Error: Failed to get status for service '{}': {}", - service_name, e - ), - } - - println!("\nLetting the service run for 10 seconds. Check logs if you can."); - thread::sleep(Duration::from_secs(10)); - - // 5. Stop the service - println!("\n3. Stopping service: '{}'", service_name); - match manager.stop(service_name) { - Ok(()) => println!("Service '{}' stopped successfully.", service_name), - Err(e) => eprintln!("Error: Failed to stop service '{}': {}", service_name, e), - } - - println!("\nWaiting for 2 seconds for the service to stop..."); - thread::sleep(Duration::from_secs(2)); - - // Check status again - println!("\n4. Checking status after stopping..."); - match manager.status(service_name) { - Ok(status) => println!("Service status: {:?}", status), - Err(e) => eprintln!( - "Error: Failed to get status for service '{}': {}", - service_name, e - ), - } - - // 6. Remove the service - println!("\n5. Removing service: '{}'", service_name); - match manager.remove(service_name) { - Ok(()) => println!("Service '{}' removed successfully.", service_name), - Err(e) => eprintln!("Error: Failed to remove service '{}': {}", service_name, e), - } - - println!("\n--- Example Finished ---"); -} diff --git a/_archive/service_manager/examples/socket_discovery_test.rs b/_archive/service_manager/examples/socket_discovery_test.rs deleted file mode 100644 index e098657..0000000 --- a/_archive/service_manager/examples/socket_discovery_test.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! 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/_archive/service_manager/src/launchctl.rs b/_archive/service_manager/src/launchctl.rs deleted file mode 100644 index 6f37630..0000000 --- a/_archive/service_manager/src/launchctl.rs +++ /dev/null @@ -1,492 +0,0 @@ -use crate::{ServiceConfig, ServiceManager, ServiceManagerError, ServiceStatus}; -use once_cell::sync::Lazy; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::path::PathBuf; -use tokio::process::Command; -use tokio::runtime::Runtime; - -// Shared runtime for async operations - production-safe initialization -static ASYNC_RUNTIME: Lazy> = Lazy::new(|| Runtime::new().ok()); - -/// Get the async runtime, creating a temporary one if the static runtime failed -fn get_runtime() -> Result { - // Try to use the static runtime first - if let Some(_runtime) = ASYNC_RUNTIME.as_ref() { - // We can't return a reference to the static runtime because we need ownership - // for block_on, so we create a new one. This is a reasonable trade-off for safety. - Runtime::new().map_err(|e| { - ServiceManagerError::Other(format!("Failed to create async runtime: {}", e)) - }) - } else { - // Static runtime failed, try to create a new one - Runtime::new().map_err(|e| { - ServiceManagerError::Other(format!("Failed to create async runtime: {}", e)) - }) - } -} - -#[derive(Debug)] -pub struct LaunchctlServiceManager { - service_prefix: String, -} - -#[derive(Serialize, Deserialize)] -struct LaunchDaemon { - #[serde(rename = "Label")] - label: String, - #[serde(rename = "ProgramArguments")] - program_arguments: Vec, - #[serde(rename = "WorkingDirectory", skip_serializing_if = "Option::is_none")] - working_directory: Option, - #[serde( - rename = "EnvironmentVariables", - skip_serializing_if = "Option::is_none" - )] - environment_variables: Option>, - #[serde(rename = "KeepAlive", skip_serializing_if = "Option::is_none")] - keep_alive: Option, - #[serde(rename = "RunAtLoad")] - run_at_load: bool, - #[serde(rename = "StandardOutPath", skip_serializing_if = "Option::is_none")] - standard_out_path: Option, - #[serde(rename = "StandardErrorPath", skip_serializing_if = "Option::is_none")] - standard_error_path: Option, -} - -impl LaunchctlServiceManager { - pub fn new() -> Self { - Self { - service_prefix: "tf.ourworld.circles".to_string(), - } - } - - fn get_service_label(&self, service_name: &str) -> String { - format!("{}.{}", self.service_prefix, service_name) - } - - fn get_plist_path(&self, service_name: &str) -> PathBuf { - let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); - PathBuf::from(home) - .join("Library") - .join("LaunchAgents") - .join(format!("{}.plist", self.get_service_label(service_name))) - } - - fn get_log_path(&self, service_name: &str) -> PathBuf { - let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); - PathBuf::from(home) - .join("Library") - .join("Logs") - .join("circles") - .join(format!("{}.log", service_name)) - } - - async fn create_plist(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> { - let label = self.get_service_label(&config.name); - let plist_path = self.get_plist_path(&config.name); - let log_path = self.get_log_path(&config.name); - - // Ensure the LaunchAgents directory exists - if let Some(parent) = plist_path.parent() { - tokio::fs::create_dir_all(parent).await?; - } - - // Ensure the logs directory exists - if let Some(parent) = log_path.parent() { - tokio::fs::create_dir_all(parent).await?; - } - - let mut program_arguments = vec![config.binary_path.clone()]; - program_arguments.extend(config.args.clone()); - - let launch_daemon = LaunchDaemon { - label: label.clone(), - program_arguments, - working_directory: config.working_directory.clone(), - environment_variables: if config.environment.is_empty() { - None - } else { - Some(config.environment.clone()) - }, - keep_alive: if config.auto_restart { - Some(true) - } else { - None - }, - run_at_load: true, - standard_out_path: Some(log_path.to_string_lossy().to_string()), - standard_error_path: Some(log_path.to_string_lossy().to_string()), - }; - - let mut plist_content = Vec::new(); - plist::to_writer_xml(&mut plist_content, &launch_daemon) - .map_err(|e| ServiceManagerError::Other(format!("Failed to serialize plist: {}", e)))?; - let plist_content = String::from_utf8(plist_content).map_err(|e| { - ServiceManagerError::Other(format!("Failed to convert plist to string: {}", e)) - })?; - - tokio::fs::write(&plist_path, plist_content).await?; - - Ok(()) - } - - async fn run_launchctl(&self, args: &[&str]) -> Result { - let output = Command::new("launchctl").args(args).output().await?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - return Err(ServiceManagerError::Other(format!( - "launchctl command failed: {}", - stderr - ))); - } - - Ok(String::from_utf8_lossy(&output.stdout).to_string()) - } - - async fn wait_for_service_status( - &self, - service_name: &str, - timeout_secs: u64, - ) -> Result<(), ServiceManagerError> { - use tokio::time::{sleep, timeout, Duration}; - - let timeout_duration = Duration::from_secs(timeout_secs); - let poll_interval = Duration::from_millis(500); - - let result = timeout(timeout_duration, async { - loop { - match self.status(service_name) { - Ok(ServiceStatus::Running) => { - return Ok(()); - } - Ok(ServiceStatus::Failed) => { - // Service failed, get error details from logs - let logs = self.logs(service_name, Some(20)).unwrap_or_default(); - let error_msg = if logs.is_empty() { - "Service failed to start (no logs available)".to_string() - } else { - // Extract error lines from logs - let error_lines: Vec<&str> = logs - .lines() - .filter(|line| { - line.to_lowercase().contains("error") - || line.to_lowercase().contains("failed") - }) - .take(3) - .collect(); - - if error_lines.is_empty() { - format!( - "Service failed to start. Recent logs:\n{}", - logs.lines() - .rev() - .take(5) - .collect::>() - .into_iter() - .rev() - .collect::>() - .join("\n") - ) - } else { - format!( - "Service failed to start. Errors:\n{}", - error_lines.join("\n") - ) - } - }; - return Err(ServiceManagerError::StartFailed( - service_name.to_string(), - error_msg, - )); - } - Ok(ServiceStatus::Stopped) | Ok(ServiceStatus::Unknown) => { - // Still starting, continue polling - sleep(poll_interval).await; - } - Err(ServiceManagerError::ServiceNotFound(_)) => { - return Err(ServiceManagerError::ServiceNotFound( - service_name.to_string(), - )); - } - Err(e) => { - return Err(e); - } - } - } - }) - .await; - - match result { - Ok(Ok(())) => Ok(()), - Ok(Err(e)) => Err(e), - Err(_) => Err(ServiceManagerError::StartFailed( - service_name.to_string(), - format!("Service did not start within {} seconds", timeout_secs), - )), - } - } -} - -impl ServiceManager for LaunchctlServiceManager { - fn exists(&self, service_name: &str) -> Result { - let plist_path = self.get_plist_path(service_name); - Ok(plist_path.exists()) - } - - fn start(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> { - // Use production-safe runtime for async operations - let runtime = get_runtime()?; - runtime.block_on(async { - let label = self.get_service_label(&config.name); - - // Check if service is already loaded - let list_output = self.run_launchctl(&["list"]).await?; - if list_output.contains(&label) { - return Err(ServiceManagerError::ServiceAlreadyExists( - config.name.clone(), - )); - } - - // Create the plist file - self.create_plist(config).await?; - - // Load the service - let plist_path = self.get_plist_path(&config.name); - self.run_launchctl(&["load", &plist_path.to_string_lossy()]) - .await - .map_err(|e| { - ServiceManagerError::StartFailed(config.name.clone(), e.to_string()) - })?; - - Ok(()) - }) - } - - fn start_existing(&self, service_name: &str) -> Result<(), ServiceManagerError> { - let runtime = get_runtime()?; - runtime.block_on(async { - let label = self.get_service_label(service_name); - let plist_path = self.get_plist_path(service_name); - - // Check if plist file exists - if !plist_path.exists() { - return Err(ServiceManagerError::ServiceNotFound( - service_name.to_string(), - )); - } - - // Check if service is already loaded and running - let list_output = self.run_launchctl(&["list"]).await?; - if list_output.contains(&label) { - // Service is loaded, check if it's running - match self.status(service_name)? { - ServiceStatus::Running => { - return Ok(()); // Already running, nothing to do - } - _ => { - // Service is loaded but not running, try to start it - self.run_launchctl(&["start", &label]).await.map_err(|e| { - ServiceManagerError::StartFailed( - service_name.to_string(), - e.to_string(), - ) - })?; - return Ok(()); - } - } - } - - // Service is not loaded, load it - self.run_launchctl(&["load", &plist_path.to_string_lossy()]) - .await - .map_err(|e| { - ServiceManagerError::StartFailed(service_name.to_string(), e.to_string()) - })?; - - Ok(()) - }) - } - - fn start_and_confirm( - &self, - config: &ServiceConfig, - timeout_secs: u64, - ) -> Result<(), ServiceManagerError> { - // First start the service - self.start(config)?; - - // Then wait for confirmation using production-safe runtime - let runtime = get_runtime()?; - runtime.block_on(async { - self.wait_for_service_status(&config.name, timeout_secs) - .await - }) - } - - fn start_existing_and_confirm( - &self, - service_name: &str, - timeout_secs: u64, - ) -> Result<(), ServiceManagerError> { - // First start the existing service - self.start_existing(service_name)?; - - // Then wait for confirmation using production-safe runtime - let runtime = get_runtime()?; - runtime.block_on(async { - self.wait_for_service_status(service_name, timeout_secs) - .await - }) - } - - fn stop(&self, service_name: &str) -> Result<(), ServiceManagerError> { - let runtime = get_runtime()?; - runtime.block_on(async { - let _label = self.get_service_label(service_name); - let plist_path = self.get_plist_path(service_name); - - // Unload the service - self.run_launchctl(&["unload", &plist_path.to_string_lossy()]) - .await - .map_err(|e| { - ServiceManagerError::StopFailed(service_name.to_string(), e.to_string()) - })?; - - Ok(()) - }) - } - - fn restart(&self, service_name: &str) -> Result<(), ServiceManagerError> { - // For launchctl, we stop and start - if let Err(e) = self.stop(service_name) { - // If stop fails because service doesn't exist, that's ok for restart - if !matches!(e, ServiceManagerError::ServiceNotFound(_)) { - return Err(ServiceManagerError::RestartFailed( - service_name.to_string(), - e.to_string(), - )); - } - } - - // We need the config to restart, but we don't have it stored - // For now, return an error - in a real implementation we might store configs - Err(ServiceManagerError::RestartFailed( - service_name.to_string(), - "Restart requires re-providing service configuration".to_string(), - )) - } - - fn status(&self, service_name: &str) -> Result { - let runtime = get_runtime()?; - runtime.block_on(async { - let label = self.get_service_label(service_name); - let plist_path = self.get_plist_path(service_name); - - // First check if the plist file exists - if !plist_path.exists() { - return Err(ServiceManagerError::ServiceNotFound( - service_name.to_string(), - )); - } - - let list_output = self.run_launchctl(&["list"]).await?; - - if !list_output.contains(&label) { - return Ok(ServiceStatus::Stopped); - } - - // Get detailed status - match self.run_launchctl(&["list", &label]).await { - Ok(output) => { - if output.contains("\"PID\" = ") { - Ok(ServiceStatus::Running) - } else if output.contains("\"LastExitStatus\" = ") { - Ok(ServiceStatus::Failed) - } else { - Ok(ServiceStatus::Unknown) - } - } - Err(_) => Ok(ServiceStatus::Stopped), - } - }) - } - - fn logs( - &self, - service_name: &str, - lines: Option, - ) -> Result { - let runtime = get_runtime()?; - runtime.block_on(async { - let log_path = self.get_log_path(service_name); - - if !log_path.exists() { - return Ok(String::new()); - } - - match lines { - Some(n) => { - let output = Command::new("tail") - .args(&["-n", &n.to_string(), &log_path.to_string_lossy()]) - .output() - .await?; - Ok(String::from_utf8_lossy(&output.stdout).to_string()) - } - None => { - let content = tokio::fs::read_to_string(&log_path).await?; - Ok(content) - } - } - }) - } - - fn list(&self) -> Result, ServiceManagerError> { - let runtime = get_runtime()?; - runtime.block_on(async { - let list_output = self.run_launchctl(&["list"]).await?; - - let services: Vec = list_output - .lines() - .filter_map(|line| { - if line.contains(&self.service_prefix) { - // Extract service name from label - line.split_whitespace() - .last() - .and_then(|label| { - label.strip_prefix(&format!("{}.", self.service_prefix)) - }) - .map(|s| s.to_string()) - } else { - None - } - }) - .collect(); - - Ok(services) - }) - } - - fn remove(&self, service_name: &str) -> Result<(), ServiceManagerError> { - // Try to stop the service first, but don't fail if it's already stopped or doesn't exist - if let Err(e) = self.stop(service_name) { - // Log the error but continue with removal - log::warn!( - "Failed to stop service '{}' before removal: {}", - service_name, - e - ); - } - - // Remove the plist file using production-safe runtime - let runtime = get_runtime()?; - runtime.block_on(async { - let plist_path = self.get_plist_path(service_name); - if plist_path.exists() { - tokio::fs::remove_file(&plist_path).await?; - } - Ok(()) - }) - } -} diff --git a/_archive/service_manager/src/lib.rs b/_archive/service_manager/src/lib.rs deleted file mode 100644 index fb02a9e..0000000 --- a/_archive/service_manager/src/lib.rs +++ /dev/null @@ -1,319 +0,0 @@ -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, - pub working_directory: Option, - pub environment: HashMap, - 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; - - /// 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; - - /// Get logs for a service - fn logs(&self, service_name: &str, lines: Option) - -> Result; - - /// List all managed services - fn list(&self) -> Result, ServiceManagerError>; - - /// Remove a service configuration (stop if running) - fn remove(&self, service_name: &str) -> Result<(), ServiceManagerError>; -} - -// Platform-specific implementations (commented out for now to simplify) -// #[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; - -// Process manager module for actor lifecycle management -pub mod process_manager; -pub use process_manager::{ - ProcessManager, ProcessConfig, ProcessStatus, ProcessManagerError, ProcessManagerResult, - SimpleProcessManager, LogInfo, -}; - -pub mod tmux_manager; -pub use tmux_manager::TmuxProcessManager; - -// Re-export process managers for easier access -pub use process_manager::SimpleProcessManager as SimpleManager; -pub use tmux_manager::TmuxProcessManager as TmuxManager; - -#[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 -/// - 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, ServiceManagerError> { - #[cfg(target_os = "macos")] - { - // LaunchctlServiceManager is commented out for now - // For now, return an error on macOS since launchctl is disabled - Err(ServiceManagerError::Other( - "Service manager not available on macOS (launchctl disabled for simplification)".to_string(), - )) - } - #[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, 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 { - 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" - ); - } -} diff --git a/_archive/service_manager/src/process_manager.rs b/_archive/service_manager/src/process_manager.rs deleted file mode 100644 index 9a6526e..0000000 --- a/_archive/service_manager/src/process_manager.rs +++ /dev/null @@ -1,371 +0,0 @@ -//! # Process Manager -//! -//! This module provides process management abstractions specifically designed for -//! actor lifecycle management. It bridges the gap between the service manager -//! and actor-specific process requirements. -//! -//! The ProcessManager trait provides a unified interface for managing actor processes -//! across different process management systems (tmux, zinit, simple spawning, etc.). - -use async_trait::async_trait; -use std::collections::HashMap; -use std::path::PathBuf; -use std::process::Stdio; -use std::sync::Arc; -use std::time::Duration; -use thiserror::Error; -use tokio::process::{Child, Command}; -use tokio::sync::Mutex; - -/// Errors that can occur during process management operations -#[derive(Error, Debug)] -pub enum ProcessManagerError { - #[error("Process '{0}' not found")] - ProcessNotFound(String), - #[error("Process '{0}' already running")] - ProcessAlreadyRunning(String), - #[error("Failed to start process '{0}': {1}")] - StartupFailed(String, String), - #[error("Failed to stop process '{0}': {1}")] - StopFailed(String, String), - #[error("Failed to get process status '{0}': {1}")] - StatusFailed(String, String), - #[error("Failed to get logs for process '{0}': {1}")] - LogsFailed(String, String), - #[error("Process manager error: {0}")] - Other(String), - #[error("IO error: {0}")] - IoError(#[from] std::io::Error), -} - -/// Result type for process manager operations -pub type ProcessManagerResult = Result; - -/// Represents the current status of a process -#[derive(Debug, Clone, PartialEq)] -pub enum ProcessStatus { - /// Process is not running - Stopped, - /// Process is currently starting up - Starting, - /// Process is running and ready - Running, - /// Process is in the process of stopping - Stopping, - /// Process has encountered an error - Error(String), -} - -/// Configuration for a process -#[derive(Debug, Clone)] -pub struct ProcessConfig { - /// Unique identifier for the process - pub process_id: String, - /// Path to the binary to execute - pub binary_path: PathBuf, - /// Command line arguments - pub args: Vec, - /// Working directory (optional) - pub working_dir: Option, - /// Environment variables - pub env_vars: HashMap, -} - -impl ProcessConfig { - /// Create a new process configuration - pub fn new(process_id: String, binary_path: PathBuf) -> Self { - Self { - process_id, - binary_path, - args: Vec::new(), - working_dir: None, - env_vars: HashMap::new(), - } - } - - /// Add a command line argument - pub fn with_arg(mut self, arg: String) -> Self { - self.args.push(arg); - self - } - - /// Add multiple command line arguments - pub fn with_args(mut self, args: Vec) -> Self { - self.args.extend(args); - self - } - - /// Set the working directory - pub fn with_working_dir(mut self, working_dir: PathBuf) -> Self { - self.working_dir = Some(working_dir); - self - } - - /// Add an environment variable - pub fn with_env_var(mut self, key: String, value: String) -> Self { - self.env_vars.insert(key, value); - self - } -} - -/// Log information for a process -#[derive(Debug, Clone)] -pub struct LogInfo { - /// Timestamp of the log entry - pub timestamp: String, - /// Log level (info, warn, error, etc.) - pub level: String, - /// Log message content - pub message: String, -} - -/// Process manager abstraction for different process management systems -#[async_trait] -pub trait ProcessManager: Send + Sync { - /// Start a process with the given configuration - async fn start_process(&mut self, config: &ProcessConfig) -> ProcessManagerResult<()>; - - /// Stop a process by process ID - async fn stop_process(&mut self, process_id: &str, force: bool) -> ProcessManagerResult<()>; - - /// Get the status of a process - async fn process_status(&self, process_id: &str) -> ProcessManagerResult; - - /// Get logs for a process - async fn process_logs(&self, process_id: &str, lines: Option, follow: bool) -> ProcessManagerResult>; - - /// Check if the process manager is available and working - async fn health_check(&self) -> ProcessManagerResult<()>; - - /// List all managed processes - async fn list_processes(&self) -> ProcessManagerResult>; -} - -/// Simple process manager implementation using direct process spawning -/// This is useful for development and testing, but production should use -/// more robust process managers like tmux or zinit. -pub struct SimpleProcessManager { - processes: Arc>>, -} - -impl SimpleProcessManager { - /// Create a new simple process manager - pub fn new() -> Self { - Self { - processes: Arc::new(Mutex::new(HashMap::new())), - } - } - - fn build_command(&self, config: &ProcessConfig) -> Command { - let mut cmd = Command::new(&config.binary_path); - - // Add arguments - for arg in &config.args { - cmd.arg(arg); - } - - // Set working directory - if let Some(working_dir) = &config.working_dir { - cmd.current_dir(working_dir); - } - - // Set environment variables - for (key, value) in &config.env_vars { - cmd.env(key, value); - } - - // Configure stdio - cmd.stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .stdin(Stdio::null()); - - cmd - } -} - -impl Default for SimpleProcessManager { - fn default() -> Self { - Self::new() - } -} - -#[async_trait] -impl ProcessManager for SimpleProcessManager { - async fn start_process(&mut self, config: &ProcessConfig) -> ProcessManagerResult<()> { - let mut processes = self.processes.lock().await; - - if processes.contains_key(&config.process_id) { - return Err(ProcessManagerError::ProcessAlreadyRunning(config.process_id.clone())); - } - - let mut cmd = self.build_command(config); - - log::debug!("Starting process for {}: {:?}", config.process_id, cmd); - - let child = cmd.spawn().map_err(|e| ProcessManagerError::StartupFailed( - config.process_id.clone(), - format!("Failed to spawn process: {}", e), - ))?; - - processes.insert(config.process_id.clone(), child); - - // Wait a moment to ensure the process started successfully - drop(processes); - tokio::time::sleep(Duration::from_millis(100)).await; - let mut processes = self.processes.lock().await; - - // Check if the process is still running - if let Some(child) = processes.get_mut(&config.process_id) { - match child.try_wait() { - Ok(Some(status)) => { - processes.remove(&config.process_id); - return Err(ProcessManagerError::StartupFailed( - config.process_id.clone(), - format!("Process exited immediately with status: {}", status), - )); - } - Ok(None) => { - // Process is still running - log::info!("Successfully started process {}", config.process_id); - } - Err(e) => { - processes.remove(&config.process_id); - return Err(ProcessManagerError::StartupFailed( - config.process_id.clone(), - format!("Failed to check process status: {}", e), - )); - } - } - } - - Ok(()) - } - - async fn stop_process(&mut self, process_id: &str, force: bool) -> ProcessManagerResult<()> { - let mut processes = self.processes.lock().await; - - let mut child = processes.remove(process_id) - .ok_or_else(|| ProcessManagerError::ProcessNotFound(process_id.to_string()))?; - - if force { - child.kill().await.map_err(|e| ProcessManagerError::StopFailed( - process_id.to_string(), - format!("Failed to kill process: {}", e), - ))?; - } else { - // Try graceful shutdown first - if let Some(id) = child.id() { - #[cfg(unix)] - { - use std::process::Command as StdCommand; - let _ = StdCommand::new("kill") - .arg("-TERM") - .arg(id.to_string()) - .output(); - - // Wait a bit for graceful shutdown - tokio::time::sleep(Duration::from_secs(5)).await; - } - } - - // Force kill if still running - let _ = child.kill().await; - } - - // Wait for the process to exit - let _ = child.wait().await; - - log::info!("Successfully stopped process {}", process_id); - Ok(()) - } - - async fn process_status(&self, process_id: &str) -> ProcessManagerResult { - let mut processes = self.processes.lock().await; - - if let Some(child) = processes.get_mut(process_id) { - match child.try_wait() { - Ok(Some(_)) => { - // Process has exited - processes.remove(process_id); - Ok(ProcessStatus::Stopped) - } - Ok(None) => { - // Process is still running - Ok(ProcessStatus::Running) - } - Err(e) => { - Ok(ProcessStatus::Error(format!("Failed to check status: {}", e))) - } - } - } else { - Ok(ProcessStatus::Stopped) - } - } - - async fn process_logs(&self, process_id: &str, _lines: Option, _follow: bool) -> ProcessManagerResult> { - // Simple process manager doesn't capture logs by default - // This would require more sophisticated process management - log::warn!("Log retrieval not implemented for SimpleProcessManager"); - Ok(vec![LogInfo { - timestamp: chrono::Utc::now().to_rfc3339(), - level: "info".to_string(), - message: format!("Log retrieval not available for process {}", process_id), - }]) - } - - async fn health_check(&self) -> ProcessManagerResult<()> { - // Simple process manager is always healthy if we can lock the processes - let _processes = self.processes.lock().await; - Ok(()) - } - - async fn list_processes(&self) -> ProcessManagerResult> { - let processes = self.processes.lock().await; - Ok(processes.keys().cloned().collect()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::path::PathBuf; - - #[tokio::test] - async fn test_process_config_creation() { - let config = ProcessConfig::new( - "test_process".to_string(), - PathBuf::from("/usr/bin/echo"), - ) - .with_arg("hello".to_string()) - .with_arg("world".to_string()) - .with_env_var("TEST_VAR".to_string(), "test_value".to_string()); - - assert_eq!(config.process_id, "test_process"); - assert_eq!(config.binary_path, PathBuf::from("/usr/bin/echo")); - assert_eq!(config.args, vec!["hello", "world"]); - assert_eq!(config.env_vars.get("TEST_VAR"), Some(&"test_value".to_string())); - } - - #[tokio::test] - async fn test_simple_process_manager_creation() { - let pm = SimpleProcessManager::new(); - assert!(pm.health_check().await.is_ok()); - } - - #[tokio::test] - async fn test_process_status_types() { - let status1 = ProcessStatus::Running; - let status2 = ProcessStatus::Stopped; - let status3 = ProcessStatus::Error("test error".to_string()); - - assert_eq!(status1, ProcessStatus::Running); - assert_eq!(status2, ProcessStatus::Stopped); - assert_ne!(status1, status2); - - if let ProcessStatus::Error(msg) = status3 { - assert_eq!(msg, "test error"); - } else { - panic!("Expected Error status"); - } - } -} diff --git a/_archive/service_manager/src/rhai.rs b/_archive/service_manager/src/rhai.rs deleted file mode 100644 index ce72813..0000000 --- a/_archive/service_manager/src/rhai.rs +++ /dev/null @@ -1,256 +0,0 @@ -//! Rhai integration for the service manager module -//! -//! This module provides Rhai scripting support for service management operations. - -use crate::{create_service_manager, ServiceConfig, ServiceManager}; -use rhai::{Engine, EvalAltResult, Map}; -use std::collections::HashMap; -use std::sync::Arc; - -/// A wrapper around ServiceManager that can be used in Rhai -#[derive(Clone)] -pub struct RhaiServiceManager { - inner: Arc>, -} - -impl RhaiServiceManager { - pub fn new() -> Result> { - let manager = create_service_manager() - .map_err(|e| format!("Failed to create service manager: {}", e))?; - Ok(Self { - inner: Arc::new(manager), - }) - } -} - -/// Register the service manager module with a Rhai engine -pub fn register_service_manager_module(engine: &mut Engine) -> Result<(), Box> { - // Factory function to create service manager - engine.register_type::(); - engine.register_fn( - "create_service_manager", - || -> Result> { RhaiServiceManager::new() }, - ); - - // Service management functions - engine.register_fn( - "start", - |manager: &mut RhaiServiceManager, config: Map| -> Result<(), Box> { - let service_config = map_to_service_config(config)?; - manager - .inner - .start(&service_config) - .map_err(|e| format!("Failed to start service: {}", e).into()) - }, - ); - - engine.register_fn( - "stop", - |manager: &mut RhaiServiceManager, - service_name: String| - -> Result<(), Box> { - manager - .inner - .stop(&service_name) - .map_err(|e| format!("Failed to stop service: {}", e).into()) - }, - ); - - engine.register_fn( - "restart", - |manager: &mut RhaiServiceManager, - service_name: String| - -> Result<(), Box> { - manager - .inner - .restart(&service_name) - .map_err(|e| format!("Failed to restart service: {}", e).into()) - }, - ); - - engine.register_fn( - "status", - |manager: &mut RhaiServiceManager, - service_name: String| - -> Result> { - let status = manager - .inner - .status(&service_name) - .map_err(|e| format!("Failed to get service status: {}", e))?; - Ok(format!("{:?}", status)) - }, - ); - - engine.register_fn( - "logs", - |manager: &mut RhaiServiceManager, - service_name: String, - lines: i64| - -> Result> { - let lines_opt = if lines > 0 { - Some(lines as usize) - } else { - None - }; - manager - .inner - .logs(&service_name, lines_opt) - .map_err(|e| format!("Failed to get service logs: {}", e).into()) - }, - ); - - engine.register_fn( - "list", - |manager: &mut RhaiServiceManager| -> Result, Box> { - manager - .inner - .list() - .map_err(|e| format!("Failed to list services: {}", e).into()) - }, - ); - - engine.register_fn( - "remove", - |manager: &mut RhaiServiceManager, - service_name: String| - -> Result<(), Box> { - manager - .inner - .remove(&service_name) - .map_err(|e| format!("Failed to remove service: {}", e).into()) - }, - ); - - engine.register_fn( - "exists", - |manager: &mut RhaiServiceManager, - service_name: String| - -> Result> { - manager - .inner - .exists(&service_name) - .map_err(|e| format!("Failed to check if service exists: {}", e).into()) - }, - ); - - engine.register_fn( - "start_and_confirm", - |manager: &mut RhaiServiceManager, - config: Map, - timeout_secs: i64| - -> Result<(), Box> { - let service_config = map_to_service_config(config)?; - let timeout = if timeout_secs > 0 { - timeout_secs as u64 - } else { - 30 - }; - manager - .inner - .start_and_confirm(&service_config, timeout) - .map_err(|e| format!("Failed to start and confirm service: {}", e).into()) - }, - ); - - engine.register_fn( - "start_existing_and_confirm", - |manager: &mut RhaiServiceManager, - service_name: String, - timeout_secs: i64| - -> Result<(), Box> { - let timeout = if timeout_secs > 0 { - timeout_secs as u64 - } else { - 30 - }; - manager - .inner - .start_existing_and_confirm(&service_name, timeout) - .map_err(|e| format!("Failed to start existing service and confirm: {}", e).into()) - }, - ); - - Ok(()) -} - -/// Convert a Rhai Map to a ServiceConfig -fn map_to_service_config(map: Map) -> Result> { - let name = map - .get("name") - .and_then(|v| v.clone().into_string().ok()) - .ok_or("Service config must have a 'name' field")?; - - let binary_path = map - .get("binary_path") - .and_then(|v| v.clone().into_string().ok()) - .ok_or("Service config must have a 'binary_path' field")?; - - let args = map - .get("args") - .and_then(|v| v.clone().try_cast::()) - .map(|arr| { - arr.into_iter() - .filter_map(|v| v.into_string().ok()) - .collect::>() - }) - .unwrap_or_default(); - - let working_directory = map - .get("working_directory") - .and_then(|v| v.clone().into_string().ok()); - - let environment = map - .get("environment") - .and_then(|v| v.clone().try_cast::()) - .map(|env_map| { - env_map - .into_iter() - .filter_map(|(k, v)| v.into_string().ok().map(|val| (k.to_string(), val))) - .collect::>() - }) - .unwrap_or_default(); - - let auto_restart = map - .get("auto_restart") - .and_then(|v| v.as_bool().ok()) - .unwrap_or(false); - - Ok(ServiceConfig { - name, - binary_path, - args, - working_directory, - environment, - auto_restart, - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use rhai::{Engine, Map}; - - #[test] - fn test_register_service_manager_module() { - let mut engine = Engine::new(); - register_service_manager_module(&mut engine).unwrap(); - - // Test that the functions are registered - // Note: Rhai doesn't expose a public API to check if functions are registered - // So we'll just verify the module registration doesn't panic - assert!(true); - } - - #[test] - fn test_map_to_service_config() { - let mut map = Map::new(); - map.insert("name".into(), "test-service".into()); - map.insert("binary_path".into(), "/bin/echo".into()); - map.insert("auto_restart".into(), true.into()); - - let config = map_to_service_config(map).unwrap(); - assert_eq!(config.name, "test-service"); - assert_eq!(config.binary_path, "/bin/echo"); - assert_eq!(config.auto_restart, true); - } -} diff --git a/_archive/service_manager/src/systemd.rs b/_archive/service_manager/src/systemd.rs deleted file mode 100644 index f897dbb..0000000 --- a/_archive/service_manager/src/systemd.rs +++ /dev/null @@ -1,434 +0,0 @@ -use crate::{ServiceConfig, ServiceManager, ServiceManagerError, ServiceStatus}; -use std::fs; -use std::path::PathBuf; -use std::process::Command; - -#[derive(Debug)] -pub struct SystemdServiceManager { - service_prefix: String, - user_mode: bool, -} - -impl SystemdServiceManager { - pub fn new() -> Self { - Self { - service_prefix: "sal".to_string(), - user_mode: true, // Default to user services for safety - } - } - - pub fn new_system() -> Self { - Self { - service_prefix: "sal".to_string(), - user_mode: false, // System-wide services (requires root) - } - } - - fn get_service_name(&self, service_name: &str) -> String { - format!("{}-{}.service", self.service_prefix, service_name) - } - - fn get_unit_file_path(&self, service_name: &str) -> PathBuf { - let service_file = self.get_service_name(service_name); - if self.user_mode { - // User service directory - let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); - PathBuf::from(home) - .join(".config") - .join("systemd") - .join("user") - .join(service_file) - } else { - // System service directory - PathBuf::from("/etc/systemd/system").join(service_file) - } - } - - fn run_systemctl(&self, args: &[&str]) -> Result { - let mut cmd = Command::new("systemctl"); - - if self.user_mode { - cmd.arg("--user"); - } - - cmd.args(args); - - let output = cmd - .output() - .map_err(|e| ServiceManagerError::Other(format!("Failed to run systemctl: {}", e)))?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - return Err(ServiceManagerError::Other(format!( - "systemctl command failed: {}", - stderr - ))); - } - - Ok(String::from_utf8_lossy(&output.stdout).to_string()) - } - - fn create_unit_file(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> { - let unit_path = self.get_unit_file_path(&config.name); - - // Ensure the directory exists - if let Some(parent) = unit_path.parent() { - fs::create_dir_all(parent).map_err(|e| { - ServiceManagerError::Other(format!("Failed to create unit directory: {}", e)) - })?; - } - - // Create the unit file content - let mut unit_content = String::new(); - unit_content.push_str("[Unit]\n"); - unit_content.push_str(&format!("Description={} service\n", config.name)); - unit_content.push_str("After=network.target\n\n"); - - unit_content.push_str("[Service]\n"); - unit_content.push_str("Type=simple\n"); - - // Build the ExecStart command - let mut exec_start = config.binary_path.clone(); - for arg in &config.args { - exec_start.push(' '); - exec_start.push_str(arg); - } - unit_content.push_str(&format!("ExecStart={}\n", exec_start)); - - if let Some(working_dir) = &config.working_directory { - unit_content.push_str(&format!("WorkingDirectory={}\n", working_dir)); - } - - // Add environment variables - for (key, value) in &config.environment { - unit_content.push_str(&format!("Environment=\"{}={}\"\n", key, value)); - } - - if config.auto_restart { - unit_content.push_str("Restart=always\n"); - unit_content.push_str("RestartSec=5\n"); - } - - unit_content.push_str("\n[Install]\n"); - unit_content.push_str("WantedBy=default.target\n"); - - // Write the unit file - fs::write(&unit_path, unit_content) - .map_err(|e| ServiceManagerError::Other(format!("Failed to write unit file: {}", e)))?; - - // Reload systemd to pick up the new unit file - self.run_systemctl(&["daemon-reload"])?; - - Ok(()) - } -} - -impl ServiceManager for SystemdServiceManager { - fn exists(&self, service_name: &str) -> Result { - let unit_path = self.get_unit_file_path(service_name); - Ok(unit_path.exists()) - } - - fn start(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> { - let service_name = self.get_service_name(&config.name); - - // Check if service already exists and is running - if self.exists(&config.name)? { - match self.status(&config.name)? { - ServiceStatus::Running => { - return Err(ServiceManagerError::ServiceAlreadyExists( - config.name.clone(), - )); - } - _ => { - // Service exists but not running, we can start it - } - } - } else { - // Create the unit file - self.create_unit_file(config)?; - } - - // Enable and start the service - self.run_systemctl(&["enable", &service_name]) - .map_err(|e| ServiceManagerError::StartFailed(config.name.clone(), e.to_string()))?; - - self.run_systemctl(&["start", &service_name]) - .map_err(|e| ServiceManagerError::StartFailed(config.name.clone(), e.to_string()))?; - - Ok(()) - } - - fn start_existing(&self, service_name: &str) -> Result<(), ServiceManagerError> { - let service_unit = self.get_service_name(service_name); - - // Check if unit file exists - if !self.exists(service_name)? { - return Err(ServiceManagerError::ServiceNotFound( - service_name.to_string(), - )); - } - - // Check if already running - match self.status(service_name)? { - ServiceStatus::Running => { - return Ok(()); // Already running, nothing to do - } - _ => { - // Start the service - self.run_systemctl(&["start", &service_unit]).map_err(|e| { - ServiceManagerError::StartFailed(service_name.to_string(), e.to_string()) - })?; - } - } - - Ok(()) - } - - fn start_and_confirm( - &self, - config: &ServiceConfig, - timeout_secs: u64, - ) -> Result<(), ServiceManagerError> { - // Start the service first - self.start(config)?; - - // Wait for confirmation with timeout - let start_time = std::time::Instant::now(); - let timeout_duration = std::time::Duration::from_secs(timeout_secs); - - while start_time.elapsed() < timeout_duration { - match self.status(&config.name) { - Ok(ServiceStatus::Running) => return Ok(()), - Ok(ServiceStatus::Failed) => { - return Err(ServiceManagerError::StartFailed( - config.name.clone(), - "Service failed to start".to_string(), - )); - } - Ok(_) => { - // Still starting, wait a bit - std::thread::sleep(std::time::Duration::from_millis(100)); - } - Err(_) => { - // Service might not exist yet, wait a bit - std::thread::sleep(std::time::Duration::from_millis(100)); - } - } - } - - Err(ServiceManagerError::StartFailed( - config.name.clone(), - format!("Service did not start within {} seconds", timeout_secs), - )) - } - - fn start_existing_and_confirm( - &self, - service_name: &str, - timeout_secs: u64, - ) -> Result<(), ServiceManagerError> { - // Start the existing service first - self.start_existing(service_name)?; - - // Wait for confirmation with timeout - let start_time = std::time::Instant::now(); - let timeout_duration = std::time::Duration::from_secs(timeout_secs); - - while start_time.elapsed() < timeout_duration { - match self.status(service_name) { - Ok(ServiceStatus::Running) => return Ok(()), - Ok(ServiceStatus::Failed) => { - return Err(ServiceManagerError::StartFailed( - service_name.to_string(), - "Service failed to start".to_string(), - )); - } - Ok(_) => { - // Still starting, wait a bit - std::thread::sleep(std::time::Duration::from_millis(100)); - } - Err(_) => { - // Service might not exist yet, wait a bit - std::thread::sleep(std::time::Duration::from_millis(100)); - } - } - } - - Err(ServiceManagerError::StartFailed( - service_name.to_string(), - format!("Service did not start within {} seconds", timeout_secs), - )) - } - - fn stop(&self, service_name: &str) -> Result<(), ServiceManagerError> { - let service_unit = self.get_service_name(service_name); - - // Check if service exists - if !self.exists(service_name)? { - return Err(ServiceManagerError::ServiceNotFound( - service_name.to_string(), - )); - } - - // Stop the service - self.run_systemctl(&["stop", &service_unit]).map_err(|e| { - ServiceManagerError::StopFailed(service_name.to_string(), e.to_string()) - })?; - - Ok(()) - } - - fn restart(&self, service_name: &str) -> Result<(), ServiceManagerError> { - let service_unit = self.get_service_name(service_name); - - // Check if service exists - if !self.exists(service_name)? { - return Err(ServiceManagerError::ServiceNotFound( - service_name.to_string(), - )); - } - - // Restart the service - self.run_systemctl(&["restart", &service_unit]) - .map_err(|e| { - ServiceManagerError::RestartFailed(service_name.to_string(), e.to_string()) - })?; - - Ok(()) - } - - fn status(&self, service_name: &str) -> Result { - let service_unit = self.get_service_name(service_name); - - // Check if service exists - if !self.exists(service_name)? { - return Err(ServiceManagerError::ServiceNotFound( - service_name.to_string(), - )); - } - - // Get service status - let output = self - .run_systemctl(&["is-active", &service_unit]) - .unwrap_or_else(|_| "unknown".to_string()); - - let status = match output.trim() { - "active" => ServiceStatus::Running, - "inactive" => ServiceStatus::Stopped, - "failed" => ServiceStatus::Failed, - _ => ServiceStatus::Unknown, - }; - - Ok(status) - } - - fn logs( - &self, - service_name: &str, - lines: Option, - ) -> Result { - let service_unit = self.get_service_name(service_name); - - // Check if service exists - if !self.exists(service_name)? { - return Err(ServiceManagerError::ServiceNotFound( - service_name.to_string(), - )); - } - - // Build journalctl command - let mut args = vec!["--unit", &service_unit, "--no-pager"]; - let lines_arg; - if let Some(n) = lines { - lines_arg = format!("--lines={}", n); - args.push(&lines_arg); - } - - // Use journalctl to get logs - let mut cmd = std::process::Command::new("journalctl"); - if self.user_mode { - cmd.arg("--user"); - } - cmd.args(&args); - - let output = cmd.output().map_err(|e| { - ServiceManagerError::LogsFailed( - service_name.to_string(), - format!("Failed to run journalctl: {}", e), - ) - })?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - return Err(ServiceManagerError::LogsFailed( - service_name.to_string(), - format!("journalctl command failed: {}", stderr), - )); - } - - Ok(String::from_utf8_lossy(&output.stdout).to_string()) - } - - fn list(&self) -> Result, ServiceManagerError> { - // List all services with our prefix - let output = - self.run_systemctl(&["list-units", "--type=service", "--all", "--no-pager"])?; - - let mut services = Vec::new(); - for line in output.lines() { - if line.contains(&format!("{}-", self.service_prefix)) { - // Extract service name from the line - if let Some(unit_name) = line.split_whitespace().next() { - if let Some(service_name) = unit_name.strip_suffix(".service") { - if let Some(name) = - service_name.strip_prefix(&format!("{}-", self.service_prefix)) - { - services.push(name.to_string()); - } - } - } - } - } - - Ok(services) - } - - fn remove(&self, service_name: &str) -> Result<(), ServiceManagerError> { - let service_unit = self.get_service_name(service_name); - - // Check if service exists - if !self.exists(service_name)? { - return Err(ServiceManagerError::ServiceNotFound( - service_name.to_string(), - )); - } - - // Try to stop the service first, but don't fail if it's already stopped - if let Err(e) = self.stop(service_name) { - log::warn!( - "Failed to stop service '{}' before removal: {}", - service_name, - e - ); - } - - // Disable the service - if let Err(e) = self.run_systemctl(&["disable", &service_unit]) { - log::warn!("Failed to disable service '{}': {}", service_name, e); - } - - // Remove the unit file - let unit_path = self.get_unit_file_path(service_name); - if unit_path.exists() { - std::fs::remove_file(&unit_path).map_err(|e| { - ServiceManagerError::Other(format!("Failed to remove unit file: {}", e)) - })?; - } - - // Reload systemd to pick up the changes - self.run_systemctl(&["daemon-reload"])?; - - Ok(()) - } -} diff --git a/_archive/service_manager/src/tmux_manager.rs b/_archive/service_manager/src/tmux_manager.rs deleted file mode 100644 index c85042b..0000000 --- a/_archive/service_manager/src/tmux_manager.rs +++ /dev/null @@ -1,404 +0,0 @@ -//! # Tmux Process Manager -//! -//! This module provides a tmux-based process manager implementation that manages -//! processes within tmux sessions and windows. This is useful for production -//! environments where you need persistent, manageable processes. - -use async_trait::async_trait; -use chrono::Utc; -use std::process::Output; -use tokio::process::Command; - -use crate::process_manager::{ - LogInfo, ProcessConfig, ProcessManager, ProcessManagerError, ProcessManagerResult, - ProcessStatus, -}; - -/// Tmux-based process manager implementation -/// -/// This manager creates and manages processes within tmux sessions, providing -/// better process isolation and management capabilities compared to simple spawning. -pub struct TmuxProcessManager { - /// Name of the tmux session to use - session_name: String, -} - -impl TmuxProcessManager { - /// Create a new tmux process manager with the specified session name - pub fn new(session_name: String) -> Self { - Self { session_name } - } - - /// Execute a tmux command and return the output - async fn tmux_command(&self, args: &[&str]) -> ProcessManagerResult { - let output = Command::new("tmux") - .args(args) - .output() - .await - .map_err(|e| ProcessManagerError::Other(format!("Failed to execute tmux command: {}", e)))?; - - log::debug!("Tmux command: tmux {}", args.join(" ")); - log::debug!("Tmux output: {}", String::from_utf8_lossy(&output.stdout)); - - if !output.stderr.is_empty() { - log::debug!("Tmux stderr: {}", String::from_utf8_lossy(&output.stderr)); - } - - Ok(output) - } - - /// Create the tmux session if it doesn't exist - async fn create_session_if_needed(&self) -> ProcessManagerResult<()> { - // Check if session exists - let output = self - .tmux_command(&["has-session", "-t", &self.session_name]) - .await?; - - if !output.status.success() { - // Session doesn't exist, create it - log::info!("Creating tmux session: {}", self.session_name); - let output = self - .tmux_command(&["new-session", "-d", "-s", &self.session_name]) - .await?; - - if !output.status.success() { - return Err(ProcessManagerError::Other(format!( - "Failed to create tmux session '{}': {}", - self.session_name, - String::from_utf8_lossy(&output.stderr) - ))); - } - } - - Ok(()) - } - - /// Build the command string for running a process - fn build_process_command(&self, config: &ProcessConfig) -> String { - let mut cmd_parts = vec![config.binary_path.to_string_lossy().to_string()]; - cmd_parts.extend(config.args.clone()); - cmd_parts.join(" ") - } - - /// Get the window name for a process - fn get_window_name(&self, process_id: &str) -> String { - format!("proc-{}", process_id) - } -} - -#[async_trait] -impl ProcessManager for TmuxProcessManager { - async fn start_process(&mut self, config: &ProcessConfig) -> ProcessManagerResult<()> { - self.create_session_if_needed().await?; - - let window_name = self.get_window_name(&config.process_id); - let command = self.build_process_command(config); - - // Check if window already exists - let check_output = self - .tmux_command(&[ - "list-windows", - "-t", - &self.session_name, - "-F", - "#{window_name}", - ]) - .await?; - - let existing_windows = String::from_utf8_lossy(&check_output.stdout); - if existing_windows.lines().any(|line| line.trim() == window_name) { - return Err(ProcessManagerError::ProcessAlreadyRunning(config.process_id.clone())); - } - - // Create new window and run the process - let mut tmux_args = vec![ - "new-window", - "-t", - &self.session_name, - "-n", - &window_name, - ]; - - // Set working directory if specified - let working_dir_arg; - if let Some(working_dir) = &config.working_dir { - working_dir_arg = working_dir.to_string_lossy().to_string(); - tmux_args.extend(&["-c", &working_dir_arg]); - } - - tmux_args.push(&command); - - let output = self.tmux_command(&tmux_args).await?; - - if !output.status.success() { - return Err(ProcessManagerError::StartupFailed( - config.process_id.clone(), - format!( - "Failed to create tmux window: {}", - String::from_utf8_lossy(&output.stderr) - ), - )); - } - - // Wait a moment and check if the process is still running - tokio::time::sleep(std::time::Duration::from_millis(500)).await; - - match self.process_status(&config.process_id).await? { - ProcessStatus::Running => { - log::info!("Successfully started process {} in tmux window {}", config.process_id, window_name); - Ok(()) - } - ProcessStatus::Stopped => { - Err(ProcessManagerError::StartupFailed( - config.process_id.clone(), - "Process exited immediately after startup".to_string(), - )) - } - ProcessStatus::Error(msg) => { - Err(ProcessManagerError::StartupFailed( - config.process_id.clone(), - format!("Process failed to start: {}", msg), - )) - } - _ => Ok(()), - } - } - - async fn stop_process(&mut self, process_id: &str, force: bool) -> ProcessManagerResult<()> { - let window_name = self.get_window_name(process_id); - - // Check if window exists - let check_output = self - .tmux_command(&[ - "list-windows", - "-t", - &self.session_name, - "-F", - "#{window_name}", - ]) - .await?; - - let existing_windows = String::from_utf8_lossy(&check_output.stdout); - if !existing_windows.lines().any(|line| line.trim() == window_name) { - return Err(ProcessManagerError::ProcessNotFound(process_id.to_string())); - } - - if force { - // Kill the window immediately - let output = self - .tmux_command(&["kill-window", "-t", &format!("{}:{}", self.session_name, window_name)]) - .await?; - - if !output.status.success() { - return Err(ProcessManagerError::StopFailed( - process_id.to_string(), - format!( - "Failed to kill tmux window: {}", - String::from_utf8_lossy(&output.stderr) - ), - )); - } - } else { - // Send SIGTERM to the process in the window - let output = self - .tmux_command(&[ - "send-keys", - "-t", - &format!("{}:{}", self.session_name, window_name), - "C-c", - ]) - .await?; - - if !output.status.success() { - log::warn!("Failed to send SIGTERM, trying force kill"); - // Fallback to force kill - return self.stop_process(process_id, true).await; - } - - // Wait a bit for graceful shutdown - tokio::time::sleep(std::time::Duration::from_secs(5)).await; - - // Check if process is still running, force kill if needed - if let Ok(ProcessStatus::Running) = self.process_status(process_id).await { - log::info!("Process {} didn't stop gracefully, force killing", process_id); - return self.stop_process(process_id, true).await; - } - } - - log::info!("Successfully stopped process {}", process_id); - Ok(()) - } - - async fn process_status(&self, process_id: &str) -> ProcessManagerResult { - let window_name = self.get_window_name(process_id); - - // Check if window exists - let check_output = self - .tmux_command(&[ - "list-windows", - "-t", - &self.session_name, - "-F", - "#{window_name}", - ]) - .await?; - - let existing_windows = String::from_utf8_lossy(&check_output.stdout); - if !existing_windows.lines().any(|line| line.trim() == window_name) { - return Ok(ProcessStatus::Stopped); - } - - // Check if there are any panes in the window (process running) - let pane_output = self - .tmux_command(&[ - "list-panes", - "-t", - &format!("{}:{}", self.session_name, window_name), - "-F", - "#{pane_pid}", - ]) - .await?; - - if pane_output.status.success() && !pane_output.stdout.is_empty() { - Ok(ProcessStatus::Running) - } else { - Ok(ProcessStatus::Stopped) - } - } - - async fn process_logs(&self, process_id: &str, lines: Option, _follow: bool) -> ProcessManagerResult> { - let window_name = self.get_window_name(process_id); - - // Capture the pane content (this is the best we can do with tmux) - let target_window = format!("{}:{}", self.session_name, window_name); - let mut tmux_args = vec![ - "capture-pane", - "-t", - &target_window, - "-p", - ]; - - // Add line limit if specified - let lines_arg; - if let Some(line_count) = lines { - lines_arg = format!("-S -{}", line_count); - tmux_args.push(&lines_arg); - } - - let output = self.tmux_command(&tmux_args).await?; - - if !output.status.success() { - return Err(ProcessManagerError::LogsFailed( - process_id.to_string(), - format!( - "Failed to capture tmux pane: {}", - String::from_utf8_lossy(&output.stderr) - ), - )); - } - - let content = String::from_utf8_lossy(&output.stdout); - let timestamp = Utc::now().to_rfc3339(); - - let logs = content - .lines() - .filter(|line| !line.trim().is_empty()) - .map(|line| LogInfo { - timestamp: timestamp.clone(), - level: "info".to_string(), - message: line.to_string(), - }) - .collect(); - - Ok(logs) - } - - async fn health_check(&self) -> ProcessManagerResult<()> { - // Check if tmux is available - let output = Command::new("tmux") - .arg("list-sessions") - .output() - .await - .map_err(|e| ProcessManagerError::Other(format!("Tmux not available: {}", e)))?; - - if !output.status.success() { - let error_msg = String::from_utf8_lossy(&output.stderr); - if error_msg.contains("no server running") { - // This is fine, tmux server will start when needed - Ok(()) - } else { - Err(ProcessManagerError::Other(format!("Tmux health check failed: {}", error_msg))) - } - } else { - Ok(()) - } - } - - async fn list_processes(&self) -> ProcessManagerResult> { - // List all windows in our session that match our process naming pattern - let output = self - .tmux_command(&[ - "list-windows", - "-t", - &self.session_name, - "-F", - "#{window_name}", - ]) - .await?; - - if !output.status.success() { - // Session might not exist - return Ok(Vec::new()); - } - - let windows = String::from_utf8_lossy(&output.stdout); - let processes = windows - .lines() - .filter_map(|line| { - let window_name = line.trim(); - if window_name.starts_with("proc-") { - Some(window_name.strip_prefix("proc-").unwrap().to_string()) - } else { - None - } - }) - .collect(); - - Ok(processes) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::path::PathBuf; - - #[tokio::test] - async fn test_tmux_manager_creation() { - let manager = TmuxProcessManager::new("test_session".to_string()); - assert_eq!(manager.session_name, "test_session"); - } - - #[tokio::test] - async fn test_window_name_generation() { - let manager = TmuxProcessManager::new("test_session".to_string()); - let window_name = manager.get_window_name("test_process"); - assert_eq!(window_name, "proc-test_process"); - } - - #[tokio::test] - async fn test_command_building() { - let manager = TmuxProcessManager::new("test_session".to_string()); - let config = ProcessConfig::new( - "test_process".to_string(), - PathBuf::from("/usr/bin/echo"), - ) - .with_arg("hello".to_string()) - .with_arg("world".to_string()); - - let command = manager.build_process_command(&config); - assert!(command.contains("/usr/bin/echo")); - assert!(command.contains("hello")); - assert!(command.contains("world")); - } -} diff --git a/_archive/service_manager/src/zinit.rs b/_archive/service_manager/src/zinit.rs deleted file mode 100644 index c6f5404..0000000 --- a/_archive/service_manager/src/zinit.rs +++ /dev/null @@ -1,379 +0,0 @@ -use crate::{ServiceConfig, ServiceManager, ServiceManagerError, ServiceStatus}; -use once_cell::sync::Lazy; -use serde_json::json; -use std::sync::Arc; -use std::time::Duration; -use tokio::runtime::Runtime; -use tokio::time::timeout; -use zinit_client::{ServiceStatus as ZinitServiceStatus, ZinitClient, ZinitError}; - -// Shared runtime for async operations - production-safe initialization -static ASYNC_RUNTIME: Lazy> = Lazy::new(|| Runtime::new().ok()); - -/// Get the async runtime, creating a temporary one if the static runtime failed -fn get_runtime() -> Result { - // Try to use the static runtime first - if let Some(_runtime) = ASYNC_RUNTIME.as_ref() { - // We can't return a reference to the static runtime because we need ownership - // for block_on, so we create a new one. This is a reasonable trade-off for safety. - Runtime::new().map_err(|e| { - ServiceManagerError::Other(format!("Failed to create async runtime: {}", e)) - }) - } else { - // Static runtime failed, try to create a new one - Runtime::new().map_err(|e| { - ServiceManagerError::Other(format!("Failed to create async runtime: {}", e)) - }) - } -} - -pub struct ZinitServiceManager { - client: Arc, -} - -impl ZinitServiceManager { - pub fn new(socket_path: &str) -> Result { - // Create the base zinit client directly - let client = Arc::new(ZinitClient::new(socket_path)); - - Ok(ZinitServiceManager { client }) - } - - /// Execute an async operation using the shared runtime or current context - fn execute_async(&self, operation: F) -> Result - where - F: std::future::Future> + Send + 'static, - T: Send + 'static, - { - // Check if we're already in a tokio runtime context - if let Ok(_handle) = tokio::runtime::Handle::try_current() { - // We're in an async context, use spawn_blocking to avoid nested runtime - let result = std::thread::spawn( - move || -> Result, ServiceManagerError> { - let rt = Runtime::new().map_err(|e| { - ServiceManagerError::Other(format!("Failed to create runtime: {}", e)) - })?; - Ok(rt.block_on(operation)) - }, - ) - .join() - .map_err(|_| ServiceManagerError::Other("Thread join failed".to_string()))?; - result?.map_err(|e| ServiceManagerError::Other(e.to_string())) - } else { - // No current runtime, use production-safe runtime - let runtime = get_runtime()?; - runtime - .block_on(operation) - .map_err(|e| ServiceManagerError::Other(e.to_string())) - } - } - - /// Execute an async operation with timeout using the shared runtime or current context - fn execute_async_with_timeout( - &self, - operation: F, - timeout_secs: u64, - ) -> Result - where - F: std::future::Future> + Send + 'static, - T: Send + 'static, - { - let timeout_duration = Duration::from_secs(timeout_secs); - let timeout_op = timeout(timeout_duration, operation); - - // Check if we're already in a tokio runtime context - if let Ok(_handle) = tokio::runtime::Handle::try_current() { - // We're in an async context, use spawn_blocking to avoid nested runtime - let result = std::thread::spawn(move || { - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(timeout_op) - }) - .join() - .map_err(|_| ServiceManagerError::Other("Thread join failed".to_string()))?; - - result - .map_err(|_| { - ServiceManagerError::Other(format!( - "Operation timed out after {} seconds", - timeout_secs - )) - })? - .map_err(|e| ServiceManagerError::Other(e.to_string())) - } else { - // No current runtime, use production-safe runtime - let runtime = get_runtime()?; - runtime - .block_on(timeout_op) - .map_err(|_| { - ServiceManagerError::Other(format!( - "Operation timed out after {} seconds", - timeout_secs - )) - })? - .map_err(|e| ServiceManagerError::Other(e.to_string())) - } - } -} - -impl ServiceManager for ZinitServiceManager { - fn exists(&self, service_name: &str) -> Result { - let status_res = self.status(service_name); - match status_res { - Ok(_) => Ok(true), - Err(ServiceManagerError::ServiceNotFound(_)) => Ok(false), - Err(e) => Err(e), - } - } - - fn start(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> { - // Build the exec command with args - let mut exec_command = config.binary_path.clone(); - if !config.args.is_empty() { - exec_command.push(' '); - exec_command.push_str(&config.args.join(" ")); - } - - // Create zinit-compatible service configuration - let mut service_config = json!({ - "exec": exec_command, - "oneshot": !config.auto_restart, // zinit uses oneshot, not restart - "env": config.environment, - }); - - // Add optional fields if present - if let Some(ref working_dir) = config.working_directory { - // Zinit doesn't support working_directory directly, so we need to modify the exec command - let cd_command = format!("cd {} && {}", working_dir, exec_command); - service_config["exec"] = json!(cd_command); - } - - let client = Arc::clone(&self.client); - let service_name = config.name.clone(); - self.execute_async( - async move { client.create_service(&service_name, service_config).await }, - ) - .map_err(|e| ServiceManagerError::StartFailed(config.name.clone(), e.to_string()))?; - - self.start_existing(&config.name) - } - - fn start_existing(&self, service_name: &str) -> Result<(), ServiceManagerError> { - let client = Arc::clone(&self.client); - let service_name_owned = service_name.to_string(); - let service_name_for_error = service_name.to_string(); - self.execute_async(async move { client.start(&service_name_owned).await }) - .map_err(|e| ServiceManagerError::StartFailed(service_name_for_error, e.to_string())) - } - - fn start_and_confirm( - &self, - config: &ServiceConfig, - timeout_secs: u64, - ) -> Result<(), ServiceManagerError> { - // Start the service first - self.start(config)?; - - // Wait for confirmation with timeout using the shared runtime - self.execute_async_with_timeout( - async move { - let start_time = std::time::Instant::now(); - let timeout_duration = Duration::from_secs(timeout_secs); - - while start_time.elapsed() < timeout_duration { - // We need to call status in a blocking way from within the async context - // For now, we'll use a simple polling approach - tokio::time::sleep(Duration::from_millis(100)).await; - } - - // Return a timeout error that will be handled by execute_async_with_timeout - // Use a generic error since we don't know the exact ZinitError variants - Err(ZinitError::from(std::io::Error::new( - std::io::ErrorKind::TimedOut, - "Timeout waiting for service confirmation", - ))) - }, - timeout_secs, - )?; - - // Check final status - match self.status(&config.name)? { - ServiceStatus::Running => Ok(()), - ServiceStatus::Failed => Err(ServiceManagerError::StartFailed( - config.name.clone(), - "Service failed to start".to_string(), - )), - _ => Err(ServiceManagerError::StartFailed( - config.name.clone(), - format!("Service did not start within {} seconds", timeout_secs), - )), - } - } - - fn start_existing_and_confirm( - &self, - service_name: &str, - timeout_secs: u64, - ) -> Result<(), ServiceManagerError> { - // Start the existing service first - self.start_existing(service_name)?; - - // Wait for confirmation with timeout using the shared runtime - self.execute_async_with_timeout( - async move { - let start_time = std::time::Instant::now(); - let timeout_duration = Duration::from_secs(timeout_secs); - - while start_time.elapsed() < timeout_duration { - tokio::time::sleep(Duration::from_millis(100)).await; - } - - // Return a timeout error that will be handled by execute_async_with_timeout - // Use a generic error since we don't know the exact ZinitError variants - Err(ZinitError::from(std::io::Error::new( - std::io::ErrorKind::TimedOut, - "Timeout waiting for service confirmation", - ))) - }, - timeout_secs, - )?; - - // Check final status - match self.status(service_name)? { - ServiceStatus::Running => Ok(()), - ServiceStatus::Failed => Err(ServiceManagerError::StartFailed( - service_name.to_string(), - "Service failed to start".to_string(), - )), - _ => Err(ServiceManagerError::StartFailed( - service_name.to_string(), - format!("Service did not start within {} seconds", timeout_secs), - )), - } - } - - fn stop(&self, service_name: &str) -> Result<(), ServiceManagerError> { - let client = Arc::clone(&self.client); - let service_name_owned = service_name.to_string(); - let service_name_for_error = service_name.to_string(); - self.execute_async(async move { client.stop(&service_name_owned).await }) - .map_err(|e| ServiceManagerError::StopFailed(service_name_for_error, e.to_string())) - } - - fn restart(&self, service_name: &str) -> Result<(), ServiceManagerError> { - let client = Arc::clone(&self.client); - let service_name_owned = service_name.to_string(); - let service_name_for_error = service_name.to_string(); - self.execute_async(async move { client.restart(&service_name_owned).await }) - .map_err(|e| ServiceManagerError::RestartFailed(service_name_for_error, e.to_string())) - } - - fn status(&self, service_name: &str) -> Result { - let client = Arc::clone(&self.client); - let service_name_owned = service_name.to_string(); - let service_name_for_error = service_name.to_string(); - let status: ZinitServiceStatus = self - .execute_async(async move { client.status(&service_name_owned).await }) - .map_err(|e| { - // Check if this is a "service not found" error - if e.to_string().contains("not found") || e.to_string().contains("does not exist") { - ServiceManagerError::ServiceNotFound(service_name_for_error) - } else { - ServiceManagerError::Other(e.to_string()) - } - })?; - - // ServiceStatus is a struct with fields, not an enum - // We need to check the state field to determine the status - // Convert ServiceState to string and match on that - let state_str = format!("{:?}", status.state).to_lowercase(); - let service_status = match state_str.as_str() { - s if s.contains("running") => crate::ServiceStatus::Running, - s if s.contains("stopped") => crate::ServiceStatus::Stopped, - s if s.contains("failed") => crate::ServiceStatus::Failed, - _ => crate::ServiceStatus::Unknown, - }; - Ok(service_status) - } - - fn logs( - &self, - service_name: &str, - _lines: Option, - ) -> Result { - // The logs method takes (follow: bool, filter: Option>) - let client = Arc::clone(&self.client); - let service_name_owned = service_name.to_string(); - let logs = self - .execute_async(async move { - use futures::StreamExt; - use tokio::time::{timeout, Duration}; - - let mut log_stream = client - .logs(false, Some(service_name_owned.as_str())) - .await?; - let mut logs = Vec::new(); - - // Collect logs from the stream with a reasonable limit - let mut count = 0; - const MAX_LOGS: usize = 100; - const LOG_TIMEOUT: Duration = Duration::from_secs(5); - - // Use timeout to prevent hanging - let result = timeout(LOG_TIMEOUT, async { - while let Some(log_result) = log_stream.next().await { - match log_result { - Ok(log_entry) => { - logs.push(format!("{:?}", log_entry)); - count += 1; - if count >= MAX_LOGS { - break; - } - } - Err(_) => break, - } - } - }) - .await; - - // Handle timeout - this is not an error, just means no more logs available - if result.is_err() { - log::debug!( - "Log reading timed out after {} seconds, returning {} logs", - LOG_TIMEOUT.as_secs(), - logs.len() - ); - } - - Ok::, ZinitError>(logs) - }) - .map_err(|e| { - ServiceManagerError::LogsFailed(service_name.to_string(), e.to_string()) - })?; - Ok(logs.join("\n")) - } - - fn list(&self) -> Result, ServiceManagerError> { - let client = Arc::clone(&self.client); - let services = self - .execute_async(async move { client.list().await }) - .map_err(|e| ServiceManagerError::Other(e.to_string()))?; - Ok(services.keys().cloned().collect()) - } - - fn remove(&self, service_name: &str) -> Result<(), ServiceManagerError> { - // Try to stop the service first, but don't fail if it's already stopped or doesn't exist - if let Err(e) = self.stop(service_name) { - // Log the error but continue with removal - log::warn!( - "Failed to stop service '{}' before removal: {}", - service_name, - e - ); - } - - let client = Arc::clone(&self.client); - let service_name = service_name.to_string(); - self.execute_async(async move { client.delete_service(&service_name).await }) - .map_err(|e| ServiceManagerError::Other(e.to_string())) - } -} diff --git a/_archive/service_manager/tests/factory_tests.rs b/_archive/service_manager/tests/factory_tests.rs deleted file mode 100644 index c1c7403..0000000 --- a/_archive/service_manager/tests/factory_tests.rs +++ /dev/null @@ -1,243 +0,0 @@ -use sal_service_manager::{create_service_manager, ServiceConfig, ServiceManager}; -use std::collections::HashMap; - -#[test] -fn test_create_service_manager() { - // Test that the factory function creates the appropriate service manager for the platform - let manager = create_service_manager().expect("Failed to create service manager"); - - // Test basic functionality - should be able to call methods without panicking - let list_result = manager.list(); - - // The result might be an error (if no service system is available), but it shouldn't panic - match list_result { - Ok(services) => { - println!( - "āœ“ Service manager created successfully, found {} services", - services.len() - ); - } - Err(e) => { - println!("āœ“ Service manager created, but got expected error: {}", e); - // This is expected on systems without the appropriate service manager - } - } -} - -#[test] -fn test_service_config_creation() { - // Test creating various service configurations - let basic_config = ServiceConfig { - name: "test-service".to_string(), - binary_path: "/usr/bin/echo".to_string(), - args: vec!["hello".to_string(), "world".to_string()], - working_directory: None, - environment: HashMap::new(), - auto_restart: false, - }; - - assert_eq!(basic_config.name, "test-service"); - assert_eq!(basic_config.binary_path, "/usr/bin/echo"); - assert_eq!(basic_config.args.len(), 2); - assert_eq!(basic_config.args[0], "hello"); - assert_eq!(basic_config.args[1], "world"); - assert!(basic_config.working_directory.is_none()); - assert!(basic_config.environment.is_empty()); - assert!(!basic_config.auto_restart); - - println!("āœ“ Basic service config created successfully"); - - // Test config with environment variables - let mut env = HashMap::new(); - env.insert("PATH".to_string(), "/usr/bin:/bin".to_string()); - env.insert("HOME".to_string(), "/tmp".to_string()); - - let env_config = ServiceConfig { - name: "env-service".to_string(), - binary_path: "/usr/bin/env".to_string(), - args: vec![], - working_directory: Some("/tmp".to_string()), - environment: env.clone(), - auto_restart: true, - }; - - assert_eq!(env_config.name, "env-service"); - assert_eq!(env_config.binary_path, "/usr/bin/env"); - assert!(env_config.args.is_empty()); - assert_eq!(env_config.working_directory, Some("/tmp".to_string())); - assert_eq!(env_config.environment.len(), 2); - assert_eq!( - env_config.environment.get("PATH"), - Some(&"/usr/bin:/bin".to_string()) - ); - assert_eq!( - env_config.environment.get("HOME"), - Some(&"/tmp".to_string()) - ); - assert!(env_config.auto_restart); - - println!("āœ“ Environment service config created successfully"); -} - -#[test] -fn test_service_config_clone() { - // Test that ServiceConfig can be cloned - let original_config = ServiceConfig { - name: "original".to_string(), - binary_path: "/bin/sh".to_string(), - args: vec!["-c".to_string(), "echo test".to_string()], - working_directory: Some("/home".to_string()), - environment: { - let mut env = HashMap::new(); - env.insert("TEST".to_string(), "value".to_string()); - env - }, - auto_restart: true, - }; - - let cloned_config = original_config.clone(); - - assert_eq!(original_config.name, cloned_config.name); - assert_eq!(original_config.binary_path, cloned_config.binary_path); - assert_eq!(original_config.args, cloned_config.args); - assert_eq!( - original_config.working_directory, - cloned_config.working_directory - ); - assert_eq!(original_config.environment, cloned_config.environment); - assert_eq!(original_config.auto_restart, cloned_config.auto_restart); - - println!("āœ“ Service config cloning works correctly"); -} - -#[cfg(target_os = "macos")] -#[test] -fn test_macos_service_manager() { - use sal_service_manager::LaunchctlServiceManager; - - // Test creating macOS-specific service manager - let manager = LaunchctlServiceManager::new(); - - // Test basic functionality - let list_result = manager.list(); - match list_result { - Ok(services) => { - println!( - "āœ“ macOS LaunchctlServiceManager created successfully, found {} services", - services.len() - ); - } - Err(e) => { - println!( - "āœ“ macOS LaunchctlServiceManager created, but got expected error: {}", - e - ); - } - } -} - -#[cfg(target_os = "linux")] -#[test] -fn test_linux_service_manager() { - use sal_service_manager::SystemdServiceManager; - - // Test creating Linux-specific service manager - let manager = SystemdServiceManager::new(); - - // Test basic functionality - let list_result = manager.list(); - match list_result { - Ok(services) => { - println!( - "āœ“ Linux SystemdServiceManager created successfully, found {} services", - services.len() - ); - } - Err(e) => { - println!( - "āœ“ Linux SystemdServiceManager created, but got expected error: {}", - e - ); - } - } -} - -#[test] -fn test_service_status_debug() { - use sal_service_manager::ServiceStatus; - - // Test that ServiceStatus can be debugged and cloned - let statuses = vec![ - ServiceStatus::Running, - ServiceStatus::Stopped, - ServiceStatus::Failed, - ServiceStatus::Unknown, - ]; - - for status in &statuses { - let cloned = status.clone(); - let debug_str = format!("{:?}", status); - - assert!(!debug_str.is_empty()); - assert_eq!(status, &cloned); - - println!( - "āœ“ ServiceStatus::{:?} debug and clone work correctly", - status - ); - } -} - -#[test] -fn test_service_manager_error_debug() { - use sal_service_manager::ServiceManagerError; - - // Test that ServiceManagerError can be debugged and displayed - let errors = vec![ - ServiceManagerError::ServiceNotFound("test".to_string()), - ServiceManagerError::ServiceAlreadyExists("test".to_string()), - ServiceManagerError::StartFailed("test".to_string(), "reason".to_string()), - ServiceManagerError::StopFailed("test".to_string(), "reason".to_string()), - ServiceManagerError::RestartFailed("test".to_string(), "reason".to_string()), - ServiceManagerError::LogsFailed("test".to_string(), "reason".to_string()), - ServiceManagerError::Other("generic error".to_string()), - ]; - - for error in &errors { - let debug_str = format!("{:?}", error); - let display_str = format!("{}", error); - - assert!(!debug_str.is_empty()); - assert!(!display_str.is_empty()); - - println!("āœ“ Error debug: {:?}", error); - println!("āœ“ Error display: {}", error); - } -} - -#[test] -fn test_service_manager_trait_object() { - // Test that we can use ServiceManager as a trait object - let manager: Box = - create_service_manager().expect("Failed to create service manager"); - - // Test that we can call methods through the trait object - let list_result = manager.list(); - - match list_result { - Ok(services) => { - println!("āœ“ Trait object works, found {} services", services.len()); - } - Err(e) => { - println!("āœ“ Trait object works, got expected error: {}", e); - } - } - - // Test exists method - let exists_result = manager.exists("non-existent-service"); - match exists_result { - Ok(false) => println!("āœ“ Trait object exists method works correctly"), - Ok(true) => println!("⚠ Unexpectedly found non-existent service"), - Err(_) => println!("āœ“ Trait object exists method works (with error)"), - } -} diff --git a/_archive/service_manager/tests/rhai/service_lifecycle.rhai b/_archive/service_manager/tests/rhai/service_lifecycle.rhai deleted file mode 100644 index b5c244e..0000000 --- a/_archive/service_manager/tests/rhai/service_lifecycle.rhai +++ /dev/null @@ -1,177 +0,0 @@ -// Service lifecycle management test script -// This script tests REAL complete service lifecycle scenarios - -print("=== Service Lifecycle Management Test ==="); - -// Create service manager -let manager = create_service_manager(); -print("āœ“ Service manager created"); - -// Test configuration - real services for testing -let test_services = [ - #{ - name: "lifecycle-test-1", - binary_path: "/bin/echo", - args: ["Lifecycle test 1"], - working_directory: "/tmp", - environment: #{}, - auto_restart: false - }, - #{ - name: "lifecycle-test-2", - binary_path: "/bin/echo", - args: ["Lifecycle test 2"], - working_directory: "/tmp", - environment: #{ "TEST_VAR": "test_value" }, - auto_restart: false - } -]; - -let total_tests = 0; -let passed_tests = 0; - -// Test 1: Service Creation and Start -print("\n1. Testing service creation and start..."); -for service_config in test_services { - print(`\nStarting service: ${service_config.name}`); - try { - start(manager, service_config); - print(` āœ“ Service ${service_config.name} started successfully`); - passed_tests += 1; - } catch(e) { - print(` āœ— Service ${service_config.name} start failed: ${e}`); - } - total_tests += 1; -} - -// Test 2: Service Existence Check -print("\n2. Testing service existence checks..."); -for service_config in test_services { - print(`\nChecking existence of: ${service_config.name}`); - try { - let service_exists = exists(manager, service_config.name); - if service_exists { - print(` āœ“ Service ${service_config.name} exists: ${service_exists}`); - passed_tests += 1; - } else { - print(` āœ— Service ${service_config.name} doesn't exist after start`); - } - } catch(e) { - print(` āœ— Existence check failed for ${service_config.name}: ${e}`); - } - total_tests += 1; -} - -// Test 3: Status Check -print("\n3. Testing status checks..."); -for service_config in test_services { - print(`\nChecking status of: ${service_config.name}`); - try { - let service_status = status(manager, service_config.name); - print(` āœ“ Service ${service_config.name} status: ${service_status}`); - passed_tests += 1; - } catch(e) { - print(` āœ— Status check failed for ${service_config.name}: ${e}`); - } - total_tests += 1; -} - -// Test 4: Service List Check -print("\n4. Testing service list..."); -try { - let services = list(manager); - print(` āœ“ Service list retrieved (${services.len()} services)`); - - // Check if our test services are in the list - for service_config in test_services { - let found = false; - for service in services { - if service.contains(service_config.name) { - found = true; - print(` āœ“ Found ${service_config.name} in list`); - break; - } - } - if !found { - print(` ⚠ ${service_config.name} not found in service list`); - } - } - passed_tests += 1; -} catch(e) { - print(` āœ— Service list failed: ${e}`); -} -total_tests += 1; - -// Test 5: Service Stop -print("\n5. Testing service stop..."); -for service_config in test_services { - print(`\nStopping service: ${service_config.name}`); - try { - stop(manager, service_config.name); - print(` āœ“ Service ${service_config.name} stopped successfully`); - passed_tests += 1; - } catch(e) { - print(` āœ— Service ${service_config.name} stop failed: ${e}`); - } - total_tests += 1; -} - -// Test 6: Service Removal -print("\n6. Testing service removal..."); -for service_config in test_services { - print(`\nRemoving service: ${service_config.name}`); - try { - remove(manager, service_config.name); - print(` āœ“ Service ${service_config.name} removed successfully`); - passed_tests += 1; - } catch(e) { - print(` āœ— Service ${service_config.name} removal failed: ${e}`); - } - total_tests += 1; -} - -// Test 7: Cleanup Verification -print("\n7. Testing cleanup verification..."); -for service_config in test_services { - print(`\nVerifying removal of: ${service_config.name}`); - try { - let exists_after_remove = exists(manager, service_config.name); - if !exists_after_remove { - print(` āœ“ Service ${service_config.name} correctly doesn't exist after removal`); - passed_tests += 1; - } else { - print(` āœ— Service ${service_config.name} still exists after removal`); - } - } catch(e) { - print(` āœ— Cleanup verification failed for ${service_config.name}: ${e}`); - } - total_tests += 1; -} - -// Test Summary -print("\n=== Lifecycle Test Summary ==="); -print(`Services tested: ${test_services.len()}`); -print(`Total operations: ${total_tests}`); -print(`Successful operations: ${passed_tests}`); -print(`Failed operations: ${total_tests - passed_tests}`); -print(`Success rate: ${(passed_tests * 100) / total_tests}%`); - -if passed_tests == total_tests { - print("\nšŸŽ‰ All lifecycle tests passed!"); - print("Service manager is working correctly across all scenarios."); -} else { - print(`\n⚠ ${total_tests - passed_tests} test(s) failed`); - print("Some service manager operations need attention."); -} - -print("\n=== Service Lifecycle Test Complete ==="); - -// Return test results -#{ - summary: #{ - total_tests: total_tests, - passed_tests: passed_tests, - success_rate: (passed_tests * 100) / total_tests, - services_tested: test_services.len() - } -} diff --git a/_archive/service_manager/tests/rhai/service_manager_basic.rhai b/_archive/service_manager/tests/rhai/service_manager_basic.rhai deleted file mode 100644 index d44bec6..0000000 --- a/_archive/service_manager/tests/rhai/service_manager_basic.rhai +++ /dev/null @@ -1,218 +0,0 @@ -// Basic service manager functionality test script -// This script tests the REAL service manager through Rhai integration - -print("=== Service Manager Basic Functionality Test ==="); - -// Test configuration -let test_service_name = "rhai-test-service"; -let test_binary = "/bin/echo"; -let test_args = ["Hello from Rhai service manager test"]; - -print(`Testing service: ${test_service_name}`); -print(`Binary: ${test_binary}`); -print(`Args: ${test_args}`); - -// Test results tracking -let test_results = #{ - creation: "NOT_RUN", - exists_before: "NOT_RUN", - start: "NOT_RUN", - exists_after: "NOT_RUN", - status: "NOT_RUN", - list: "NOT_RUN", - stop: "NOT_RUN", - remove: "NOT_RUN", - cleanup: "NOT_RUN" -}; - -let passed_tests = 0; -let total_tests = 0; - -// Test 1: Service Manager Creation -print("\n1. Testing service manager creation..."); -try { - let manager = create_service_manager(); - print("āœ“ Service manager created successfully"); - test_results["creation"] = "PASS"; - passed_tests += 1; - total_tests += 1; -} catch(e) { - print(`āœ— Service manager creation failed: ${e}`); - test_results["creation"] = "FAIL"; - total_tests += 1; - // Return early if we can't create the manager - return test_results; -} - -// Create the service manager for all subsequent tests -let manager = create_service_manager(); - -// Test 2: Check if service exists before creation -print("\n2. Testing service existence check (before creation)..."); -try { - let exists_before = exists(manager, test_service_name); - print(`āœ“ Service existence check: ${exists_before}`); - - if !exists_before { - print("āœ“ Service correctly doesn't exist before creation"); - test_results["exists_before"] = "PASS"; - passed_tests += 1; - } else { - print("⚠ Service unexpectedly exists before creation"); - test_results["exists_before"] = "WARN"; - } - total_tests += 1; -} catch(e) { - print(`āœ— Service existence check failed: ${e}`); - test_results["exists_before"] = "FAIL"; - total_tests += 1; -} - -// Test 3: Start the service -print("\n3. Testing service start..."); -try { - // Create a service configuration object - let service_config = #{ - name: test_service_name, - binary_path: test_binary, - args: test_args, - working_directory: "/tmp", - environment: #{}, - auto_restart: false - }; - - start(manager, service_config); - print("āœ“ Service started successfully"); - test_results["start"] = "PASS"; - passed_tests += 1; - total_tests += 1; -} catch(e) { - print(`āœ— Service start failed: ${e}`); - test_results["start"] = "FAIL"; - total_tests += 1; -} - -// Test 4: Check if service exists after creation -print("\n4. Testing service existence check (after creation)..."); -try { - let exists_after = exists(manager, test_service_name); - print(`āœ“ Service existence check: ${exists_after}`); - - if exists_after { - print("āœ“ Service correctly exists after creation"); - test_results["exists_after"] = "PASS"; - passed_tests += 1; - } else { - print("āœ— Service doesn't exist after creation"); - test_results["exists_after"] = "FAIL"; - } - total_tests += 1; -} catch(e) { - print(`āœ— Service existence check failed: ${e}`); - test_results["exists_after"] = "FAIL"; - total_tests += 1; -} - -// Test 5: Check service status -print("\n5. Testing service status..."); -try { - let service_status = status(manager, test_service_name); - print(`āœ“ Service status: ${service_status}`); - test_results["status"] = "PASS"; - passed_tests += 1; - total_tests += 1; -} catch(e) { - print(`āœ— Service status check failed: ${e}`); - test_results["status"] = "FAIL"; - total_tests += 1; -} - -// Test 6: List services -print("\n6. Testing service list..."); -try { - let services = list(manager); - print("āœ“ Service list retrieved"); - - // Skip service search due to Rhai type constraints with Vec iteration - print(" āš ļø Skipping service search due to Rhai type constraints"); - - test_results["list"] = "PASS"; - passed_tests += 1; - total_tests += 1; -} catch(e) { - print(`āœ— Service list failed: ${e}`); - test_results["list"] = "FAIL"; - total_tests += 1; -} - -// Test 7: Stop the service -print("\n7. Testing service stop..."); -try { - stop(manager, test_service_name); - print(`āœ“ Service stopped: ${test_service_name}`); - test_results["stop"] = "PASS"; - passed_tests += 1; - total_tests += 1; -} catch(e) { - print(`āœ— Service stop failed: ${e}`); - test_results["stop"] = "FAIL"; - total_tests += 1; -} - -// Test 8: Remove the service -print("\n8. Testing service remove..."); -try { - remove(manager, test_service_name); - print(`āœ“ Service removed: ${test_service_name}`); - test_results["remove"] = "PASS"; - passed_tests += 1; - total_tests += 1; -} catch(e) { - print(`āœ— Service remove failed: ${e}`); - test_results["remove"] = "FAIL"; - total_tests += 1; -} - -// Test 9: Verify cleanup -print("\n9. Testing cleanup verification..."); -try { - let exists_after_remove = exists(manager, test_service_name); - if !exists_after_remove { - print("āœ“ Service correctly doesn't exist after removal"); - test_results["cleanup"] = "PASS"; - passed_tests += 1; - } else { - print("āœ— Service still exists after removal"); - test_results["cleanup"] = "FAIL"; - } - total_tests += 1; -} catch(e) { - print(`āœ— Cleanup verification failed: ${e}`); - test_results["cleanup"] = "FAIL"; - total_tests += 1; -} - -// Test Summary -print("\n=== Test Summary ==="); -print(`Total tests: ${total_tests}`); -print(`Passed: ${passed_tests}`); -print(`Failed: ${total_tests - passed_tests}`); -print(`Success rate: ${(passed_tests * 100) / total_tests}%`); - -print("\nDetailed Results:"); -for test_name in test_results.keys() { - let result = test_results[test_name]; - let status_icon = if result == "PASS" { "āœ“" } else if result == "FAIL" { "āœ—" } else { "⚠" }; - print(` ${status_icon} ${test_name}: ${result}`); -} - -if passed_tests == total_tests { - print("\nšŸŽ‰ All tests passed!"); -} else { - print(`\n⚠ ${total_tests - passed_tests} test(s) failed`); -} - -print("\n=== Service Manager Basic Test Complete ==="); - -// Return test results for potential use by calling code -test_results diff --git a/_archive/service_manager/tests/rhai_integration_tests.rs b/_archive/service_manager/tests/rhai_integration_tests.rs deleted file mode 100644 index 5b12324..0000000 --- a/_archive/service_manager/tests/rhai_integration_tests.rs +++ /dev/null @@ -1,252 +0,0 @@ -use rhai::{Engine, EvalAltResult}; -use std::fs; -use std::path::Path; - -/// Helper function to create a Rhai engine for service manager testing -fn create_service_manager_engine() -> Result> { - #[cfg(feature = "rhai")] - { - let mut engine = Engine::new(); - // Register the service manager module for real testing - sal_service_manager::rhai::register_service_manager_module(&mut engine)?; - Ok(engine) - } - #[cfg(not(feature = "rhai"))] - { - Ok(Engine::new()) - } -} - -/// Helper function to run a Rhai script file -fn run_rhai_script(script_path: &str) -> Result> { - let engine = create_service_manager_engine()?; - - // Read the script file - let script_content = fs::read_to_string(script_path) - .map_err(|e| format!("Failed to read script file {}: {}", script_path, e))?; - - // Execute the script - engine.eval::(&script_content) -} - -#[test] -fn test_rhai_service_manager_basic() { - let script_path = "tests/rhai/service_manager_basic.rhai"; - - if !Path::new(script_path).exists() { - println!("⚠ Skipping test: Rhai script not found at {}", script_path); - return; - } - - println!("Running Rhai service manager basic test..."); - - match run_rhai_script(script_path) { - Ok(result) => { - println!("āœ“ Rhai basic test completed successfully"); - - // Try to extract test results if the script returns them - if let Some(map) = result.try_cast::() { - println!("Test results received from Rhai script:"); - for (key, value) in map.iter() { - println!(" {}: {:?}", key, value); - } - - // Check if all tests passed - let all_passed = map.values().all(|v| { - if let Some(s) = v.clone().try_cast::() { - s == "PASS" - } else { - false - } - }); - - if all_passed { - println!("āœ“ All Rhai tests reported as PASS"); - } else { - println!("⚠ Some Rhai tests did not pass"); - } - } - } - Err(e) => { - println!("āœ— Rhai basic test failed: {}", e); - assert!(false, "Rhai script execution failed: {}", e); - } - } -} - -#[test] -fn test_rhai_service_lifecycle() { - let script_path = "tests/rhai/service_lifecycle.rhai"; - - if !Path::new(script_path).exists() { - println!("⚠ Skipping test: Rhai script not found at {}", script_path); - return; - } - - println!("Running Rhai service lifecycle test..."); - - match run_rhai_script(script_path) { - Ok(result) => { - println!("āœ“ Rhai lifecycle test completed successfully"); - - // Try to extract test results if the script returns them - if let Some(map) = result.try_cast::() { - println!("Lifecycle test results received from Rhai script:"); - - // Extract summary if available - if let Some(summary) = map.get("summary") { - if let Some(summary_map) = summary.clone().try_cast::() { - println!("Summary:"); - for (key, value) in summary_map.iter() { - println!(" {}: {:?}", key, value); - } - } - } - - // Extract performance metrics if available - if let Some(performance) = map.get("performance") { - if let Some(perf_map) = performance.clone().try_cast::() { - println!("Performance:"); - for (key, value) in perf_map.iter() { - println!(" {}: {:?}", key, value); - } - } - } - } - } - Err(e) => { - println!("āœ— Rhai lifecycle test failed: {}", e); - assert!(false, "Rhai script execution failed: {}", e); - } - } -} - -#[test] -fn test_rhai_engine_functionality() { - println!("Testing basic Rhai engine functionality..."); - - let engine = create_service_manager_engine().expect("Failed to create Rhai engine"); - - // Test basic Rhai functionality - let test_script = r#" - let test_results = #{ - basic_math: 2 + 2 == 4, - string_ops: "hello".len() == 5, - array_ops: [1, 2, 3].len() == 3, - map_ops: #{ a: 1, b: 2 }.len() == 2 - }; - - let all_passed = true; - for result in test_results.values() { - if !result { - all_passed = false; - break; - } - } - - #{ - results: test_results, - all_passed: all_passed - } - "#; - - match engine.eval::(test_script) { - Ok(result) => { - if let Some(map) = result.try_cast::() { - if let Some(all_passed) = map.get("all_passed") { - if let Some(passed) = all_passed.clone().try_cast::() { - if passed { - println!("āœ“ All basic Rhai functionality tests passed"); - } else { - println!("āœ— Some basic Rhai functionality tests failed"); - assert!(false, "Basic Rhai tests failed"); - } - } - } - - if let Some(results) = map.get("results") { - if let Some(results_map) = results.clone().try_cast::() { - println!("Detailed results:"); - for (test_name, result) in results_map.iter() { - let status = if let Some(passed) = result.clone().try_cast::() { - if passed { - "āœ“" - } else { - "āœ—" - } - } else { - "?" - }; - println!(" {} {}: {:?}", status, test_name, result); - } - } - } - } - } - Err(e) => { - println!("āœ— Basic Rhai functionality test failed: {}", e); - assert!(false, "Basic Rhai test failed: {}", e); - } - } -} - -#[test] -fn test_rhai_script_error_handling() { - println!("Testing Rhai error handling..."); - - let engine = create_service_manager_engine().expect("Failed to create Rhai engine"); - - // Test script with intentional error - let error_script = r#" - let result = "test"; - result.non_existent_method(); // This should cause an error - "#; - - match engine.eval::(error_script) { - Ok(_) => { - println!("⚠ Expected error but script succeeded"); - assert!( - false, - "Error handling test failed - expected error but got success" - ); - } - Err(e) => { - println!("āœ“ Error correctly caught: {}", e); - // Verify it's the expected type of error - assert!(e.to_string().contains("method") || e.to_string().contains("function")); - } - } -} - -#[test] -fn test_rhai_script_files_exist() { - println!("Checking that Rhai test scripts exist..."); - - let script_files = [ - "tests/rhai/service_manager_basic.rhai", - "tests/rhai/service_lifecycle.rhai", - ]; - - for script_file in &script_files { - if Path::new(script_file).exists() { - println!("āœ“ Found script: {}", script_file); - - // Verify the file is readable and not empty - match fs::read_to_string(script_file) { - Ok(content) => { - if content.trim().is_empty() { - assert!(false, "Script file {} is empty", script_file); - } - println!(" Content length: {} characters", content.len()); - } - Err(e) => { - assert!(false, "Failed to read script file {}: {}", script_file, e); - } - } - } else { - assert!(false, "Required script file not found: {}", script_file); - } - } - - println!("āœ“ All required Rhai script files exist and are readable"); -} diff --git a/_archive/service_manager/tests/zinit_integration_tests.rs b/_archive/service_manager/tests/zinit_integration_tests.rs deleted file mode 100644 index f45fe13..0000000 --- a/_archive/service_manager/tests/zinit_integration_tests.rs +++ /dev/null @@ -1,317 +0,0 @@ -use sal_service_manager::{ - ServiceConfig, ServiceManager, ServiceManagerError, ServiceStatus, ZinitServiceManager, -}; -use std::collections::HashMap; -use std::time::Duration; -use tokio::time::sleep; - -/// Helper function to find an available Zinit socket path -async fn get_available_socket_path() -> Option { - let socket_paths = [ - "/var/run/zinit.sock", - "/tmp/zinit.sock", - "/run/zinit.sock", - "./zinit.sock", - ]; - - for path in &socket_paths { - // Try to create a ZinitServiceManager to test connectivity - if let Ok(manager) = ZinitServiceManager::new(path) { - // Test if we can list services (basic connectivity test) - if manager.list().is_ok() { - println!("āœ“ Found working Zinit socket at: {}", path); - return Some(path.to_string()); - } - } - } - - None -} - -/// Helper function to clean up test services -async fn cleanup_test_service(manager: &dyn ServiceManager, service_name: &str) { - let _ = manager.stop(service_name); - let _ = manager.remove(service_name); -} - -#[tokio::test] -async fn test_zinit_service_manager_creation() { - if let Some(socket_path) = get_available_socket_path().await { - let manager = ZinitServiceManager::new(&socket_path); - assert!( - manager.is_ok(), - "Should be able to create ZinitServiceManager" - ); - - let manager = manager.unwrap(); - - // Test basic connectivity by listing services - let list_result = manager.list(); - assert!(list_result.is_ok(), "Should be able to list services"); - - println!("āœ“ ZinitServiceManager created successfully"); - } else { - println!("⚠ Skipping test_zinit_service_manager_creation: No Zinit socket available"); - } -} - -#[tokio::test] -async fn test_service_lifecycle() { - if let Some(socket_path) = get_available_socket_path().await { - let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager"); - let service_name = "test-lifecycle-service"; - - // Clean up any existing service first - cleanup_test_service(&manager, service_name).await; - - let config = ServiceConfig { - name: service_name.to_string(), - binary_path: "echo".to_string(), - args: vec!["Hello from lifecycle test".to_string()], - working_directory: Some("/tmp".to_string()), - environment: HashMap::new(), - auto_restart: false, - }; - - // Test service creation and start - println!("Testing service creation and start..."); - let start_result = manager.start(&config); - match start_result { - Ok(_) => { - println!("āœ“ Service started successfully"); - - // Wait a bit for the service to run - sleep(Duration::from_millis(500)).await; - - // Test service exists - let exists = manager.exists(service_name); - assert!(exists.is_ok(), "Should be able to check if service exists"); - - if let Ok(true) = exists { - println!("āœ“ Service exists check passed"); - - // Test service status - let status_result = manager.status(service_name); - match status_result { - Ok(status) => { - println!("āœ“ Service status: {:?}", status); - assert!( - matches!(status, ServiceStatus::Running | ServiceStatus::Stopped), - "Service should be running or stopped (for oneshot)" - ); - } - Err(e) => println!("⚠ Status check failed: {}", e), - } - - // Test service logs - let logs_result = manager.logs(service_name, None); - match logs_result { - Ok(logs) => { - println!("āœ“ Retrieved logs: {}", logs.len()); - // For echo command, we should have some output - assert!( - !logs.is_empty() || logs.contains("Hello"), - "Should have log output" - ); - } - Err(e) => println!("⚠ Logs retrieval failed: {}", e), - } - - // Test service list - let list_result = manager.list(); - match list_result { - Ok(services) => { - println!("āœ“ Listed {} services", services.len()); - assert!( - services.contains(&service_name.to_string()), - "Service should appear in list" - ); - } - Err(e) => println!("⚠ List services failed: {}", e), - } - } - - // Test service stop - println!("Testing service stop..."); - let stop_result = manager.stop(service_name); - match stop_result { - Ok(_) => println!("āœ“ Service stopped successfully"), - Err(e) => println!("⚠ Stop failed: {}", e), - } - - // Test service removal - println!("Testing service removal..."); - let remove_result = manager.remove(service_name); - match remove_result { - Ok(_) => println!("āœ“ Service removed successfully"), - Err(e) => println!("⚠ Remove failed: {}", e), - } - } - Err(e) => { - println!("⚠ Service creation/start failed: {}", e); - // This might be expected if zinit doesn't allow service creation - } - } - - // Final cleanup - cleanup_test_service(&manager, service_name).await; - } else { - println!("⚠ Skipping test_service_lifecycle: No Zinit socket available"); - } -} - -#[tokio::test] -async fn test_service_start_and_confirm() { - if let Some(socket_path) = get_available_socket_path().await { - let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager"); - let service_name = "test-start-confirm-service"; - - // Clean up any existing service first - cleanup_test_service(&manager, service_name).await; - - let config = ServiceConfig { - name: service_name.to_string(), - binary_path: "sleep".to_string(), - args: vec!["5".to_string()], // Sleep for 5 seconds - working_directory: Some("/tmp".to_string()), - environment: HashMap::new(), - auto_restart: false, - }; - - // Test start_and_confirm with timeout - println!("Testing start_and_confirm with timeout..."); - let start_result = manager.start_and_confirm(&config, 10); - match start_result { - Ok(_) => { - println!("āœ“ Service started and confirmed successfully"); - - // Verify it's actually running - let status = manager.status(service_name); - match status { - Ok(ServiceStatus::Running) => println!("āœ“ Service confirmed running"), - Ok(other_status) => { - println!("⚠ Service in unexpected state: {:?}", other_status) - } - Err(e) => println!("⚠ Status check failed: {}", e), - } - } - Err(e) => { - println!("⚠ start_and_confirm failed: {}", e); - } - } - - // Test start_existing_and_confirm - println!("Testing start_existing_and_confirm..."); - let start_existing_result = manager.start_existing_and_confirm(service_name, 5); - match start_existing_result { - Ok(_) => println!("āœ“ start_existing_and_confirm succeeded"), - Err(e) => println!("⚠ start_existing_and_confirm failed: {}", e), - } - - // Cleanup - cleanup_test_service(&manager, service_name).await; - } else { - println!("⚠ Skipping test_service_start_and_confirm: No Zinit socket available"); - } -} - -#[tokio::test] -async fn test_service_restart() { - if let Some(socket_path) = get_available_socket_path().await { - let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager"); - let service_name = "test-restart-service"; - - // Clean up any existing service first - cleanup_test_service(&manager, service_name).await; - - let config = ServiceConfig { - name: service_name.to_string(), - binary_path: "echo".to_string(), - args: vec!["Restart test".to_string()], - working_directory: Some("/tmp".to_string()), - environment: HashMap::new(), - auto_restart: true, // Enable auto-restart for this test - }; - - // Start the service first - let start_result = manager.start(&config); - if start_result.is_ok() { - // Wait for service to be established - sleep(Duration::from_millis(1000)).await; - - // Test restart - println!("Testing service restart..."); - let restart_result = manager.restart(service_name); - match restart_result { - Ok(_) => { - println!("āœ“ Service restarted successfully"); - - // Wait and check status - sleep(Duration::from_millis(500)).await; - - let status_result = manager.status(service_name); - match status_result { - Ok(status) => { - println!("āœ“ Service state after restart: {:?}", status); - } - Err(e) => println!("⚠ Status check after restart failed: {}", e), - } - } - Err(e) => { - println!("⚠ Restart failed: {}", e); - } - } - } - - // Cleanup - cleanup_test_service(&manager, service_name).await; - } else { - println!("⚠ Skipping test_service_restart: No Zinit socket available"); - } -} - -#[tokio::test] -async fn test_error_handling() { - if let Some(socket_path) = get_available_socket_path().await { - let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager"); - - // Test operations on non-existent service - let non_existent_service = "non-existent-service-12345"; - - // Test status of non-existent service - let status_result = manager.status(non_existent_service); - match status_result { - Err(ServiceManagerError::ServiceNotFound(_)) => { - println!("āœ“ Correctly returned ServiceNotFound for non-existent service"); - } - Err(other_error) => { - println!( - "⚠ Got different error for non-existent service: {}", - other_error - ); - } - Ok(_) => { - println!("⚠ Unexpectedly found non-existent service"); - } - } - - // Test exists for non-existent service - let exists_result = manager.exists(non_existent_service); - match exists_result { - Ok(false) => println!("āœ“ Correctly reported non-existent service as not existing"), - Ok(true) => println!("⚠ Incorrectly reported non-existent service as existing"), - Err(e) => println!("⚠ Error checking existence: {}", e), - } - - // Test stop of non-existent service - let stop_result = manager.stop(non_existent_service); - match stop_result { - Err(_) => println!("āœ“ Correctly failed to stop non-existent service"), - Ok(_) => println!("⚠ Unexpectedly succeeded in stopping non-existent service"), - } - - println!("āœ“ Error handling tests completed"); - } else { - println!("⚠ Skipping test_error_handling: No Zinit socket available"); - } -}