- Add `zinit_client` package to the workspace, enabling its use in the SAL monorepo. This allows for better organization and dependency management. - Update `MONOREPO_CONVERSION_PLAN.md` to reflect the addition of `zinit_client` and its status. This ensures the conversion plan stays up-to-date. - Move `src/zinit_client/` directory to `zinit_client/` for better organization. This improves the overall structure of the project. - Update references to `zinit_client` to use the new path. This ensures the codebase correctly links to the `zinit_client` package.
7.6 KiB
SAL Zinit Client (sal-zinit-client
)
A Rust client library for interacting with Zinit, a process supervisor daemon for Linux systems. This package provides both a Rust API and Rhai scripting integration for comprehensive service management.
Features
- Async Operations: Built on tokio for non-blocking communication
- Unix Socket Communication: Connects to Zinit daemon via Unix domain sockets
- Global Client Management: Efficient connection reuse with lazy initialization
- Comprehensive Service Management: Full lifecycle control (start, stop, restart, monitor, etc.)
- Service Configuration: Create, delete, and retrieve service configurations
- Real-time Log Streaming: Retrieve logs with filtering support
- Rhai Integration: Complete scripting support for automation
- Production Ready: Real-world tested with comprehensive error handling
Installation
Add this to your Cargo.toml
:
[dependencies]
sal-zinit-client = "0.1.0"
Quick Start
Rust API
use sal_zinit_client::{list, status, create_service, start, stop};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let socket_path = "/var/run/zinit.sock";
// List all services
let services = list(socket_path).await?;
println!("Services: {:?}", services);
// Create a new service
create_service(socket_path, "my-service", "echo 'Hello World'", true).await?;
// Start the service
start(socket_path, "my-service").await?;
// Get service status
let service_status = status(socket_path, "my-service").await?;
println!("Status: {:?}", service_status);
Ok(())
}
Rhai Scripting
// Zinit socket path
let socket_path = "/var/run/zinit.sock";
// List all services
let services = zinit_list(socket_path);
print(`Found ${services.len()} services`);
// Create and manage a service
let service_name = "rhai-test-service";
let exec_command = "echo 'Hello from Rhai'";
// Create service
zinit_create_service(socket_path, service_name, exec_command, true);
// Monitor and start
zinit_monitor(socket_path, service_name);
zinit_start(socket_path, service_name);
// Get status
let status = zinit_status(socket_path, service_name);
print(`Service state: ${status.state}`);
// Clean up
zinit_stop(socket_path, service_name);
zinit_forget(socket_path, service_name);
zinit_delete_service(socket_path, service_name);
API Reference
Core Functions
Service Management
list(socket_path)
- List all services and their statesstatus(socket_path, name)
- Get detailed status of a specific servicestart(socket_path, name)
- Start a servicestop(socket_path, name)
- Stop a servicerestart(socket_path, name)
- Restart a servicemonitor(socket_path, name)
- Start monitoring a serviceforget(socket_path, name)
- Stop monitoring a servicekill(socket_path, name, signal)
- Send a signal to a service
Service Configuration
create_service(socket_path, name, exec, oneshot)
- Create a simple servicecreate_service_full(socket_path, name, exec, oneshot, after, env, log, test)
- Create service with full optionsdelete_service(socket_path, name)
- Delete a serviceget_service(socket_path, name)
- Get service configuration
Logs
logs(socket_path, filter)
- Get logs with optional filteringlogs(socket_path, None)
- Get all logs
Rhai Functions
All Rust functions are available in Rhai with zinit_
prefix:
zinit_list(socket_path)
→ Mapzinit_status(socket_path, name)
→ Mapzinit_start(socket_path, name)
→ boolzinit_stop(socket_path, name)
→ boolzinit_restart(socket_path, name)
→ boolzinit_monitor(socket_path, name)
→ boolzinit_forget(socket_path, name)
→ boolzinit_kill(socket_path, name, signal)
→ boolzinit_create_service(socket_path, name, exec, oneshot)
→ Stringzinit_delete_service(socket_path, name)
→ Stringzinit_get_service(socket_path, name)
→ Dynamiczinit_logs(socket_path, filter)
→ Arrayzinit_logs_all(socket_path)
→ Array
Configuration
Socket Paths
Common Zinit socket locations:
/var/run/zinit.sock
(default system location)/tmp/zinit.sock
(temporary/testing)/run/zinit.sock
(alternative system location)
Environment Variables
The client respects standard environment configurations and handles connection failures gracefully.
Testing
The package includes comprehensive tests that work with real Zinit servers:
# Run all tests
cargo test
# Run only unit tests
cargo test --test zinit_client_tests
# Run only Rhai integration tests
cargo test --test rhai_integration_tests
Test Requirements
IMPORTANT: For full test coverage, you must start a Zinit server before running tests:
# Start Zinit for testing (recommended for development)
zinit -s /tmp/zinit.sock init
# Alternative: Start with system socket (requires sudo)
sudo zinit --socket /var/run/zinit.sock init
# Or use systemd (if available)
sudo systemctl start zinit
Without a running Zinit server:
- Tests will gracefully skip when no socket is available
- You'll see messages like "⚠ No Zinit socket found. Tests will be skipped."
- This is expected behavior and not a test failure
With a running Zinit server:
- Tests will connect to the server and perform real operations
- Service creation, management, and deletion will be tested
- Log retrieval and signal handling will be validated
Examples
Service Lifecycle Management
use sal_zinit_client::*;
async fn manage_web_server() -> Result<(), Box<dyn std::error::Error>> {
let socket = "/var/run/zinit.sock";
let service = "web-server";
// Create web server service
create_service(socket, service, "python3 -m http.server 8080", false).await?;
// Start monitoring and run
monitor(socket, service).await?;
start(socket, service).await?;
// Check if running
let status = status(socket, service).await?;
println!("Web server PID: {}", status.pid);
// Graceful shutdown
stop(socket, service).await?;
forget(socket, service).await?;
delete_service(socket, service).await?;
Ok(())
}
Log Monitoring
use sal_zinit_client::logs;
async fn monitor_logs() -> Result<(), Box<dyn std::error::Error>> {
let socket = "/var/run/zinit.sock";
// Get all logs
let all_logs = logs(socket, None).await?;
println!("Total log entries: {}", all_logs.len());
// Get filtered logs
let error_logs = logs(socket, Some("error".to_string())).await?;
println!("Error log entries: {}", error_logs.len());
Ok(())
}
Error Handling
The client provides comprehensive error handling:
use sal_zinit_client::{list, ZinitError};
async fn handle_errors() {
let socket = "/invalid/path/zinit.sock";
match list(socket).await {
Ok(services) => println!("Services: {:?}", services),
Err(e) => {
eprintln!("Zinit error: {}", e);
// Handle specific error types
}
}
}
Integration with SAL
This package is part of the SAL (System Abstraction Layer) ecosystem:
use sal::zinit_client;
// Access through SAL
let services = sal::zinit_client::list("/var/run/zinit.sock").await?;
Contributing
This package follows SAL's strict quality standards:
- Real functionality only (no placeholders or stubs)
- Comprehensive test coverage with actual behavior validation
- Production-ready error handling and logging
- Security considerations for credential handling
License
Apache-2.0