#!@BASH_PATH@ # Config watchman_version='@VERSION@' # Functions watchman.usage() { while read -r; do printf '%s\n' "$REPLY"; done <<- EOF Usage: watchman [flags] 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 "$@"