Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run
- 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.
273 lines
7.6 KiB
Markdown
273 lines
7.6 KiB
Markdown
# SAL Zinit Client (`sal-zinit-client`)
|
|
|
|
A Rust client library for interacting with [Zinit](https://github.com/systeminit/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`:
|
|
|
|
```toml
|
|
[dependencies]
|
|
sal-zinit-client = "0.1.0"
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
### Rust API
|
|
|
|
```rust
|
|
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
|
|
|
|
```rhai
|
|
// 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 states
|
|
- `status(socket_path, name)` - Get detailed status of a specific service
|
|
- `start(socket_path, name)` - Start a service
|
|
- `stop(socket_path, name)` - Stop a service
|
|
- `restart(socket_path, name)` - Restart a service
|
|
- `monitor(socket_path, name)` - Start monitoring a service
|
|
- `forget(socket_path, name)` - Stop monitoring a service
|
|
- `kill(socket_path, name, signal)` - Send a signal to a service
|
|
|
|
#### Service Configuration
|
|
- `create_service(socket_path, name, exec, oneshot)` - Create a simple service
|
|
- `create_service_full(socket_path, name, exec, oneshot, after, env, log, test)` - Create service with full options
|
|
- `delete_service(socket_path, name)` - Delete a service
|
|
- `get_service(socket_path, name)` - Get service configuration
|
|
|
|
#### Logs
|
|
- `logs(socket_path, filter)` - Get logs with optional filtering
|
|
- `logs(socket_path, None)` - Get all logs
|
|
|
|
### Rhai Functions
|
|
|
|
All Rust functions are available in Rhai with `zinit_` prefix:
|
|
|
|
- `zinit_list(socket_path)` → Map
|
|
- `zinit_status(socket_path, name)` → Map
|
|
- `zinit_start(socket_path, name)` → bool
|
|
- `zinit_stop(socket_path, name)` → bool
|
|
- `zinit_restart(socket_path, name)` → bool
|
|
- `zinit_monitor(socket_path, name)` → bool
|
|
- `zinit_forget(socket_path, name)` → bool
|
|
- `zinit_kill(socket_path, name, signal)` → bool
|
|
- `zinit_create_service(socket_path, name, exec, oneshot)` → String
|
|
- `zinit_delete_service(socket_path, name)` → String
|
|
- `zinit_get_service(socket_path, name)` → Dynamic
|
|
- `zinit_logs(socket_path, filter)` → Array
|
|
- `zinit_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:
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```rust
|
|
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
|
|
|
|
```rust
|
|
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:
|
|
|
|
```rust
|
|
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:
|
|
|
|
```rust
|
|
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
|