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