Initial commit.
This commit is contained in:
553
watchman.bash
Normal file
553
watchman.bash
Normal file
@@ -0,0 +1,553 @@
|
||||
#!@BASH_PATH@
|
||||
|
||||
# Config
|
||||
watchman_version='@VERSION@'
|
||||
|
||||
# Functions
|
||||
watchman.usage() {
|
||||
while read -r; do printf '%s\n' "$REPLY"; done <<- EOF
|
||||
Usage: watchman [flags] <service> <action>
|
||||
Flags:
|
||||
-h, --help Show this message.
|
||||
-v, --version Show watchman's version.
|
||||
-d, --verbose Verbose output.
|
||||
EOF
|
||||
}
|
||||
|
||||
watchman.msg() { printf '%s\n' "[watchman] $1"; }
|
||||
watchman.err() { watchman.msg "(error) $1"; }
|
||||
watchman.debug() {
|
||||
[[ "$flag_verbose" ]] && {
|
||||
watchman.msg "{v} $1"
|
||||
}
|
||||
}
|
||||
|
||||
watchman.init() {
|
||||
# Check if XDG_ stuff is set
|
||||
XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-"/run/user/$UID"}
|
||||
XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-"$HOME/.config"}
|
||||
|
||||
# Check if the user is root.
|
||||
if (( UID )); then
|
||||
cfg_dir="@CFGDIR_USER@"
|
||||
cfg_rundir="@RUNDIR_USER@"
|
||||
cfg_logdir="@LOGDIR_USER@"
|
||||
else
|
||||
cfg_dir="@CFGDIR@"
|
||||
cfg_rundir="@RUNDIR@"
|
||||
cfg_logdir="@LOGDIR@"
|
||||
fi
|
||||
|
||||
[[ -f "${cfg_dir}/watchman.conf" ]] && {
|
||||
source "${cfg_dir}/watchman.conf" || {
|
||||
watchman.err "Failed to source config: ${cfg_dir}/watchman.conf. Fix it to proceed."
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
cfg_initdir="${cfg_dir}/init.d"
|
||||
cfg_init_dirs+=( "$cfg_dir"/{init.d,conf.d} "$cfg_rundir" "$cfg_logdir" )
|
||||
|
||||
for i in "${cfg_init_dirs[@]}"; do
|
||||
[[ -e "$i" ]] || {
|
||||
watchman.debug "$i does not exist, creating..."
|
||||
mkdir -p "$i" || {
|
||||
watchman.err "Can't create $i!"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
watchman.done() {
|
||||
>"$cfg_rundir/${service_name}.done" || {
|
||||
watchman.msg "Warning! Can't mark ${service_name} as enabled."
|
||||
}
|
||||
}
|
||||
|
||||
watchman.spawn() {
|
||||
[[ "$service_command" ]] || {
|
||||
watchman.err "Please specify what to run with \$service_command"
|
||||
}
|
||||
|
||||
if [[ "$service_respawn" == 'true' ]]; then
|
||||
[[ "$service_type" == 'oneshot' ]] && {
|
||||
watchman.err "A oneshot service can not be respawned. Choose one or the other."
|
||||
return 1
|
||||
}
|
||||
|
||||
if [[ "${service_pidfile}" ]]; then
|
||||
watchman.err "Respawning forking services is not supported!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
watchman-svc -p "$service_pidfile_int" -l "$service_logfile" -r "$service_command" "${service_args[@]}"
|
||||
else
|
||||
if [[ "$service_type" == 'oneshot' ]]; then
|
||||
"${service_command}" "${service_args[@]}" 1>>"$service_logfile" 2>>"$service_logfile" || { return 1; }
|
||||
else
|
||||
watchman-svc -p "$service_pidfile_int" -l "$service_logfile" "$service_command" "${service_args[@]}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
watchman.tempfiles() {
|
||||
# Create temporary files
|
||||
for f in "${service_tmpfiles[@]}"; do
|
||||
IFS=':' read -r f_path f_type f_args <<< "$f"
|
||||
|
||||
if ! [[ -e $f_path ]]; then
|
||||
case "$f_type" in
|
||||
symlink)
|
||||
f_target=$f_args
|
||||
ln -s "$f_target" "$f_path"
|
||||
;;
|
||||
|
||||
file|dir)
|
||||
IFS=':' read -r f_perms f_owner f_group <<< "$f_args"
|
||||
|
||||
if [[ $f_type == 'file' ]]; then
|
||||
> "$f_path"
|
||||
chmod "${f_perms:-644}" "$f_path"
|
||||
else
|
||||
mkdir -p -m "${f_perms:-755}" "$f_path"
|
||||
fi
|
||||
|
||||
if [[ "$f_owner" || "$f_group" ]]; then
|
||||
chown "${f_owner:-root}:${f_group:-root}" "$f_path"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
watchman.start() {
|
||||
local try
|
||||
|
||||
watchman.status && {
|
||||
if [[ "$service_type" == 'oneshot' ]]; then
|
||||
watchman.err "$service_name is already enabled"
|
||||
else
|
||||
watchman.err "$service_name is already running"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Start dependencies
|
||||
for d in "${service_depends[@]}"; do
|
||||
IFS=':' read -r d_name d_type <<< "$d"
|
||||
|
||||
"$0" "$d_name" start || {
|
||||
err "Failed to start service dependency: $d_name"
|
||||
|
||||
if ! [[ $d_type == 'soft' ]]; then
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
done
|
||||
|
||||
# pre-start
|
||||
run_if_function pre_start || { return $?; }
|
||||
|
||||
[[ "$service_workdir" ]] && { cd "$service_workdir"; }
|
||||
|
||||
>"$service_logfile" 2>/dev/null || {
|
||||
watchman.msg "Warning! Logfile is not writeable! Defaulting to /dev/null"
|
||||
service_logfile='/dev/null'
|
||||
}
|
||||
|
||||
watchman.tempfiles
|
||||
|
||||
spawn 1>>"$service_logfile" 2>>"$service_logfile"
|
||||
spawn_result="$?"
|
||||
|
||||
case "$service_type" in
|
||||
(oneshot)
|
||||
(( spawn_result )) && {
|
||||
watchman.err "Failed to enable ${service_name}!"
|
||||
return 1
|
||||
}
|
||||
|
||||
watchman.done
|
||||
watchman.msg "$service_name enabled"
|
||||
;;
|
||||
|
||||
(*)
|
||||
if [[ "$service_pidfile" ]]; then
|
||||
if [[ ! -f "$service_pidfile" ]]; then
|
||||
watchman.msg "Making sure the process had time to wite its pidfile..."
|
||||
try=0; while [[ ! -f "$service_pidfile" && "$try" -lt "${service_spawn_delay:-3}" ]]; do
|
||||
((try++))
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
if ! [[ -f "$service_pidfile" ]]; then
|
||||
return 3
|
||||
fi
|
||||
fi
|
||||
|
||||
watchman.msg "$service_name started"
|
||||
|
||||
run_if_function post_start || { return $?; }
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
watchman.depends() {
|
||||
[[ "$action" == 'start' ]] || { return 0; }
|
||||
|
||||
for s in "$@"; do
|
||||
"$0" "$s" status || {
|
||||
watchman.msg "Starting ${s} as ${service_name} depends on it."
|
||||
"$0" "$s" start || {
|
||||
watchman.err "Starting $s failed, aborting."
|
||||
return 31
|
||||
}
|
||||
}
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
watchman.pid_check() { kill -0 "$1" 2>/dev/null; }
|
||||
|
||||
watchman.pid_wait() {
|
||||
counter='0'
|
||||
printf "[watchman] Waiting for PID to exit: %s..." "$1"
|
||||
|
||||
while watchman.pid_check "$1"; do
|
||||
((counter++))
|
||||
|
||||
[[ "$counter" -ge "${service_stop_timeout:-30}" ]] && {
|
||||
printf 'timeout\n'
|
||||
return 1
|
||||
}
|
||||
|
||||
sleep 1
|
||||
printf '.'
|
||||
done
|
||||
|
||||
printf '\n'
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
watchman.stop() {
|
||||
watchman.status || {
|
||||
watchman.err "$service_name doesn't seem to be running"
|
||||
return 1
|
||||
}
|
||||
|
||||
run_if_function pre_stop || { return $?; }
|
||||
|
||||
case "$service_type" in
|
||||
(oneshot)
|
||||
watchman.msg "Removing $cfg_rundir/${service_name}.done file..."
|
||||
rm "$cfg_rundir/${service_name}.done"
|
||||
;;
|
||||
|
||||
(*)
|
||||
kill "$service_pid"
|
||||
|
||||
watchman.pid_wait "$service_pid" || { return 3; }
|
||||
|
||||
rm -f "$service_pidfile" || {
|
||||
watchman.err "Failed to remove pidfile: $service_pidfile"
|
||||
}
|
||||
;;
|
||||
esac
|
||||
|
||||
run_if_function post_stop || { return $?; }
|
||||
}
|
||||
|
||||
watchman.status() {
|
||||
case "$service_type" in
|
||||
(oneshot)
|
||||
if [[ -e "$cfg_rundir/${service_name}.done" ]]; then
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
|
||||
(*)
|
||||
if [[ "$service_pid" ]]; then
|
||||
if kill -0 "$service_pid" &>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
watchman.service_usage() {
|
||||
local valid_actions
|
||||
|
||||
for i in "${service_actions[@]}"; do
|
||||
if is_function "$i"; then
|
||||
valid_actions+="$i|";
|
||||
fi
|
||||
done
|
||||
|
||||
watchman.msg "Usage: $service_name <${valid_actions%|*}>"
|
||||
}
|
||||
|
||||
watchman.service_get_pid() {
|
||||
local _pid
|
||||
|
||||
if [[ "$service_pidfile" ]]; then
|
||||
if [[ -f "$service_pidfile" ]]; then
|
||||
_pid=$(<"$service_pidfile")
|
||||
fi
|
||||
else
|
||||
if [[ -f "$service_pidfile_int" ]]; then
|
||||
_pid=$(<"$service_pidfile_int")
|
||||
fi
|
||||
fi
|
||||
|
||||
printf '%s' "$_pid"
|
||||
}
|
||||
|
||||
watchman.reload() {
|
||||
watchman.status || {
|
||||
watchman.err "$service_name is not running"
|
||||
return 1
|
||||
}
|
||||
|
||||
watchman.msg "Reloading ${service_name}..."
|
||||
kill -1 "$service_pid"
|
||||
}
|
||||
|
||||
watchman.logs() {
|
||||
if [[ -f "$service_logfile" ]]; then
|
||||
watchman.msg "Output for ${service_name}:"
|
||||
while read -r; do
|
||||
printf '%s\n' "$REPLY"
|
||||
done < "$service_logfile"
|
||||
else
|
||||
watchman.err "No log available for ${service_name}"
|
||||
fi
|
||||
|
||||
if [[ "$service_respawn" == 'true' ]]; then
|
||||
if [[ -f "$cfg_logdir/${service_name}_respawn.log" ]]; then
|
||||
watchman.msg "Output for ${service_name} (watchdog):"
|
||||
while read -r; do
|
||||
printf '%s\n' "$REPLY"
|
||||
done < "$cfg_logdir/${service_name}_respawn.log"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
watchman.edit() {
|
||||
if ! [[ "$EDITOR" ]]; then
|
||||
EDITOR='vim'
|
||||
fi
|
||||
|
||||
"$EDITOR" "$service_script"
|
||||
}
|
||||
|
||||
watchman.args() {
|
||||
while [[ "$1" ]]; do
|
||||
case "$1" in
|
||||
(-h) watchman.usage; exit;;
|
||||
(-d) flag_verbose='1';;
|
||||
(-v) printf '%s\n' "$watchman_version"; exit;;
|
||||
|
||||
(--) shift; break;;
|
||||
|
||||
(-*)
|
||||
watchman.err "Invalid key: $1"
|
||||
watchman.usage
|
||||
return 1
|
||||
;;
|
||||
|
||||
(*) args+=( "$1" );;
|
||||
esac; shift
|
||||
done
|
||||
}
|
||||
|
||||
nullerr() {
|
||||
"$@" 2>/dev/null
|
||||
}
|
||||
|
||||
is_variable() {
|
||||
declare param_args param=$1
|
||||
|
||||
read -r _ param_args _ <<< $( nullerr declare -p "$param" )
|
||||
|
||||
if (( $? )); then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$param_args" == '--' ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
is_function() {
|
||||
declare word=$1 word_type
|
||||
|
||||
word_type=$( type -t "$word" )
|
||||
|
||||
if [[ $word_type == 'function' ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
run_if_function() {
|
||||
declare func=$1; shift
|
||||
|
||||
if is_function "$func"; then
|
||||
"$func" "$@"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
spawn() { watchman.spawn; }
|
||||
|
||||
start() { watchman.start; }
|
||||
stop() { watchman.stop; }
|
||||
restart() { stop; start; }
|
||||
reload() { watchman.reload; }
|
||||
edit() { watchman.edit; }
|
||||
|
||||
status() {
|
||||
local service_status
|
||||
|
||||
watchman.status && {
|
||||
service_status='running'
|
||||
}
|
||||
|
||||
if [[ "$service_status" ]]; then
|
||||
[[ "$service_type" == 'oneshot' ]] && {
|
||||
watchman.msg "$service_name is enabled"
|
||||
} || {
|
||||
watchman.msg "$service_name is running ($service_pid)"
|
||||
}
|
||||
|
||||
return 0
|
||||
else
|
||||
watchman.msg "$service_name is down"
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
logs() { watchman.logs; }
|
||||
|
||||
depends() { watchman.depends "$@"; }
|
||||
|
||||
watchman.main() {
|
||||
# Parse arguments
|
||||
watchman.args "$@" || { return "$?"; }
|
||||
set -- "${args[@]}"
|
||||
|
||||
watchman.init "$@" || { return "$?"; }
|
||||
|
||||
# Exit on no arguments
|
||||
(( $# )) || {
|
||||
watchman.usage
|
||||
return 9
|
||||
}
|
||||
|
||||
# Set default service actions.
|
||||
service_actions=( 'start' 'stop' 'restart' 'reload' 'status' )
|
||||
|
||||
# Set $action and exit immediately if it's not set.
|
||||
action="$2"
|
||||
[[ "$action" ]] || {
|
||||
watchman.err 'Syntax error: no action specified.'
|
||||
watchman.service_usage
|
||||
return 1
|
||||
}
|
||||
|
||||
# Basename for poor people.
|
||||
service_name="${1##*/}"
|
||||
|
||||
# Check if the first argument is an existing file,
|
||||
# set $service_script to it if it is. Otherwise, search
|
||||
# for the service in the initdir.
|
||||
if [[ -f "$1" ]]; then
|
||||
service_script="$1"
|
||||
else
|
||||
if [[ -f "${cfg_initdir}/${service_name}" ]]; then
|
||||
service_script="${cfg_initdir}/${service_name}"
|
||||
else
|
||||
watchman.err "$service_name doesn't exist in $cfg_initdir"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Source the script
|
||||
source "$service_script" || { return 13; }
|
||||
|
||||
# check if the command exists and is an executable file.
|
||||
[[ "$service_command" ]] && {
|
||||
[[ -e "${service_command}" ]] || {
|
||||
watchman.err "Service command for ${service_name} is set but does not exist: ${service_command}"
|
||||
return 1
|
||||
}
|
||||
|
||||
[[ -f "${service_command}" ]] || {
|
||||
watchman.err "Service command for ${service_name} is set but is not a file: ${service_command}"
|
||||
return 1
|
||||
}
|
||||
|
||||
[[ -x "${service_command}" ]] || {
|
||||
watchman.err "Service command for ${service_name} is set but is not executable: ${service_command}"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
# Source the additional service configuration if that exists.
|
||||
[[ -f "${cfg_dir}/conf.d/${service_name}.sh" ]] && {
|
||||
source "${cfg_dir}/conf.d/${service_name}.sh" || {
|
||||
return $?
|
||||
}
|
||||
}
|
||||
|
||||
# If $service_args is a variable, convert it into an array
|
||||
if is_variable service_args; then
|
||||
watchman.err "\$service_args for the $service_name service is a variable."
|
||||
watchman.err "Since watchman 1.11.1, \$service_args is supposed to be an array."
|
||||
watchman.err "Please fix it in the service definition."
|
||||
|
||||
read -r -a service_args <<< "$service_args"
|
||||
fi
|
||||
|
||||
# Set default service_logfile and internal pidfile
|
||||
# These need to be here so that services behave as expected if $service_name is explicitly set.
|
||||
[[ "$service_logfile" ]] || { service_logfile="$cfg_logdir/${service_name}.log"; }
|
||||
service_pidfile_int="$cfg_rundir/${service_name}.pid" # Does not need to be reassignable. Seriously. Don't.
|
||||
|
||||
# Try to get the service's pid.
|
||||
service_pid="$(watchman.service_get_pid)"
|
||||
|
||||
# If the action is defined, do it. If not, fail gracefully.
|
||||
if is_function "$action"; then
|
||||
"$action"
|
||||
|
||||
action_rcode="$?"
|
||||
(( action_rcode > 1 )) && {
|
||||
watchman.err "Function $action failed for ${service_name}."
|
||||
}
|
||||
|
||||
return "$action_rcode"
|
||||
else
|
||||
watchman.err "Function $action is not defined for ${service_name}."
|
||||
return 17
|
||||
fi
|
||||
}
|
||||
|
||||
# Main
|
||||
watchman.main "$@"
|
||||
Reference in New Issue
Block a user