Initial commit.
This commit is contained in:
13
LICENSE
Normal file
13
LICENSE
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Copyright (c) 2012-2014, Jack L. Frost <fbt@fleshless.org>
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
68
README.md
Normal file
68
README.md
Normal file
@@ -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.
|
||||||
15
bash/completion/watchman
Normal file
15
bash/completion/watchman
Normal file
@@ -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
|
||||||
22
config.mk.sh
Normal file
22
config.mk.sh
Normal file
@@ -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"}
|
||||||
106
make.sh
Executable file
106
make.sh
Executable file
@@ -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 "$@"
|
||||||
14
services/init.d/cronpl
Executable file
14
services/init.d/cronpl
Executable file
@@ -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
|
||||||
|
}
|
||||||
18
services/init.d/named
Executable file
18
services/init.d/named
Executable file
@@ -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
|
||||||
|
}
|
||||||
24
services/init.d/openvpn
Executable file
24
services/init.d/openvpn
Executable file
@@ -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)
|
||||||
|
}
|
||||||
45
watchman-runas.bash
Normal file
45
watchman-runas.bash
Normal file
@@ -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 "$@"
|
||||||
29
watchman-service.bash
Normal file
29
watchman-service.bash
Normal file
@@ -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" "$@"
|
||||||
150
watchman-svc.bash
Normal file
150
watchman-svc.bash
Normal file
@@ -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 <pidfile>
|
||||||
|
-l <logfile>
|
||||||
|
-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 "$@"
|
||||||
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