feat: Add service manager support
- Add a new service manager crate for dynamic service management - Integrate service manager with Rhai for scripting - Provide examples for circle worker management and basic usage - Add comprehensive tests for service lifecycle and error handling - Implement cross-platform support for macOS and Linux (zinit/systemd)
This commit is contained in:
116
examples/service_manager/README.md
Normal file
116
examples/service_manager/README.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# Service Manager Examples
|
||||
|
||||
This directory contains examples demonstrating the SAL service manager functionality for dynamically launching and managing services across platforms.
|
||||
|
||||
## Overview
|
||||
|
||||
The service manager provides a unified interface for managing system services:
|
||||
- **macOS**: Uses `launchctl` for service management
|
||||
- **Linux**: Uses `zinit` for service management (systemd also available as alternative)
|
||||
|
||||
## Examples
|
||||
|
||||
### 1. Circle Worker Manager (`circle_worker_manager.rhai`)
|
||||
|
||||
**Primary Use Case**: Demonstrates dynamic circle worker management for freezone residents.
|
||||
|
||||
This example shows:
|
||||
- Creating service configurations for circle workers
|
||||
- Complete service lifecycle management (start, stop, restart, remove)
|
||||
- Status monitoring and log retrieval
|
||||
- Error handling and cleanup
|
||||
|
||||
```bash
|
||||
# Run the circle worker management example
|
||||
herodo examples/service_manager/circle_worker_manager.rhai
|
||||
```
|
||||
|
||||
### 2. Basic Usage (`basic_usage.rhai`)
|
||||
|
||||
**Learning Example**: Simple demonstration of the core service manager API.
|
||||
|
||||
This example covers:
|
||||
- Creating and configuring services
|
||||
- Starting and stopping services
|
||||
- Checking service status
|
||||
- Listing managed services
|
||||
- Retrieving service logs
|
||||
|
||||
```bash
|
||||
# Run the basic usage example
|
||||
herodo examples/service_manager/basic_usage.rhai
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Linux (zinit)
|
||||
|
||||
Make sure zinit is installed and running:
|
||||
|
||||
```bash
|
||||
# Start zinit with default socket
|
||||
zinit -s /tmp/zinit.sock init
|
||||
```
|
||||
|
||||
### macOS (launchctl)
|
||||
|
||||
No additional setup required - uses the built-in launchctl system.
|
||||
|
||||
## Service Manager API
|
||||
|
||||
The service manager provides these key functions:
|
||||
|
||||
- `create_service_manager()` - Create platform-appropriate service manager
|
||||
- `start(manager, config)` - Start a new service
|
||||
- `stop(manager, service_name)` - Stop a running service
|
||||
- `restart(manager, service_name)` - Restart a service
|
||||
- `status(manager, service_name)` - Get service status
|
||||
- `logs(manager, service_name, lines)` - Retrieve service logs
|
||||
- `list(manager)` - List all managed services
|
||||
- `remove(manager, service_name)` - Remove a service
|
||||
- `exists(manager, service_name)` - Check if service exists
|
||||
- `start_and_confirm(manager, config, timeout)` - Start with confirmation
|
||||
|
||||
## Service Configuration
|
||||
|
||||
Services are configured using a map with these fields:
|
||||
|
||||
```rhai
|
||||
let config = #{
|
||||
name: "my-service", // Service name
|
||||
binary_path: "/usr/bin/my-app", // Executable path
|
||||
args: ["--config", "/etc/my-app.conf"], // Command arguments
|
||||
working_directory: "/var/lib/my-app", // Working directory (optional)
|
||||
environment: #{ // Environment variables
|
||||
"VAR1": "value1",
|
||||
"VAR2": "value2"
|
||||
},
|
||||
auto_restart: true // Auto-restart on failure
|
||||
};
|
||||
```
|
||||
|
||||
## Real-World Usage
|
||||
|
||||
The circle worker example demonstrates the exact use case requested by the team:
|
||||
|
||||
> "We want to be able to launch circle workers dynamically. For instance when someone registers to the freezone, we need to be able to launch a circle worker for the new resident."
|
||||
|
||||
The service manager enables:
|
||||
1. **Dynamic service creation** - Create services on-demand for new residents
|
||||
2. **Cross-platform support** - Works on both macOS and Linux
|
||||
3. **Lifecycle management** - Full control over service lifecycle
|
||||
4. **Monitoring and logging** - Track service status and retrieve logs
|
||||
5. **Cleanup** - Proper service removal when no longer needed
|
||||
|
||||
## Error Handling
|
||||
|
||||
All service manager functions can throw errors. Use try-catch blocks for robust error handling:
|
||||
|
||||
```rhai
|
||||
try {
|
||||
sm::start(manager, config);
|
||||
print("✅ Service started successfully");
|
||||
} catch (error) {
|
||||
print(`❌ Failed to start service: ${error}`);
|
||||
}
|
||||
```
|
81
examples/service_manager/basic_usage.rhai
Normal file
81
examples/service_manager/basic_usage.rhai
Normal file
@@ -0,0 +1,81 @@
|
||||
// Basic Service Manager Usage Example
|
||||
//
|
||||
// This example demonstrates the basic API of the service manager.
|
||||
// It works on both macOS (launchctl) and Linux (zinit).
|
||||
//
|
||||
// Prerequisites:
|
||||
//
|
||||
// Linux: Make sure zinit is running:
|
||||
// zinit -s /tmp/zinit.sock init
|
||||
//
|
||||
// macOS: No additional setup required (uses launchctl).
|
||||
//
|
||||
// Usage:
|
||||
// herodo examples/service_manager/basic_usage.rhai
|
||||
|
||||
// Service Manager Basic Usage Example
|
||||
// This example uses the SAL service manager through Rhai integration
|
||||
|
||||
print("🚀 Basic Service Manager Usage Example");
|
||||
print("======================================");
|
||||
|
||||
// Create a service manager for the current platform
|
||||
let manager = create_service_manager();
|
||||
|
||||
print("🍎 Using service manager for current platform");
|
||||
|
||||
// Create a simple service configuration
|
||||
let config = #{
|
||||
name: "example-service",
|
||||
binary_path: "/bin/echo",
|
||||
args: ["Hello from service manager!"],
|
||||
working_directory: "/tmp",
|
||||
environment: #{
|
||||
"EXAMPLE_VAR": "hello_world"
|
||||
},
|
||||
auto_restart: false
|
||||
};
|
||||
|
||||
print("\n📝 Service Configuration:");
|
||||
print(` Name: ${config.name}`);
|
||||
print(` Binary: ${config.binary_path}`);
|
||||
print(` Args: ${config.args}`);
|
||||
|
||||
// Start the service
|
||||
print("\n🚀 Starting service...");
|
||||
start(manager, config);
|
||||
print("✅ Service started successfully");
|
||||
|
||||
// Check service status
|
||||
print("\n📊 Checking service status...");
|
||||
let status = status(manager, "example-service");
|
||||
print(`Status: ${status}`);
|
||||
|
||||
// List all services
|
||||
print("\n📋 Listing all managed services...");
|
||||
let services = list(manager);
|
||||
print(`Found ${services.len()} services:`);
|
||||
for service in services {
|
||||
print(` - ${service}`);
|
||||
}
|
||||
|
||||
// Get service logs
|
||||
print("\n📄 Getting service logs...");
|
||||
let logs = logs(manager, "example-service", 5);
|
||||
if logs.trim() == "" {
|
||||
print("No logs available");
|
||||
} else {
|
||||
print(`Logs:\n${logs}`);
|
||||
}
|
||||
|
||||
// Stop the service
|
||||
print("\n🛑 Stopping service...");
|
||||
stop(manager, "example-service");
|
||||
print("✅ Service stopped");
|
||||
|
||||
// Remove the service
|
||||
print("\n🗑️ Removing service...");
|
||||
remove(manager, "example-service");
|
||||
print("✅ Service removed");
|
||||
|
||||
print("\n🎉 Example completed successfully!");
|
141
examples/service_manager/circle_worker_manager.rhai
Normal file
141
examples/service_manager/circle_worker_manager.rhai
Normal file
@@ -0,0 +1,141 @@
|
||||
// Circle Worker Manager Example
|
||||
//
|
||||
// This example demonstrates how to use the service manager to dynamically launch
|
||||
// circle workers for new freezone residents. This is the primary use case requested
|
||||
// by the team.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// On macOS (uses launchctl):
|
||||
// herodo examples/service_manager/circle_worker_manager.rhai
|
||||
//
|
||||
// On Linux (uses zinit - requires zinit to be running):
|
||||
// First start zinit: zinit -s /tmp/zinit.sock init
|
||||
// herodo examples/service_manager/circle_worker_manager.rhai
|
||||
|
||||
// Circle Worker Manager Example
|
||||
// This example uses the SAL service manager through Rhai integration
|
||||
|
||||
print("🚀 Circle Worker Manager Example");
|
||||
print("=================================");
|
||||
|
||||
// Create the appropriate service manager for the current platform
|
||||
let service_manager = create_service_manager();
|
||||
print("✅ Created service manager for current platform");
|
||||
|
||||
// Simulate a new freezone resident registration
|
||||
let resident_id = "resident_12345";
|
||||
let worker_name = `circle-worker-${resident_id}`;
|
||||
|
||||
print(`\n📝 New freezone resident registered: ${resident_id}`);
|
||||
print(`🔧 Creating circle worker service: ${worker_name}`);
|
||||
|
||||
// Create service configuration for the circle worker
|
||||
let config = #{
|
||||
name: worker_name,
|
||||
binary_path: "/bin/sh",
|
||||
args: [
|
||||
"-c",
|
||||
`echo 'Circle worker for ${resident_id} starting...'; sleep 30; echo 'Circle worker for ${resident_id} completed'`
|
||||
],
|
||||
working_directory: "/tmp",
|
||||
environment: #{
|
||||
"RESIDENT_ID": resident_id,
|
||||
"WORKER_TYPE": "circle",
|
||||
"LOG_LEVEL": "info"
|
||||
},
|
||||
auto_restart: true
|
||||
};
|
||||
|
||||
print("📋 Service configuration created:");
|
||||
print(` Name: ${config.name}`);
|
||||
print(` Binary: ${config.binary_path}`);
|
||||
print(` Args: ${config.args}`);
|
||||
print(` Auto-restart: ${config.auto_restart}`);
|
||||
|
||||
print(`\n🔄 Demonstrating service lifecycle for: ${worker_name}`);
|
||||
|
||||
// 1. Check if service already exists
|
||||
print("\n1️⃣ Checking if service exists...");
|
||||
if exists(service_manager, worker_name) {
|
||||
print("⚠️ Service already exists, removing it first...");
|
||||
remove(service_manager, worker_name);
|
||||
print("🗑️ Existing service removed");
|
||||
} else {
|
||||
print("✅ Service doesn't exist, ready to create");
|
||||
}
|
||||
|
||||
// 2. Start the service
|
||||
print("\n2️⃣ Starting the circle worker service...");
|
||||
start(service_manager, config);
|
||||
print("✅ Service started successfully");
|
||||
|
||||
// 3. Check service status
|
||||
print("\n3️⃣ Checking service status...");
|
||||
let status = status(service_manager, worker_name);
|
||||
print(`📊 Service status: ${status}`);
|
||||
|
||||
// 4. List all services to show our service is there
|
||||
print("\n4️⃣ Listing all managed services...");
|
||||
let services = list(service_manager);
|
||||
print(`📋 Managed services (${services.len()}):`);
|
||||
for service in services {
|
||||
let marker = if service == worker_name { "👉" } else { " " };
|
||||
print(` ${marker} ${service}`);
|
||||
}
|
||||
|
||||
// 5. Wait a moment and check status again
|
||||
print("\n5️⃣ Waiting 3 seconds and checking status again...");
|
||||
sleep(3000); // 3 seconds in milliseconds
|
||||
let status = status(service_manager, worker_name);
|
||||
print(`📊 Service status after 3s: ${status}`);
|
||||
|
||||
// 6. Get service logs
|
||||
print("\n6️⃣ Retrieving service logs...");
|
||||
let logs = logs(service_manager, worker_name, 10);
|
||||
if logs.trim() == "" {
|
||||
print("📄 No logs available yet (this is normal for new services)");
|
||||
} else {
|
||||
print("📄 Recent logs:");
|
||||
let log_lines = logs.split('\n');
|
||||
for i in 0..5 {
|
||||
if i < log_lines.len() {
|
||||
print(` ${log_lines[i]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Demonstrate start_and_confirm with timeout
|
||||
print("\n7️⃣ Testing start_and_confirm (should succeed quickly since already running)...");
|
||||
start_and_confirm(service_manager, config, 5);
|
||||
print("✅ Service confirmed running within timeout");
|
||||
|
||||
// 8. Stop the service
|
||||
print("\n8️⃣ Stopping the service...");
|
||||
stop(service_manager, worker_name);
|
||||
print("🛑 Service stopped");
|
||||
|
||||
// 9. Check status after stopping
|
||||
print("\n9️⃣ Checking status after stop...");
|
||||
let status = status(service_manager, worker_name);
|
||||
print(`📊 Service status after stop: ${status}`);
|
||||
|
||||
// 10. Restart the service
|
||||
print("\n🔟 Restarting the service...");
|
||||
restart(service_manager, worker_name);
|
||||
print("🔄 Service restarted successfully");
|
||||
|
||||
// 11. Final cleanup
|
||||
print("\n🧹 Cleaning up - removing the service...");
|
||||
remove(service_manager, worker_name);
|
||||
print("🗑️ Service removed successfully");
|
||||
|
||||
// 12. Verify removal
|
||||
print("\n✅ Verifying service removal...");
|
||||
if !exists(service_manager, worker_name) {
|
||||
print("✅ Service successfully removed");
|
||||
} else {
|
||||
print("⚠️ Service still exists after removal");
|
||||
}
|
||||
|
||||
print("\n🎉 Circle worker management demonstration complete!");
|
Reference in New Issue
Block a user