#!/usr/bin/env bash shopt -s nullglob # Utility functions ## Make setting default values a bit less awkward default() { declare -n _p=$1 if ! [[ "$_p" ]]; then _p=$2 fi } ## Die. Why not? die() { declare code=${1:-0} [[ "$2" ]] && printf '%s\n' "$2" exit "$code" } ## Run the command and wait for it to die svc() { svc::cleanup() { kill -n "$service_stop_signal" "$service_pid" pid_wait "$service_pid" && { rm -f $svc_pidfile } }; trap 'svc::cleanup' TERM INT svc::reload() { kill -n "$service_reload_signal" "$service_pid" }; trap 'svc::reload' HUP exec "$@" & service_pid=$! wait } ## Respawn implementation respawn() { declare jpid respawn::cleanup() { kill -n 15 0 wait exit 0 }; trap 'svc::reload' TERM while true; do "$@" & jpid=$! wait done } ## Run a command with its output discarded nullexec() { "$@" &>/dev/null } ## Wait for a pid to die pid_wait() { declare cnt=0 while nullexec kill -0 "$1"; do (( cnt >= service_stop_timeout )) && return 1 sleep 1 (( cnt++ )) done return 0 } ## See if NAME is a function is_function() { declare name=$1 name_tupe name_type=$( type -t "$name" ) if [[ $name_type == 'function' ]]; then return 0 fi return 1 } # DSL depends() { service_depends=( "$@" ) } # Super functions ## Start the service, write down the svc pid super_start() { (( service_running )) && return 3 if (( service_managed )); then if (( service_respawn )); then svc respawn "${service_command[@]}" &>"$service_logfile" & else svc "${service_command[@]}" &>"$service_logfile" & fi svc_pid=$! printf '%s' "$svc_pid" > "$svc_pidfile" else "${service_command[@]}" & fi return 0 } ## Reload the service ## Usually just sends HUP super_reload() { (( service_running )) || return 3 kill -n 1 "$service_pid" } ## Stop the service ## Returns: ## 3: Service is not running. super_stop() { (( service_running )) || return 3 nullexec kill -n "$service_stop_signal" "$service_pid" || return 1 pid_wait "$service_pid" || return 5 return 0 } # Overloadable functions start() { super_start; } stop() { super_stop; } restart() { "$0" "$service_name" stop "$0" "$service_name" start } # Code main() { # Let's set some defaults service_managed=1 if (( $UID )); then # XDG stuff default XDG_CONFIG_HOME "$HOME/.config" default XDG_RUNTIME_DIR "/run/user/$UID" cfgdir="$XDG_CONFIG_HOME/sm" rundir="$XDG_RUNTIME_DIR/sm" logdir="$HOME/log/sm" else cfgdir='/etc/sm' rundir='/run/sm' logdir='/var/log/sm' fi # Load custom functions for f in "$cfgdir/functions"/*; do source "$f" || die 9 "Failed to source functions from $f" done # Now create the needed runtime stuff for d in "$rundir" "$logdir"; do mkdir -p "$d" || die 3 "Failed to create runtime dir: $d" done # service_name is just $1 service_name=$1 # Semi-hardcoded stuff svc_pidfile="$rundir/$service_name.pid" # Get the service defaults [[ -f "$cfgdir/conf.d/$1" ]] && { source "$cfgdir/conf.d/$1" || die 5 "Failed to read service defaults: $cfgdir/conf.d/$1" } # Get the service config source "$cfgdir/init.d/$1" || die 7 "Failed to read the service config: $cfgdir/init.d/$1" # Legacy [[ "$service_args" ]] && service_command=( "${service_command[@]}" "${service_args[@]}" ) [[ "$service_respawn" == 'true' ]] && service_respawn=1 [[ "$service_pidfile" ]] && service_managed=0 if ! (( service_managed )); then (( service_respawn )) && die 21 "Refusing to respawn a service that manages itself." fi # Service-level defaults default service_pidfile "$svc_pidfile" default service_logfile "$logdir/$service_name.log" default service_stop_timeout 30 default service_stop_signal 15 default service_reload_signal 1 # Let's see if there's a PID if [[ -f "$service_pidfile" ]]; then service_pid=$(<$service_pidfile) # Let's see if it's running if nullexec kill -0 "$service_pid"; then service_running=1 fi fi # Check if action is even defined is_function "$2" || die 17 "Function $2 is not defined for $service_name." # Run pre_$action function if is_function "pre_$2"; then "pre_$2" || { printf 'pre_%s failed!\n' "$2" || die 13 } fi # Run the function case "$2" in stop) printf 'Stopping %s... ' "$service_name" stop case "$?" in 0) printf 'ok.\n';; 3) printf 'not running.\n' "$service_name";; 5) printf 'timed out.\n';; *) printf 'fail.\n';; esac ;; start) printf 'Starting %s... ' "$service_name" start case "$?" in 0) printf 'ok.\n';; 3) printf 'already running.\n';; *) printf 'fail.\n';; esac ;; *) "$2";; esac # Run post_$action function if is_function "post_$2"; then "post_$2" || { printf 'post_%s failed!\n' "$2" || die 15 } fi } main "$@"