265 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			265 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
| #!/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 "$@"
 |