Skip to content

osal.systemd #

SystemD Module

A V module for managing systemd services with comprehensive error handling and monitoring capabilities.

Features

  • Create, start, stop, and delete systemd services
  • Service status monitoring with detailed error reporting
  • Journal log retrieval with filtering options
  • Health checks for service validation
  • Automatic retry logic for service operations

Quick Start

import incubaid.herolib.lib.osal.systemd

// Create systemd factory
mut systemd := systemd.new()!

// Create a new service
mut redis_service := systemd.new(
    name: 'redis_custom'
    cmd: 'redis-server /etc/redis/redis.conf'
    description: 'Custom Redis server'
    start: true
)!

// Check service status
status := redis_service.status()!
println('Redis service status: ${status}')

// Get service logs
logs := redis_service.get_logs(50)!
println('Recent logs:\n${logs}')

Creating Services

Basic Service

mut service := systemd.new(
    name: 'my_service'
    cmd: '/usr/bin/my_application --config /etc/my_app.conf'
    description: 'My custom application'
    start: true
)!

Service with Environment Variables

mut service := systemd.new(
    name: 'web_app'
    cmd: '/usr/bin/webapp'
    description: 'Web application server'
    env: {
        'PORT': '8080'
        'ENV': 'production'
        'DB_HOST': 'localhost'
    }
    start: true
)!

Service with Complex Command

// For multi-line commands, systemd will create a script file
mut service := systemd.new(
    name: 'backup_service'
    cmd: '
        #!/bin/bash
        cd /var/backups
        tar -czf backup_$(date +%Y%m%d).tar.gz /home/data/
        aws s3 cp backup_$(date +%Y%m%d).tar.gz s3://my-bucket/
    '
    description: 'Daily backup service'
    start: true
)!

Service Management

Starting and Stopping Services

// Start service (with automatic verification)
service.start()! // Will wait and verify service started successfully

// Stop service (with verification)
service.stop()! // Will wait and verify service stopped

// Restart service
service.restart()!

Checking Service Status

// Get simple status
status := service.status()!
match status {
    .active { println('Service is running') }
    .failed { println('Service has failed') }
    .inactive { println('Service is stopped') }
    else { println('Service status: ${status}') }
}

// Get detailed status information
detailed_status := service.status_detailed()!
println(detailed_status)

Log Management

Basic Log Retrieval

// Get last 100 lines
logs := service.get_logs(100)!

// Using journalctl directly
logs := systemd.journalctl(service: 'my_service', limit: 50)!

Advanced Log Filtering

// Get error logs only
error_logs := systemd.journalctl_errors('my_service')!

// Get logs since specific time
recent_logs := systemd.journalctl_recent('my_service', '1 hour ago')!

// Custom log filtering
filtered_logs := systemd.journalctl(
    service: 'my_service'
    since: '2024-01-01'
    priority: 'warning'
    grep: 'connection'
    limit: 200
)!

Health Monitoring

Individual Service Health Check

is_healthy := systemd.validate_service('my_service')!
if !is_healthy {
    println('Service needs attention')
}

System-wide Health Check

health_results := systemd.health_check()!
for service_name, is_healthy in health_results {
    if !is_healthy {
        println('Service ${service_name} is not healthy')
    }
}

Error Handling

The module provides detailed error messages with log context:

// Service creation with error handling
mut service := systemd.new(
    name: 'problematic_service'
    cmd: '/nonexistent/binary'
    start: true
) or {
    println('Failed to create service: ${err}')
    // Error will include recent logs showing why service failed
    return
}

Service Deletion

// Stop and remove service completely
service.delete()!

// Or using systemd factory
systemd.destroy('service_name')!

Best Practices

  1. Always handle errors: Service operations can fail, always use ! or or blocks
  2. Use descriptive names: Service names should be clear and unique
  3. Check logs on failure: When services fail, check logs for diagnostic information
  4. Validate service health: Regularly check service status in production
  5. Use environment variables: Keep configuration flexible with environment variables

Common Patterns

Conditional Service Creation

if !systemd.exists('my_service') {
    mut service := systemd.new(
        name: 'my_service'
        cmd: 'my_application'
        start: true
    )!
}

Service with Dependency

// Ensure dependency is running first
redis_status := systemd.get('redis')!.status()!
if redis_status != .active {
    return error('Redis must be running before starting web service')
}

mut web_service := systemd.new(
    name: 'web_service'
    cmd: 'web_server --redis-host localhost:6379'
    start: true
)!

Troubleshooting

Service Won't Start

  1. Check service logs: service.get_logs(100)!
  2. Verify command exists: osal.cmd_exists('your_command')
  3. Check file permissions and paths
  4. Review systemd unit file: cat /etc/systemd/system/service_name.service

Service Keeps Failing

  1. Get error logs: systemd.journalctl_errors('service_name')!
  2. Check if command is executable
  3. Verify environment variables and working directory
  4. Test command manually: your_command_here

Testing

// Test module
vtest ~/code/github/incubaid/herolib/lib/osal/systemd/systemd_process_test.v

fn check #

fn check() !bool

check if systemd is on system, returns True if yes

fn journalctl #

fn journalctl(args JournalArgs) !string

fn journalctl_errors #

fn journalctl_errors(service string) !string

Add convenience methods

fn journalctl_recent #

fn journalctl_recent(service string, since string) !string

fn new #

fn new() !&Systemd

fn process_list #

fn process_list() ![]SystemdProcessInfo

enum ActiveState #

enum ActiveState {
	active       // The unit has been started successfully and is running as expected.
	inactive     // The unit is not running.
	activating   // The unit is in the process of being started.
	deactivating // The unit is in the process of being stopped.
	failed       // The unit tried to start but failed.
}

enum LoadState #

enum LoadState {
	loaded    // The unit's configuration file has been successfully loaded into memory.
	not_found // The unit's configuration file could not be found.
	error     // There was an error loading the unit's configuration file.
	masked    // The unit has been masked, which means it has been explicitly disabled and cannot be started.
}

enum SubState #

enum SubState {
	unknown
	start
	running // The service is currently running.
	exited  // The service has completed its process and exited. For services that do something at startup and then exit (oneshot services), this is a normal state.
	failed  // The service has failed after starting.
	waiting // The service is waiting for some condition to be met.
	autorestart
	dead
}

This provides more detailed information about the unit's state, often referred to as the "sub-state". This can vary significantly between different types of units (services, sockets, timers, etc.)

enum SystemdFactoryStatus #

enum SystemdFactoryStatus {
	init
	ok
	error
}

struct JournalArgs #

@[params]
struct JournalArgs {
pub:
	service  string // name of service for which logs will be retrieved
	limit    int = 100 // number of last log lines to be shown
	since    string // time since when to show logs (e.g., "1 hour ago", "2024-01-01")
	follow   bool   // follow logs in real-time
	priority string // log priority (emerg, alert, crit, err, warning, notice, info, debug)
	grep     string // filter logs containing this text
}

Add more flexible journalctl options

struct Systemd #

@[heap]
struct Systemd {
pub mut:
	processes []&SystemdProcess
	path      pathlib.Path
	path_cmd  pathlib.Path
	status    SystemdFactoryStatus
}

fn (Systemd) reload #

fn (mut systemd Systemd) reload() !

fn (Systemd) new #

fn (mut systemd Systemd) new(args_ SystemdProcessNewArgs) !SystemdProcess
 name      string            @[required]
 cmd       string            @[required]
 description string @[required]

fn (Systemd) names #

fn (mut systemd Systemd) names() []string

fn (Systemd) get #

fn (mut systemd Systemd) get(name_ string) !&SystemdProcess

fn (Systemd) exists #

fn (mut systemd Systemd) exists(name_ string) bool

fn (Systemd) destroy #

fn (mut systemd Systemd) destroy(name_ string) !

fn (Systemd) validate_service #

fn (mut systemd Systemd) validate_service(name string) !bool

Add validation method

fn (Systemd) health_check #

fn (mut systemd Systemd) health_check() !map[string]bool

Add method to check all services

struct SystemdProcess #

@[heap]
struct SystemdProcess {
pub mut:
	name        string
	unit        string // as generated or used by systemd
	cmd         string
	pid         int
	env         map[string]string
	systemd     &Systemd @[skip; str: skip]
	description string
	info        SystemdProcessInfo
	restart     bool = true // whether process will be restarted upon failure
}

fn (SystemdProcess) servicefile_path #

fn (mut self SystemdProcess) servicefile_path() string

fn (SystemdProcess) write #

fn (mut self SystemdProcess) write() !

fn (SystemdProcess) start #

fn (mut self SystemdProcess) start() !

fn (SystemdProcess) refresh #

fn (mut self SystemdProcess) refresh() !

get status from system

fn (SystemdProcess) delete #

fn (mut self SystemdProcess) delete() !

fn (SystemdProcess) stop #

fn (mut self SystemdProcess) stop() !

fn (SystemdProcess) restart #

fn (mut self SystemdProcess) restart() !

fn (SystemdProcess) get_logs #

fn (self SystemdProcess) get_logs(lines int) !string

fn (SystemdProcess) status #

fn (self SystemdProcess) status() !SystemdStatus

Improve status method with better error handling

fn (SystemdProcess) status_detailed #

fn (self SystemdProcess) status_detailed() !string

Add detailed status method

struct SystemdProcessInfo #

struct SystemdProcessInfo {
pub mut:
	unit         string
	load_state   LoadState
	active_state ActiveState
	sub_state    SubState
	description  string
}

struct SystemdProcessNewArgs #

@[params]
struct SystemdProcessNewArgs {
pub mut:
	name        string @[required]
	cmd         string @[required]
	description string
	env         map[string]string
	start       bool = true
	restart     bool = true
}