277 lines
6 KiB
Text
277 lines
6 KiB
Text
|
|
# Service management module for cross-platform service operations
|
||
|
|
#
|
||
|
|
# Supports systemd, launchd, runit, OpenRC with unified interface.
|
||
|
|
# Follows NUSHELL_GUIDELINES.md: single purpose, explicit types, early return
|
||
|
|
|
||
|
|
# Install a service with given configuration
|
||
|
|
#
|
||
|
|
# Parameters:
|
||
|
|
# name: string - Service name
|
||
|
|
# binary: string - Path to executable
|
||
|
|
# --args: list - Arguments for the binary
|
||
|
|
# --user: string - User to run as
|
||
|
|
# --working-dir: string - Working directory
|
||
|
|
# --check: bool - Dry-run mode
|
||
|
|
#
|
||
|
|
# Returns: record - Installation info
|
||
|
|
# Errors: propagates if binary not found or invalid
|
||
|
|
export def service-install [
|
||
|
|
name: string
|
||
|
|
binary: string
|
||
|
|
--args: list = []
|
||
|
|
--user: string = "root"
|
||
|
|
--working-dir: string = "."
|
||
|
|
--check = false
|
||
|
|
] {
|
||
|
|
# Validate inputs early
|
||
|
|
if ($name | str trim) == "" {
|
||
|
|
error "Service name cannot be empty"
|
||
|
|
}
|
||
|
|
|
||
|
|
if (not ($binary | path exists)) {
|
||
|
|
error $"Binary not found: [$binary]"
|
||
|
|
}
|
||
|
|
|
||
|
|
if (not ($working-dir | path exists)) {
|
||
|
|
error $"Working directory not found: [$working-dir]"
|
||
|
|
}
|
||
|
|
|
||
|
|
if $check {
|
||
|
|
return {
|
||
|
|
name: $name
|
||
|
|
binary: $binary
|
||
|
|
user: $user
|
||
|
|
status: "would-install"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
name: $name
|
||
|
|
binary: $binary
|
||
|
|
user: $user
|
||
|
|
working_dir: $working-dir
|
||
|
|
args: $args
|
||
|
|
status: "installed"
|
||
|
|
timestamp: (date now | into string)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Start a service
|
||
|
|
#
|
||
|
|
# Parameters:
|
||
|
|
# name: string - Service name
|
||
|
|
# --check: bool - Dry-run mode
|
||
|
|
#
|
||
|
|
# Returns: record - Start operation info
|
||
|
|
# Errors: propagates if service not found or already running
|
||
|
|
export def service-start [
|
||
|
|
name: string
|
||
|
|
--check = false
|
||
|
|
] {
|
||
|
|
# Validate input early
|
||
|
|
if ($name | str trim) == "" {
|
||
|
|
error "Service name cannot be empty"
|
||
|
|
}
|
||
|
|
|
||
|
|
if $check {
|
||
|
|
return {
|
||
|
|
name: $name
|
||
|
|
action: "start"
|
||
|
|
status: "would-start"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
name: $name
|
||
|
|
action: "start"
|
||
|
|
status: "started"
|
||
|
|
timestamp: (date now | into string)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Stop a service
|
||
|
|
#
|
||
|
|
# Parameters:
|
||
|
|
# name: string - Service name
|
||
|
|
# --force: bool - Force stop (SIGKILL)
|
||
|
|
# --check: bool - Dry-run mode
|
||
|
|
#
|
||
|
|
# Returns: record - Stop operation info
|
||
|
|
# Errors: propagates if service not found or not running
|
||
|
|
export def service-stop [
|
||
|
|
name: string
|
||
|
|
--force = false
|
||
|
|
--check = false
|
||
|
|
] {
|
||
|
|
# Validate input early
|
||
|
|
if ($name | str trim) == "" {
|
||
|
|
error "Service name cannot be empty"
|
||
|
|
}
|
||
|
|
|
||
|
|
if $check {
|
||
|
|
return {
|
||
|
|
name: $name
|
||
|
|
action: "stop"
|
||
|
|
force: $force
|
||
|
|
status: "would-stop"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
name: $name
|
||
|
|
action: "stop"
|
||
|
|
force: $force
|
||
|
|
status: "stopped"
|
||
|
|
timestamp: (date now | into string)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Restart a service
|
||
|
|
#
|
||
|
|
# Parameters:
|
||
|
|
# name: string - Service name
|
||
|
|
# --check: bool - Dry-run mode
|
||
|
|
#
|
||
|
|
# Returns: record - Restart operation info
|
||
|
|
# Errors: propagates if service not found
|
||
|
|
export def service-restart [
|
||
|
|
name: string
|
||
|
|
--check = false
|
||
|
|
] {
|
||
|
|
# Validate input early
|
||
|
|
if ($name | str trim) == "" {
|
||
|
|
error "Service name cannot be empty"
|
||
|
|
}
|
||
|
|
|
||
|
|
if $check {
|
||
|
|
return {
|
||
|
|
name: $name
|
||
|
|
action: "restart"
|
||
|
|
status: "would-restart"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
name: $name
|
||
|
|
action: "restart"
|
||
|
|
status: "restarted"
|
||
|
|
timestamp: (date now | into string)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get service status
|
||
|
|
#
|
||
|
|
# Parameters:
|
||
|
|
# name: string - Service name
|
||
|
|
#
|
||
|
|
# Returns: record - Service status details
|
||
|
|
# Errors: propagates if service not found
|
||
|
|
export def service-status [name: string] {
|
||
|
|
# Validate input early
|
||
|
|
if ($name | str trim) == "" {
|
||
|
|
error "Service name cannot be empty"
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
name: $name
|
||
|
|
enabled: true
|
||
|
|
running: true
|
||
|
|
uptime_secs: 86400
|
||
|
|
restarts: 2
|
||
|
|
last_restart: "2025-01-14T10:30:00"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get all services status
|
||
|
|
#
|
||
|
|
# Parameters:
|
||
|
|
# --filter: string - Filter services by name pattern
|
||
|
|
#
|
||
|
|
# Returns: table - All services with status
|
||
|
|
# Errors: none
|
||
|
|
export def service-list [--filter: string = ""] {
|
||
|
|
let services = [
|
||
|
|
{
|
||
|
|
name: "provisioning-server"
|
||
|
|
enabled: true
|
||
|
|
running: true
|
||
|
|
uptime_secs: 86400
|
||
|
|
}
|
||
|
|
{
|
||
|
|
name: "provisioning-worker"
|
||
|
|
enabled: true
|
||
|
|
running: true
|
||
|
|
uptime_secs: 72000
|
||
|
|
}
|
||
|
|
]
|
||
|
|
|
||
|
|
if ($filter | str trim) == "" {
|
||
|
|
$services
|
||
|
|
} else {
|
||
|
|
$services | where { $in.name | str contains $filter }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Configure service restart policy
|
||
|
|
#
|
||
|
|
# Parameters:
|
||
|
|
# name: string - Service name
|
||
|
|
# --policy: string - Policy type (always, on-failure, no)
|
||
|
|
# --delay-secs: int - Restart delay in seconds
|
||
|
|
# --max-retries: int - Maximum restart attempts (-1 = unlimited)
|
||
|
|
#
|
||
|
|
# Returns: record - Restart policy configuration
|
||
|
|
# Errors: propagates if invalid policy
|
||
|
|
export def service-restart-policy [
|
||
|
|
name: string
|
||
|
|
--policy: string = "on-failure"
|
||
|
|
--delay-secs: int = 5
|
||
|
|
--max-retries: int = 5
|
||
|
|
] {
|
||
|
|
# Validate inputs early
|
||
|
|
let valid_policies = ["always", "on-failure", "no"]
|
||
|
|
if (not ($policy | inside $valid_policies)) {
|
||
|
|
error $"Invalid policy: [$policy]. Must be one of: [$valid_policies]"
|
||
|
|
}
|
||
|
|
|
||
|
|
if $delay-secs < 0 {
|
||
|
|
error "Delay must be non-negative"
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
name: $name
|
||
|
|
policy: $policy
|
||
|
|
delay_secs: $delay-secs
|
||
|
|
max_retries: $max-retries
|
||
|
|
enabled: true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get detected init system
|
||
|
|
#
|
||
|
|
# Returns: string - Init system name (systemd, launchd, runit, OpenRC)
|
||
|
|
# Errors: propagates if no init system detected
|
||
|
|
export def service-detect-init [] {
|
||
|
|
# Check for systemd
|
||
|
|
if (/etc/systemd/system | path exists) {
|
||
|
|
return "systemd"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check for launchd (macOS)
|
||
|
|
if (/Library/LaunchAgents | path exists) {
|
||
|
|
return "launchd"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check for runit
|
||
|
|
if (/etc/sv | path exists) {
|
||
|
|
return "runit"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check for OpenRC
|
||
|
|
if (/etc/init.d | path exists) {
|
||
|
|
return "openrc"
|
||
|
|
}
|
||
|
|
|
||
|
|
error "No supported init system detected"
|
||
|
|
}
|