# Init-Servs Crate - Comprehensive Integration Guide ## Overview The Init-Servs crate provides a unified abstraction for managing services across different init systems. This comprehensive guide covers advanced integration patterns, platform-specific considerations, and best practices for cross-platform service management in production environments. ## Table of Contents 1. [Supported Init Systems](#supported-init-systems) 2. [Platform Detection Strategy](#platform-detection-strategy) 3. [Service File Generation](#service-file-generation) 4. [Configuration Patterns](#configuration-patterns) 5. [Init System Specifics](#init-system-specifics) 6. [Advanced Service Configuration](#advanced-service-configuration) 7. [Restart Policies](#restart-policies) 8. [Environment Management](#environment-management) 9. [Error Handling](#error-handling) 10. [Security Considerations](#security-considerations) 11. [Troubleshooting](#troubleshooting) ## Supported Init Systems ### systemd (Linux) **Platforms**: Most modern Linux distributions (Ubuntu, Debian, CentOS, Fedora, etc.) **Service Directory**: `/etc/systemd/system/` or `/usr/lib/systemd/system/` **Key Features**: - Unit-based service management - Advanced restart policies - Dependency ordering - Resource limits - Journal logging integration **Service File Example**: ```ini [Unit] Description=My App Service After=network-online.target [Service] Type=simple User=myapp Group=myapp ExecStart=/usr/bin/my-app ExecStop=/bin/kill $MAINPID Restart=on-failure RestartSec=10 Environment="RUST_LOG=info" WorkingDirectory=/var/lib/myapp [Install] WantedBy=multi-user.target ``` **Configuration in Rust**: ```rust use init_servs::{ServiceConfig, InitSystemType, InitConfig, InitSystem}; let config = InitConfig::new(InitSystemType::Systemd); let init = InitSystem::new(config); let service = ServiceConfig::new("myapp") .with_description("My App Service") .with_exec_start("/usr/bin/my-app") .with_user("myapp") .with_group("myapp") .with_working_directory("/var/lib/myapp") .with_environment("RUST_LOG", "info") .with_after(vec!["network-online.target".to_string()]) .with_requires(vec!["network-online.target".to_string()]); init.install_service(&service).await?; ``` ### launchd (macOS) **Platforms**: macOS (all versions) **Service Directory**: - User agents: `~/Library/LaunchAgents/` - System daemons: `/Library/LaunchDaemons/` **Key Features**: - Property list (plist) format - Launch-on-demand support - KeepAlive policies - Standard input/output redirection - Environment variable support **Service File Example**: ```xml Label com.example.myapp ProgramArguments /usr/local/bin/my-app KeepAlive StandardOutPath /var/log/myapp/app.log StandardErrorPath /var/log/myapp/app.err EnvironmentVariables RUST_LOG info ``` **Configuration in Rust**: ```rust use init_servs::{ServiceConfig, InitSystemType, InitConfig, InitSystem}; let config = InitConfig::new(InitSystemType::Launchd); let init = InitSystem::new(config); let service = ServiceConfig::new("com.example.myapp") .with_description("My App Service") .with_exec_start("/usr/local/bin/my-app") .with_environment("RUST_LOG", "info") .with_working_directory("/var/lib/myapp"); init.install_service(&service).await?; ``` ### OpenRC (Alpine Linux, Gentoo) **Platforms**: Alpine Linux, Gentoo, and other distributions using OpenRC **Service Directory**: `/etc/init.d/` **Key Features**: - Shell script-based services - Dependency ordering via rc-service - Simple configuration syntax - Process supervision **Service File Example**: ```bash #!/sbin/openrc-run description="My App Service" command="/usr/bin/my-app" command_user="myapp" command_background="yes" pidfile="/run/myapp/app.pid" output_log="/var/log/myapp/app.log" error_log="/var/log/myapp/app.err" directory="/var/lib/myapp" # Environment variables export RUST_LOG=info # Supervisor configuration supervisor="supervise-daemon" ``` **Configuration in Rust**: ```rust use init_servs::{ServiceConfig, InitSystemType, InitConfig, InitSystem}; let config = InitConfig::new(InitSystemType::OpenRC); let init = InitSystem::new(config); let service = ServiceConfig::new("myapp") .with_description("My App Service") .with_exec_start("/usr/bin/my-app") .with_user("myapp") .with_group("myapp") .with_working_directory("/var/lib/myapp") .with_environment("RUST_LOG", "info"); init.install_service(&service).await?; ``` ### runit (Void Linux, Devuan) **Platforms**: Void Linux, Devuan, and other runit-based systems **Service Directory**: `/etc/sv/` **Key Features**: - Supervision suite for service management - Run scripts (executable shell scripts) - Process supervision by supervise daemon - Signal handling **Service File Structure**: ``` /etc/sv/myapp/ ├── run # Main service script ├── finish # Cleanup script (optional) ├── log/ │ ├── run # Logging script │ └── main/ # Log directory ``` **Run Script Example**: ```bash #!/bin/sh exec 2>&1 exec chpst -u myapp -g myapp \ env RUST_LOG=info \ /usr/bin/my-app ``` **Configuration in Rust**: ```rust use init_servs::{ServiceConfig, InitSystemType, InitConfig, InitSystem}; let config = InitConfig::new(InitSystemType::Runit); let init = InitSystem::new(config); let service = ServiceConfig::new("myapp") .with_description("My App Service") .with_exec_start("/usr/bin/my-app") .with_user("myapp") .with_group("myapp") .with_environment("RUST_LOG", "info"); init.install_service(&service).await?; ``` ## Platform Detection Strategy The init-servs crate automatically detects the appropriate init system: ### Linux Detection Priority ``` 1. systemd (check: /run/systemd/system or ps output) 2. OpenRC (check: /sbin/openrc-run) 3. runit (check: /command/supervise or /sbin/runit) 4. sysvinit (fallback) ``` **Implementation**: ```rust use init_servs::detect_init_system; #[tokio::main] async fn main() -> Result<(), Box> { let init_type = detect_init_system().await?; println!("Detected: {}", init_type.name()); // systemd, launchd, OpenRC, or runit Ok(()) } ``` ### macOS Detection On macOS, launchd is the only supported init system: ```rust use init_servs::{InitSystemType, InitConfig, InitSystem}; #[cfg(target_os = "macos")] fn create_init_system() -> InitSystem { let config = InitConfig::new(InitSystemType::Launchd); InitSystem::new(config) } ``` ### Cross-Platform Usage ```rust use init_servs::{detect_init_system, InitSystem, InitConfig, ServiceConfig}; #[tokio::main] async fn main() -> Result<(), Box> { // Auto-detect init system let init_type = detect_init_system().await?; // Create configuration for detected system let config = InitConfig::new(init_type); let init = InitSystem::new(config); // Define service (works across all platforms) let service = ServiceConfig::new("my-app") .with_description("My Application") .with_exec_start("/usr/bin/my-app") .with_user("appuser") .with_restart_policy(init_servs::RestartPolicy::OnFailure); // Install (generates correct format for detected init system) init.install_service(&service).await?; println!("Service installed using {}", init_type.name()); Ok(()) } ``` ## Service File Generation ### Auto-Generation from Rust Configuration The init-servs crate automatically generates native service files: ```rust use init_servs::{ServiceConfig, InitSystemType, InitConfig, InitSystem}; #[tokio::main] async fn main() -> Result<(), Box> { let config = InitConfig::new(InitSystemType::Systemd); let init = InitSystem::new(config); let service = ServiceConfig::new("tracker") .with_description("My App Service") .with_exec_start("/usr/bin/my-app") .with_user("myapp") .with_environment("RUST_LOG", "info"); // Generates: /etc/systemd/system/tracker.service let generated_file = init.generate_service_file(&service).await?; println!("Generated: {}", generated_file); Ok(()) } ``` ### Viewing Generated Content ```rust use init_servs::{ServiceConfig, InitSystemType, InitConfig, InitSystem}; #[tokio::main] async fn main() -> Result<(), Box> { let config = InitConfig::new(InitSystemType::Systemd); let init = InitSystem::new(config); let service = ServiceConfig::new("tracker") .with_description("My App Service") .with_exec_start("/usr/bin/my-app") .with_user("myapp"); // Get generated content without writing to disk let content = init.generate_service_content(&service)?; println!("{}", content); Ok(()) } ``` ## Configuration Patterns ### Basic Service Configuration ```rust let service = ServiceConfig::new("myservice") .with_description("My Service") .with_exec_start("/usr/bin/myservice"); ``` ### Configuration with All Options ```rust let service = ServiceConfig::new("myservice") .with_description("My Service") .with_exec_start("/usr/bin/myservice") .with_exec_stop("/bin/kill $MAINPID") .with_user("myuser") .with_group("mygroup") .with_working_directory("/var/lib/myservice") .with_restart_policy(init_servs::RestartPolicy::OnFailure) .with_environment("LOG_LEVEL", "debug") .with_environment("CONFIG_PATH", "/etc/myservice.conf") .with_after(vec!["network-online.target".to_string()]) .with_requires(vec!["postgresql.service".to_string()]); ``` ### Environment-Driven Configuration ```rust use init_servs::ServiceConfig; fn create_service_from_env(name: &str) -> ServiceConfig { let mut service = ServiceConfig::new(name); // Load from environment if let Ok(exec_start) = std::env::var("EXEC_START") { service = service.with_exec_start(&exec_start); } if let Ok(user) = std::env::var("SERVICE_USER") { service = service.with_user(&user); } if let Ok(log_level) = std::env::var("LOG_LEVEL") { service = service.with_environment("RUST_LOG", &log_level); } service } ``` ### Template-Based Configuration ```rust use init_servs::{ServiceConfig, ServiceTemplate}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] struct ServiceTemplate { name: String, description: String, exec_start: String, user: String, group: String, environment: std::collections::HashMap, } fn create_service_from_template(template: ServiceTemplate) -> ServiceConfig { let mut service = ServiceConfig::new(&template.name) .with_description(&template.description) .with_exec_start(&template.exec_start) .with_user(&template.user) .with_group(&template.group); for (key, value) in template.environment { service = service.with_environment(&key, &value); } service } ``` ## Init System Specifics ### systemd-Specific Features **Resource Limits**: ```rust let service = ServiceConfig::new("tracker") // These map to systemd directives .with_exec_start("/usr/bin/my-app") .with_exec_stop("systemctl stop my-app"); // Note: Full resource limit support would require extending ServiceConfig ``` **Socket Activation** (Advanced): Create separate .socket and .service files for socket activation. ### launchd-Specific Features **Launch-On-Demand**: ```rust // launchd natively supports launch-on-demand let service = ServiceConfig::new("com.example.myapp") .with_description("My App Service"); // launchd will automatically start on first request ``` **Standard I/O Redirection**: ```rust let service = ServiceConfig::new("tracker") .with_environment("LOG_PATH", "/var/log/tracker.log"); // Maps to StandardOutPath in generated plist ``` ### OpenRC-Specific Features **Dependency Ordering**: ```rust let service = ServiceConfig::new("tracker") .with_after(vec!["networking.service".to_string()]) .with_requires(vec!["postgresql.service".to_string()]); ``` ### runit-Specific Features **Logging Service**: For runit, logging is handled via a separate log/run script: ```bash #!/bin/sh exec svlogd -tt /var/log/tracker ``` The init-servs crate automatically creates this when needed. ## Advanced Service Configuration ### Services with Dependencies ```rust use init_servs::{ServiceConfig, RestartPolicy}; #[tokio::main] async fn main() -> Result<(), Box> { let config = InitConfig::default(); let init = InitSystem::new(config); // Database service let db_service = ServiceConfig::new("tracker-db") .with_description("Tracker Database") .with_exec_start("/usr/bin/postgresql"); // API service depends on database let api_service = ServiceConfig::new("app-api") .with_description("App API") .with_exec_start("/usr/bin/my-app-api") .with_after(vec!["tracker-db.service".to_string()]) .with_requires(vec!["tracker-db.service".to_string()]); init.install_service(&db_service).await?; init.install_service(&api_service).await?; Ok(()) } ``` ### Multi-Instance Services ```rust use init_servs::ServiceConfig; fn create_instance_service(instance: u32) -> ServiceConfig { ServiceConfig::new(&format!("app-{}", instance)) .with_description(&format!("App Instance {}", instance)) .with_exec_start(&format!("/usr/bin/my-app --instance {}", instance)) .with_user("myapp") .with_environment("INSTANCE_ID", &instance.to_string()) } ``` ### Health Check Integration ```rust let service = ServiceConfig::new("app") .with_description("My App Service") .with_exec_start("/usr/bin/my-app") // For systemd, add health check via ExecHealthCheck .with_exec_stop("/usr/bin/tracker-health-check"); ``` ## Restart Policies ### Policy Types ```rust use init_servs::RestartPolicy; // No restart on failure let service = ServiceConfig::new("tracker") .with_restart_policy(RestartPolicy::No); // Always restart let service = ServiceConfig::new("tracker") .with_restart_policy(RestartPolicy::Always); // Restart only on failure let service = ServiceConfig::new("tracker") .with_restart_policy(RestartPolicy::OnFailure); // Restart on abnormal termination let service = ServiceConfig::new("tracker") .with_restart_policy(RestartPolicy::OnAbnormal); ``` ### Restart Behavior by Init System | Policy | systemd | launchd | OpenRC | runit | |--------|---------|---------|--------|-------| | No | `Restart=no` | Not supported | - | - | | Always | `Restart=always` | `KeepAlive=true` | `respawn` | Automatic | | OnFailure | `Restart=on-failure` | Limited support | `respawn` | Automatic | | OnAbnormal | `Restart=on-abnormal` | Custom handler | - | - | ## Environment Management ### Setting Environment Variables ```rust let service = ServiceConfig::new("tracker") .with_environment("RUST_LOG", "debug") .with_environment("TRACKER_PORT", "6969") .with_environment("DATABASE_URL", "sqlite:///var/lib/tracker.db") .with_environment("CONFIG_PATH", "/etc/tracker/config.toml"); ``` ### Loading from .env Files ```rust use std::fs; fn load_env_file(path: &str) -> std::collections::HashMap { let content = fs::read_to_string(path).unwrap_or_default(); content .lines() .filter(|line| !line.starts_with('#') && !line.is_empty()) .map(|line| { let parts: Vec<&str> = line.split('=').collect(); if parts.len() == 2 { (parts[0].to_string(), parts[1].to_string()) } else { (String::new(), String::new()) } }) .collect() } let env_vars = load_env_file("/etc/tracker/.env"); let mut service = ServiceConfig::new("tracker"); for (key, value) in env_vars { service = service.with_environment(&key, &value); } ``` ### Secret Management Integration ```rust use init_servs::ServiceConfig; // Load secrets from vault or similar async fn create_service_with_secrets(name: &str) -> ServiceConfig { let mut service = ServiceConfig::new(name); // Load from encrypt crate use encrypt::config::EncryptionConfig; let config = EncryptionConfig::default(); // Load encrypted secrets and set as environment service } ``` ## Error Handling ### Common Error Scenarios **Permission Denied**: ```rust use init_servs::InitError; match init.install_service(&service).await { Err(InitError::PermissionDenied) => { eprintln!("Permission denied. Run with sudo or proper permissions"); } Err(e) => eprintln!("Error: {}", e), Ok(_) => println!("Service installed"), } ``` **Service Already Exists**: ```rust match init.install_service(&service).await { Err(InitError::ServiceExists) => { eprintln!("Service already exists. Remove it first: sudo systemctl stop tracker"); } Err(e) => eprintln!("Error: {}", e), Ok(_) => println!("Service installed"), } ``` **Init System Not Supported**: ```rust use init_servs::detect_init_system; match detect_init_system().await { Ok(init_type) => { println!("Using: {}", init_type.name()); } Err(e) => { eprintln!("Could not detect init system: {}", e); eprintln!("Please specify manually"); } } ``` ## Security Considerations ### User and Group Management **Best Practices**: ```bash # Create dedicated user for service sudo useradd -r -s /bin/false myapp # Create service-specific group sudo groupadd myapp # Assign user to group sudo usermod -g myapp myapp ``` **In Configuration**: ```rust let service = ServiceConfig::new("app") .with_user("myapp") // Non-root user .with_group("myapp") // Service-specific group .with_working_directory("/var/lib/myapp"); // Restricted directory ``` ### File Permissions ```bash # Set proper ownership sudo chown -R myapp:myapp /var/lib/myapp sudo chown -R myapp:myapp /etc/myapp # Set restrictive permissions sudo chmod 700 /var/lib/myapp sudo chmod 750 /etc/myapp sudo chmod 640 /etc/myapp/*.conf ``` ### Environment Variable Security ```rust let service = ServiceConfig::new("app") .with_user("myapp") // Run as non-root // Don't include secrets in environment // Use credential files with restricted permissions instead .with_environment("CONFIG_PATH", "/etc/myapp/secrets.conf"); ``` **Secure file permissions**: ```bash sudo chmod 600 /etc/myapp/secrets.conf sudo chown myapp:myapp /etc/myapp/secrets.conf ``` ## Troubleshooting ### Service Won't Start **Diagnosis**: ```bash # systemd sudo systemctl status tracker sudo journalctl -u tracker -n 50 # launchd log show --predicate 'process == "tracker"' --last 1h # OpenRC sudo rc-service tracker status sudo tail -f /var/log/tracker.log # runit sudo sv status tracker sudo tail -f /var/log/tracker/current ``` **Common Issues**: 1. Executable not found: Check `with_exec_start()` path 2. User doesn't exist: Create user before installing service 3. Working directory doesn't exist: Create directory first 4. Permission denied: Check file ownership and permissions ### Service Crashes Immediately **Causes**: 1. Missing dependencies (check `with_requires()`) 2. Configuration file not found 3. Port already in use 4. Insufficient resources **Solution**: ```bash # Run service manually to see error /usr/bin/my-app # Check logs journalctl -u app -n 100 --no-pager ``` ### Service Not Restarting **Check restart policy**: ```rust let service = ServiceConfig::new("app") .with_restart_policy(init_servs::RestartPolicy::OnFailure); ``` **Verify in generated file**: ```bash # systemd cat /etc/systemd/system/app.service | grep Restart # launchd defaults read ~/Library/LaunchAgents/com.example.myapp.plist | grep -i keepalive ``` ### Managing Service Lifecycle ```bash # systemd sudo systemctl start app sudo systemctl stop app sudo systemctl restart app sudo systemctl status app sudo systemctl enable app # Start on boot sudo systemctl disable app # Don't start on boot # launchd launchctl load ~/Library/LaunchAgents/com.example.myapp.plist launchctl unload ~/Library/LaunchAgents/com.example.myapp.plist launchctl start com.example.myapp launchctl stop com.example.myapp # OpenRC sudo rc-service tracker start sudo rc-service tracker stop sudo rc-service tracker restart sudo rc-update add tracker # Start on boot sudo rc-update del tracker # Don't start on boot # runit sudo sv start tracker sudo sv stop tracker sudo sv restart tracker ``` ## Integration Patterns ### Deployment Automation ```rust use init_servs::{InitSystem, InitConfig, ServiceConfig}; #[tokio::main] async fn main() -> Result<(), Box> { // Auto-detect init system let config = InitConfig::default(); let init = InitSystem::new(config); // Create and install services let services = vec![ create_database_service(), create_tracker_service(), create_api_service(), ]; for service in services { init.install_service(&service).await?; } println!("All services installed successfully"); Ok(()) } ``` ### Infrastructure as Code ```rust use init_servs::ServiceConfig; use serde::Deserialize; use std::fs; #[derive(Deserialize)] struct ServiceManifest { services: Vec, } #[derive(Deserialize)] struct ServiceDefinition { name: String, description: String, exec_start: String, user: String, group: String, environment: std::collections::HashMap, } fn load_manifest(path: &str) -> Result, Box> { let content = fs::read_to_string(path)?; let manifest: ServiceManifest = serde_yaml::from_str(&content)?; Ok(manifest .services .into_iter() .map(|def| { let mut service = ServiceConfig::new(&def.name) .with_description(&def.description) .with_exec_start(&def.exec_start) .with_user(&def.user) .with_group(&def.group); for (key, value) in def.environment { service = service.with_environment(&key, &value); } service }) .collect()) } ``` ### Container-to-Systemd Migration For migrating containerized services to native systemd: ```rust use init_servs::ServiceConfig; fn migrate_from_docker( image: &str, binary_path: &str, ) -> ServiceConfig { ServiceConfig::new("migrated-service") .with_description(&format!("Migrated from {}", image)) .with_exec_start(binary_path) .with_user("appuser") .with_restart_policy(init_servs::RestartPolicy::OnFailure) // Additional configuration } ``` ## Conclusion The Init-Servs crate provides a robust, cross-platform abstraction for service management. Proper configuration and understanding of platform-specific behaviors ensure reliable service operation across diverse environments. For more information, see: - [Init-Servs README.md](README.md) - API documentation - [Examples](../../../examples/) - Complete examples - [Integration Architecture](../../../docs/integration-architecture.md) - System integration patterns