commit 9177854a278229392e5ceae7384ffaf99b588355 Author: Sergey Fedosov Date: Sat Sep 3 22:35:07 2016 +0300 Initial commit. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..94998e0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2012-2014, Jack L. Frost + + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without +fee is hereby granted, provided that the above copyright notice and this permission notice appear +in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +OF THIS SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..952222b --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +watchman +======== + +Yet another service manager. + +Installation +------------ +### Arch Linux: +#### Latest PKGBUILDS: + + * https://aur4.archlinux.org/packages/watchman-sm + * https://pkg.fleshless.org/watchman-sm/tree + +#### Manual: + +##### Linux: + git clone https://github.com/fbt/watchman.git + cd watchman + ./make.sh + ./make.sh install + +##### FreeBSD: +FreeBSD doesn't have /run as a tmpfs (or anything), so you would need to tell watchman where to keep runtime files + + git clone https://github.com/fbt/watchman.git + cd watchman + RUNDIR=/var/run/watchman ./make.sh + ./make.sh install + +##### Others: +I'e not personally tested watchman on any other systems. Someone told me it works on Solaris and OpenBSD. Test it and get in touch with me if you want. + +Services +-------- +A service is a script in the watchman's init.d directory. +By default it's /etc/watchman/init.d for system services and $HOME/.watchman/init.d for user ones. +Watchman creates relevant dirs if they don't exist. + +Variables +--------- +There are some variables you can use to alter a service's behaviour. +All of them are optional. Yes, I mean all of them. + + $service_command # The service's executable. Watchman will try to find the service in $PATH by the service name, so yes, + even this is optional. + $service_args # Arguments to $service_command + $service_respawn # Set this to restart the service if it exists. Only works with services that don't daemonize. + $service_name # It is useful to set $service_name explicitly if you use a symlink to refer to a service and want to use + both the original script and the symlink. + $service_workdir # The directory to cd to before running $service_command + $service_pidfile # The service's pid file. Will not be overwritten by watchman. By specifying this, you basically tell + watchman that the service mantains its pidfile itself. + $service_logfile # The file with $service_command's stdout/stderr. + $service_actions # Array. Available service actions. Should not be overriden, only added to. If you need to remove + an action, just do unset $action. + +Functions +--------- + +Standart functions: + + start() # Start the service. Calls watchman.start(). + stop() # Stop the service. Calls watchman.stop(). + restart() # Restart the service. Calls stop(), then start(). + reload() # Reload the service's configuration. Calls watchman.reload, which sends SIGHUP to the service. + status() # Show the service's status. Calls watchman.status(). + depends() # Starts the specified services. Calls watchman.depends. + logs() # Shows the service stdout log. Calls watchman.logs. diff --git a/bash/completion/watchman b/bash/completion/watchman new file mode 100644 index 0000000..1a21c4c --- /dev/null +++ b/bash/completion/watchman @@ -0,0 +1,15 @@ +_service_module() { + local cur + + COMPREPLY=() + cur=${COMP_WORDS[COMP_CWORD]} + + if [[ "$UID" == '0' ]]; then + IFS=$'\n' words=( /etc/rc.d/* ) + else + IFS=$'\n' words=( $HOME/.watchman/init.d/* ) + fi + + COMPREPLY=( $(compgen -W "${words[*]##*/}" -- "$cur") ) +} +complete -F _service_module service watchman diff --git a/config.mk.sh b/config.mk.sh new file mode 100644 index 0000000..520c6bf --- /dev/null +++ b/config.mk.sh @@ -0,0 +1,22 @@ +# build.sh config + +VERSION=v1.11.2 + +PREFIX=${PREFIX:-/opt} +USR=${USR:-"${PREFIX}usr"} + +BINDIR=${BINDIR:-"${DESTDIR}${USR}/bin"} + +CFGDIR=${CFGDIR:-"${PREFIX}etc/watchman"} +LOGDIR=${LOGDIR:-"/share/MD0_DATA/Public/logs/watchman"} +RUNDIR=${RUNDIR:-"${PREFIX}run/watchman"} + +CFGDIR_USER=${CFGDIR_USER:-"\$XDG_CONFIG_HOME/watchman"} +LOGDIR_USER=${LOGDIR_USER:-"\$HOME/.local/var/log/watchman"} +RUNDIR_USER=${RUNDIR_USER:-"\$XDG_RUNTIME_DIR/watchman/run"} + +INITDIRS="\"${CFGDIR}/init.d\" \"${CFGDIR_USER}/init.d\"" + +TOOLS=${TOOLS-"runas service svc"} + +BASH_PATH=${BASH_PATH:-"/bin/bash"} diff --git a/make.sh b/make.sh new file mode 100755 index 0000000..6dd36ac --- /dev/null +++ b/make.sh @@ -0,0 +1,106 @@ +#!/bin/sh +set -e + +# Functions +set_vars() { + printf 'Setting vars...\n' >&2 + + sed -r \ + -e "s%@VERSION@%${VERSION}%" \ + -e "s%@CFGDIR_USER@%${CFGDIR_USER}%" \ + -e "s%@RUNDIR_USER@%${RUNDIR_USER}%" \ + -e "s%@LOGDIR_USER@%${LOGDIR_USER}%" \ + -e "s%@CFGDIR@%${CFGDIR}%" \ + -e "s%@RUNDIR@%${RUNDIR}%" \ + -e "s%@LOGDIR@%${LOGDIR}%" \ + -e "s%@INITDIRS@%${INITDIRS}%" \ + -e "s%@BASH_PATH@%${BASH_PATH}%" +} + +do_tell() { + printf 'Executing: %s\n' "$*" + "$@" +} + +# Targets +target_watchman() { + target='watchman' + + printf '%s\n' "Building watchman..." + set_vars < "$target".bash > "$target" + do_tell chmod 755 "$target" + ls -l "$target" +} + +target_tools() { + if [ "$TOOLS" ]; then + do_tell mkdir -p tools + + for i in $TOOLS; do + printf '%s\n' "Building ${i}..." + set_vars < "watchman-${i}.bash" > "tools/watchman-$i" + do_tell chmod 755 "tools/watchman-$i" + done + + ls -l tools/* + fi +} + +target_clean() { + do_tell rm -f tools/* watchman build + if [ -d tools ]; then + do_tell rmdir tools + fi +} + +target_install() { + mkdir -p "$BINDIR" + + do_tell cp watchman "${BINDIR}/watchman" + do_tell chmod 755 "${BINDIR}/watchman" + + for i in $TOOLS; do + do_tell cp "tools/watchman-$i" "$BINDIR/watchman-$i" + do_tell chmod 755 "$BINDIR/watchman-$i" + done +} + +target_all() { + target_watchman + target_tools +} + +gitver() { + if last_tag=$( git describe --long --tags 2>/dev/null ); then + printf '%s\n' "$last_tag" | sed 's/\([^-]*-g\)/r\1/;s/-/./g' + else + printf "git-r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" + fi +} + +main() { + while [ -n "$1" ]; do + case "$1" in + *=*) export "${1%%=*}=${1#*=}";; + --) shift; break;; + *) break;; + esac + shift + done + + target="${1:-all}" + + # Build configuration + . './config.mk.sh' + + printf '%s\n' "Building target: $target" + + if [ -d .git ]; then + VERSION=$(gitver) + printf '%s\n' "Building from git, overriding version: $VERSION" + fi + + "target_$target" +} + +main "$@" diff --git a/services/init.d/cronpl b/services/init.d/cronpl new file mode 100755 index 0000000..3d483af --- /dev/null +++ b/services/init.d/cronpl @@ -0,0 +1,14 @@ +#!/opt/bin/watchman +# named init script for watchman + +ROOTDIR='/share/MD0_DATA/.qpkg/Optware' +service_command="${ROOTDIR}/etc/cron.pl" +service_respawn=true +service_logfile='/share/Public/logs/cron.pl.log' + +unset reload status + +start() { + while [ ! -x /opt/bin/perl ]; do sleep 1; done + watchman.start +} diff --git a/services/init.d/named b/services/init.d/named new file mode 100755 index 0000000..50165f3 --- /dev/null +++ b/services/init.d/named @@ -0,0 +1,18 @@ +#!/opt/bin/watchman +# named init script for watchman + +ROOTDIR='/share/MD0_DATA/.qpkg/Optware' +service_command="${ROOTDIR}/etc/named/named.fucked" +service_respawn=true +service_logfile='/share/Public/logs/named/named.log' + +unset reload + +start() { + while [ ! -x /opt/sbin/named ]; do sleep 1; done + watchman.start +} + +status() { + /opt/sbin/rndc -s 127.0.0.1 status +} diff --git a/services/init.d/openvpn b/services/init.d/openvpn new file mode 100755 index 0000000..a532cb9 --- /dev/null +++ b/services/init.d/openvpn @@ -0,0 +1,24 @@ +#!/opt/bin/watchman + +# openvpn init script for watchman + +ROOTDIR='/share/MD0_DATA/.qpkg/Optware' +service_command="/usr/sbin/openvpn" +service_respawn=true +service_args=(${ROOTDIR}/etc/openvpn/dia/dia.ovpn) +#service_pidfile="${ROOTDIR}/var/run/openvpndia.pid" +service_logfile='/share/Public/logs/openvpn/openvpndia.log' + +unset reload + +start(){ + while [ ! -e /lib/modules/others/tun.ko ]; do sleep 1; done + while [ ! -x /usr/sbin/openvpn ]; do sleep 1; done + while [ ! -e ${ROOTDIR}/etc/openvpn/dia/dia.ovpn ]; do sleep 1; done + while [ $(lsmod|grep tun|wc -l) != 1 ]; do insmod /lib/modules/others/tun.ko; sleep 1; done + watchman.start +} + +status() { + kill -USR2 $(< $service_pidfile) +} diff --git a/watchman-runas.bash b/watchman-runas.bash new file mode 100644 index 0000000..af547cb --- /dev/null +++ b/watchman-runas.bash @@ -0,0 +1,45 @@ +#!@BASH_PATH@ + +watchman_version='@VERSION@' + +err() { printf '%s\n' "$1" >&2; } + +runas::main() { + local job_username + + while [[ "$1" ]]; do + case "$1" in + (-u) job_username="$2"; shift;; + (-h) runas::usage;; + + (-v) + printf '%s\n' "$watchman_version" + exit 0 + ;; + + (-*) + err "Unknown option: $1" + runas::usage + return 1 + ;; + + (*) break;; + esac + shift + done + + [[ "$UID" -eq 0 ]] || { + err "Only root is allowed to runas!" + return 1 + } + + (( $# )) || { + err "No command supplied" + runas::usage + return 1 + } + + exec su "$job_username" -s "$BASH" -c "$*" +} + +runas::main "$@" diff --git a/watchman-service.bash b/watchman-service.bash new file mode 100644 index 0000000..cb560aa --- /dev/null +++ b/watchman-service.bash @@ -0,0 +1,29 @@ +#!@BASH_PATH@ + +watchman_version='@VERSION@' + +# Check if XDG_ stuff is set +XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-"/run/user/$UID"} +XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-"$HOME/.config"} + +[[ "$1" == '-v' ]] && { + printf '%s\n' "$watchman_version" + exit 0 +} + +cfg_init_dirs=( @INITDIRS@ ) + +service="$1"; shift + +for i in "${cfg_init_dirs[@]}"; do + [[ -f "${i}/${service}" ]] && { + service_script="${i}/${service}" + } +done + +[[ "$service_script" ]] || { + printf '%s\n' "Service $service not found." + exit 1 +} + +"$service_script" "$@" diff --git a/watchman-svc.bash b/watchman-svc.bash new file mode 100644 index 0000000..fceda59 --- /dev/null +++ b/watchman-svc.bash @@ -0,0 +1,150 @@ +#!@BASH_PATH@ + +watchman_version='@VERSION@' + +msg() { printf '%s\n' "$*"; } +err() { printf '%s\n' "$*" >&2; } + +usage() { + while read; do msg "$REPLY"; done <<- 'EOF' + Usage: watchman-svc <-p pidfile> [options] command + Flags: + -p + -l + -r # Respawn the process if it dies. + -h # Show this message. + -v # Show version. + EOF +} + +cleanup() { + for i in "$respawn_pid" "$job_pid"; do + kill_wait "$i" || { + err "Failed to stop child: $i" + exit 13 + } + done + + rm -f "$job_pidfile" +} + +respawn_cleanup() { + kill_wait "$job_pid" || { + err "Failed to stop child: $i" + exit 13 + } + + return 0 +} + +respawn_passthrough() { + kill -"$1" "$job_pid" +} + +kill_wait() { + kill "$1" &>/dev/null + + counter=0 + while kill -0 "$1" &>/dev/null; do + sleep 1 + (( counter >= 10 )) && { return 1; } + (( counter++ )) + done + + return 0 +} + +job_run() { + "${cmd[@]}" >&3 2>&3 & job_pid=$! + export job_pid + return 0 +} + + +job_respawn() { + trap 'respawn_cleanup; return' INT TERM + + trap 'respawn_passthrough 1' HUP + trap 'respawn_passthrough 10' USR1 + trap 'respawn_passthrough 12' USR2 + + while job_run; do + last_run=$SECONDS + if kill -0 "$job_pid"; then + wait "$job_pid" + fi + + curtime=$SECONDS + if (( last_run + 1 > ( curtime - 2 ) )); then + err "The job exists right as we start it. Bailing." + return 1 + fi + done +} + +job_wait() { + trap cleanup INT TERM + + if (( flag_respawn )); then + job_respawn & respawn_pid=$! + + printf '%s' "$respawn_pid" > "$job_pidfile" + + wait "$respawn_pid" + else + job_run + + printf '%s' "$job_pid" > "$job_pidfile" + + wait "$job_pid" + fi + + cleanup +} + +main() { + while (( $# )); do + case "$1" in + (-h) usage; return 0;; + + (-p) job_pidfile="$2"; shift;; + (-l) job_logfile="$2"; shift;; + + (-r) flag_respawn=1;; + + (-v) + printf '%s\n' "$watchman_version" + exit 0 + ;; + + (--) shift; break;; + (-*) + err "Unknown key: $1" + usage + return 1 + ;; + + (*) break;; + esac + shift + done + + if [[ -z "$job_pidfile" ]]; then + usage + return 1 + fi + + cmd=( "$@" ) + + if [[ "$job_logfile" ]]; then + exec 3>"$job_logfile" + else + exec 3>&1 + fi + + job_wait & + + return 0 +} + +main "$@" diff --git a/watchman.bash b/watchman.bash new file mode 100644 index 0000000..0b9b5bb --- /dev/null +++ b/watchman.bash @@ -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] + 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 "$@"