ssm/sm

374 lines
6.8 KiB
Plaintext
Raw Normal View History

#!/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 $service_ready_flag
}
}; trap 'svc::cleanup' TERM INT
svc::reload() {
kill -n "$service_reload_signal" "$service_pid"
}; trap 'svc::reload' HUP
"$@" &
service_pid=$!
while nullexec kill -n 0 "$service_pid"; do
wait
done
}
#cat >/dev/null << EOF
## Respawn
respawn() {
respawn::cleanup() {
kill -n 15 $(jobs -p)
wait
exit 0
}; trap 'respawn::cleanup' TERM
while true; do
exec "$@" &
job_pid=$!
wait
done
}
#EOF
## 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*10) )) && return 1
sleep 0.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
}
## Simple timer
timer() {
declare cnt timeout=$1
shift
while ! "$@"; do
(( cnt >= (timeout*10) )) && return 1
sleep 0.1
(( cnt++ ))
done
return 0
}
## Is a service ready?
is_ready() [[ -f "$service_ready_flag" ]]
## Wait for this service to get ready
wait_ready() {
timer "$service_ready_timeout" is_ready
}
## Depend on other services to be started
depend() {
declare s
for s in "$@"; do
if ! "$0" "$s" qstatus; then
nullexec "$0" "$s" start || {
failed_deps+=( "$s" )
return 1
}
fi
done
}
## Depend on other services to be ready
depend_ready() {
declare s
depend "$@" || return 1
for s in "$@"; do
"$0" "$s" wait_ready || {
failed_deps+=( "$s" )
return 1
}
done
}
# Super functions
## Start the service, write down the svc pid
super_start() {
(( service_running )) && return 3
[[ -f "${service_command[0]}" ]] || return 9
depend "${service_depends[@]}" || return 7
depend_ready "${service_depends_ready[@]}" || return 7
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
if timer "$service_ready_timeout" ready; then
printf '1' > "$service_ready_flag"
else
return 5
fi
return 0
}
# A separate function for oneshot services
super_oneshot() {
(( service_enabled )) && return 3
}
## 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; }
reload() { super_reload; }
restart() {
"$0" "$service_name" stop
"$0" "$service_name" start
}
logs() { cat "$service_logfile"; }
## Status is a bit of a special case. It's talkative.
status() {
(( service_running )) && return 0
return 1
}
## For use in scripts
qstatus() { nullexec status; }
## By default there is no ready check
ready() { :; }
# 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_ready_timeout 15
default service_stop_signal 15
default service_reload_signal 1
default service_ready_flag "$rundir/$service_name.ready"
# 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; res=$?
case "$res" 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; res=$?
case "$res" in
0) printf 'ok.\n';;
3) printf 'already running.\n';;
5) printf 'readyness check timed out.\n';;
7) printf 'dependencies failed: %s.\n' "${failed_deps[@]}";;
9) printf 'service_command does not exist: %s.\n' "${service_command[0]}";;
*) printf 'fail.\n';;
esac
;;
reload)
printf 'Reloading %s... ' "$service_name"
reload; res=$?
case "$res" in
0) printf 'ok.\n';;
*) printf 'fail.\n';;
esac
;;
status)
status; res=$?
case "$res" in
0) printf '%s is running/enabled.\n' "$service_name";;
1) printf '%s is not running/enabled.\n' "$service_name";;
*) printf '%s: status unknown.\n' "$service_name";;
esac
;;
logs) logs;;
*) "$2"; res=$?;;
esac
(( res )) && return "$res"
# Run post_$action function
if is_function "post_$2"; then
"post_$2" || {
printf 'post_%s failed!\n' "$2"
die 15
}
fi
}
main "$@"