Compare commits
20 Commits
Author | SHA1 | Date |
---|---|---|
Jack L. Frost | 7c584eb8ee | |
Jack L. Frost | 608e7d4d9d | |
Jack L. Frost | be3d90ea79 | |
Jack L. Frost | c5bbf92022 | |
Jack L. Frost | bba7eef3d9 | |
Jack L. Frost | a0ef06810c | |
Jack L. Frost | a47b00f665 | |
Jack L. Frost | b67fec6f16 | |
Jack L. Frost | c6bba36f94 | |
Jack L. Frost | d07fd3362b | |
Jack L. Frost | e948f6985a | |
Jack L. Frost | 0d95965b5b | |
Jack L. Frost | e433bdccdd | |
Jack L. Frost | a4bd260bfe | |
Jack L. Frost | 7afeb29bf2 | |
Jack L. Frost | 924324aa10 | |
Jack L. Frost | 2aff1db42b | |
Jack L. Frost | 0dd00077a5 | |
Jack L. Frost | e227235f46 | |
Jack L. Frost | 5506a922c6 |
|
@ -8,6 +8,9 @@ Services
|
||||||
A service is a script in the ssm's init.d directory.
|
A service is a script in the ssm's init.d directory.
|
||||||
By default it's /etc/ssm/services for system services and $XDG_CONFIG_HOME/ssm/services for user ones.
|
By default it's /etc/ssm/services for system services and $XDG_CONFIG_HOME/ssm/services for user ones.
|
||||||
|
|
||||||
|
Note that they are BASH scripts that get sourced by `ssm`. Same pitfalls apply.
|
||||||
|
Hopefully, you won't have to do complex logic in the services.
|
||||||
|
|
||||||
## Global settings
|
## Global settings
|
||||||
|
|
||||||
* `cgroups = 0` — Enable cgroup-related features.
|
* `cgroups = 0` — Enable cgroup-related features.
|
||||||
|
@ -25,8 +28,8 @@ Optional settings (incomplete list):
|
||||||
* `service_pidfile` — If the service manages its own pidfile, set this.
|
* `service_pidfile` — If the service manages its own pidfile, set this.
|
||||||
* `service_pidfile_timeout = 15` — How long to wait for the service to create its pidfile.
|
* `service_pidfile_timeout = 15` — How long to wait for the service to create its pidfile.
|
||||||
* `service_pidfile_remove_stale = yes` — Remove stale pidfiles before spawning the service process.
|
* `service_pidfile_remove_stale = yes` — Remove stale pidfiles before spawning the service process.
|
||||||
* `service_logfile_out = $logdir/$service_name.log` — Logfile for output.
|
* `service_logfile_out = "$logdir/$service_name.log"` — Logfile for output.
|
||||||
* `service_logfile_err = $service_logfile_out` — Logfile for stderr.
|
* `service_logfile_err = "$service_logfile_out"` — Logfile for stderr.
|
||||||
* `service_stop_timeout = 30` — How long to wait after sending the stop command.
|
* `service_stop_timeout = 30` — How long to wait after sending the stop command.
|
||||||
* `service_ready_timeout = 15` — How long to wait till the service is ready, in seconds.
|
* `service_ready_timeout = 15` — How long to wait till the service is ready, in seconds.
|
||||||
* `service_stop_signal = 15` — Which signal to send to the service when stopping.
|
* `service_stop_signal = 15` — Which signal to send to the service when stopping.
|
||||||
|
@ -40,3 +43,5 @@ Optional settings (incomplete list):
|
||||||
* `service_success_exit = 0` — Array. Which exit codes to treat as successful termination. Only works for managed services (With no custom pidfile).
|
* `service_success_exit = 0` — Array. Which exit codes to treat as successful termination. Only works for managed services (With no custom pidfile).
|
||||||
* `service_oneshot = no` — The service is supposed to do something and die instead of daemonizing.
|
* `service_oneshot = no` — The service is supposed to do something and die instead of daemonizing.
|
||||||
* `service_signals_passthru` — Array, empty by default. Which signals should the svc pass directly to the service mainpid. This is dangerous, use with caution.
|
* `service_signals_passthru` — Array, empty by default. Which signals should the svc pass directly to the service mainpid. This is dangerous, use with caution.
|
||||||
|
|
||||||
|
Mind your expansions.
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/env ssm
|
||||||
|
|
||||||
|
service_oneshot = true
|
||||||
|
service_command = /usr/bin/false
|
||||||
|
|
||||||
|
service::pre_start() {
|
||||||
|
cat <<- EOF
|
||||||
|
Please don't. Use ferm or any other wrapper.
|
||||||
|
You will just reinvent one in here anyway.
|
||||||
|
EOF
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env ssm
|
||||||
|
|
||||||
|
service_respawn = on-failure
|
||||||
|
service_command = /usr/bin/nginx
|
||||||
|
service_pidfile = /run/nginx.pid
|
||||||
|
|
||||||
|
# Do not reload the service if the config test fails.
|
||||||
|
pre_reload() { "$service_command" -t "$@"; }
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env ssm
|
||||||
|
|
||||||
|
service_type = oneshot
|
||||||
|
service_command = /etc/rc.local
|
||||||
|
|
||||||
|
pre_start() {
|
||||||
|
# Do nothing, successfully, if /etc/rc.local is not executable.
|
||||||
|
[[ -x "/etc/rc.local" ]] || die 0
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/env ssm
|
||||||
|
|
||||||
|
service_respawn = always
|
||||||
|
service_command = /usr/bin/sshd -D -f "/etc/ssh/sshd_config"
|
||||||
|
|
||||||
|
pre_start() {
|
||||||
|
if ! [[ -e "/etc/ssh/ssh_host_key" ]]; then
|
||||||
|
ssh-keygen -A
|
||||||
|
fi
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/usr/bin/env ssm
|
||||||
|
|
||||||
|
# This trick was neat back in 2011, I swear
|
||||||
|
var tinc_network = "${service_name##*-}"
|
||||||
|
|
||||||
|
service_respawn = on-failure
|
||||||
|
service_command = /usr/bin/tincd -D -n "$tinc_network"
|
|
@ -0,0 +1,37 @@
|
||||||
|
# This is actually a bash script
|
||||||
|
|
||||||
|
# Where to search for services, works as a PATH-like array.
|
||||||
|
#service_path = "$XDG_CONFIG_HOME/ssm/services" /etc/ssm/services "$rundir/services" /usr/share/ssm/services
|
||||||
|
|
||||||
|
# Service defaults
|
||||||
|
# Respawn the service if it exits
|
||||||
|
#service_respawn = 0
|
||||||
|
|
||||||
|
# Working directory
|
||||||
|
#service_workdir = '/'
|
||||||
|
|
||||||
|
# How long do we wait for the service to stop
|
||||||
|
#service_stop_timeout = 30
|
||||||
|
|
||||||
|
# How long do we wait for the service to get ready
|
||||||
|
#service_ready_timeout = 15
|
||||||
|
|
||||||
|
# The signal to send to reload the service
|
||||||
|
#service_reload_signal = 1
|
||||||
|
|
||||||
|
# The signal to send to stop the service
|
||||||
|
#service_stop_signal = 15
|
||||||
|
|
||||||
|
# Enable cgroup-related functions
|
||||||
|
# Only works with cgroups v2 and a helper.
|
||||||
|
# Basically, don't.
|
||||||
|
#
|
||||||
|
#cgroups = 1
|
||||||
|
|
||||||
|
# Refuse to start the service if its cgroup is not empty
|
||||||
|
#service_cgroup_exclusive = 0
|
||||||
|
|
||||||
|
# Wait on all the members of the cgroup to exit when stopping the service.
|
||||||
|
#service_cgroup_wait = 0
|
||||||
|
|
||||||
|
#service_cgroup_strict = 0
|
341
ssm
341
ssm
|
@ -119,7 +119,7 @@ var() {
|
||||||
die() {
|
die() {
|
||||||
declare code=${1:-0}
|
declare code=${1:-0}
|
||||||
|
|
||||||
[[ "$2" ]] && printf '%s\n' "$2"
|
[[ "$2" ]] && printf '%s\n' "$2" >&2
|
||||||
exit "$code"
|
exit "$code"
|
||||||
}; readonly -f die
|
}; readonly -f die
|
||||||
|
|
||||||
|
@ -143,17 +143,17 @@ run_service_action() {
|
||||||
|
|
||||||
spawn() {
|
spawn() {
|
||||||
if [[ $service_logfile_out == "$service_logfile_err" ]]; then
|
if [[ $service_logfile_out == "$service_logfile_err" ]]; then
|
||||||
exec "$@" >"$service_logfile_out" 2>&1
|
exec 3>"$service_logfile_out"
|
||||||
else
|
else
|
||||||
exec "$@" >"$service_logfile_out" 2>"$service_logfile_err"
|
exec 3>"$service_logfile_err"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
exec "$@" >"$service_logfile_out" 2>&3
|
||||||
}; readonly spawn;
|
}; readonly spawn;
|
||||||
|
|
||||||
cgroup_get_procs() {
|
cgroup_get_procs() {
|
||||||
if service_cgroup_path is dir; then
|
if service_cgroup_path is dir; then
|
||||||
while read -r line; do
|
mapfile -t service_cgroup_procs < "$cgroup_home/$service_cgroup_name/cgroup.procs"
|
||||||
service_cgroup_procs += "$line"
|
|
||||||
done < "$cgroup_home/$service_cgroup_name/cgroup.procs"
|
|
||||||
fi
|
fi
|
||||||
}; readonly -f cgroup_get_procs
|
}; readonly -f cgroup_get_procs
|
||||||
|
|
||||||
|
@ -163,7 +163,8 @@ svc() {
|
||||||
var job_pid job_exit job_success last_respawn fail_counter date counter p
|
var job_pid job_exit job_success last_respawn fail_counter date counter p
|
||||||
|
|
||||||
svc::cleanup() {
|
svc::cleanup() {
|
||||||
nullexec kill -n "$service_stop_signal" "$job_pid"
|
#nullexec kill -n "$service_stop_signal" "$job_pid"
|
||||||
|
nullexec "${service_stop_command[@]}"
|
||||||
|
|
||||||
anywait "$job_pid" "$service_stop_timeout"
|
anywait "$job_pid" "$service_stop_timeout"
|
||||||
|
|
||||||
|
@ -189,19 +190,9 @@ svc() {
|
||||||
|
|
||||||
rm -f "$svc_pidfile" "$service_pidfile" "$service_ready_flag"
|
rm -f "$svc_pidfile" "$service_pidfile" "$service_ready_flag"
|
||||||
|
|
||||||
die 0
|
die "$job_exit"
|
||||||
}; trap 'svc::cleanup' TERM
|
}; trap 'svc::cleanup' TERM
|
||||||
|
|
||||||
svc::reload() {
|
|
||||||
nullexec kill -n "$service_reload_signal" "$job_pid"
|
|
||||||
}; trap 'svc::reload' HUP
|
|
||||||
|
|
||||||
# Signals to pass through to the mainpid.
|
|
||||||
svc::passthru() { kill -n "$1" "$2"; }
|
|
||||||
for s in "${service_signals_passthru[@]}"; do
|
|
||||||
trap "svc::passthru $s \$job_pid" "$s"
|
|
||||||
done
|
|
||||||
|
|
||||||
printf '%s' $BASHPID > "$svc_pidfile"
|
printf '%s' $BASHPID > "$svc_pidfile"
|
||||||
|
|
||||||
# Cgroups
|
# Cgroups
|
||||||
|
@ -229,11 +220,7 @@ svc() {
|
||||||
|
|
||||||
# Wait for the process to exit and record the exit code
|
# Wait for the process to exit and record the exit code
|
||||||
# This depends on a few things
|
# This depends on a few things
|
||||||
if service_managed; then
|
if service_own_pidfile; then
|
||||||
printf '%s' "$job_pid" > "$service_pidfile"
|
|
||||||
|
|
||||||
wait "$job_pid"; job_exit=$?
|
|
||||||
else
|
|
||||||
# We need to wait for the service to write down its pidfile
|
# We need to wait for the service to write down its pidfile
|
||||||
until service_pidfile is file; do
|
until service_pidfile is file; do
|
||||||
(( counter >= service_pidfile_timeout*10 )) && {
|
(( counter >= service_pidfile_timeout*10 )) && {
|
||||||
|
@ -246,28 +233,35 @@ svc() {
|
||||||
|
|
||||||
read -r job_pid < "$service_pidfile"
|
read -r job_pid < "$service_pidfile"
|
||||||
|
|
||||||
# We consider any termination of an unmanaged service to be a failure
|
# We consider any termination of a service with its own pidfile
|
||||||
|
# to be a failure
|
||||||
anywait "$job_pid"; job_exit=127
|
anywait "$job_pid"; job_exit=127
|
||||||
|
else
|
||||||
|
printf '%s' "$job_pid" > "$service_pidfile"
|
||||||
|
|
||||||
|
wait "$job_pid"; job_exit=$?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# One service failure, two service failures...
|
||||||
if service_success_exit u "$job_exit"; then
|
if service_success_exit u "$job_exit"; then
|
||||||
job_success = 1
|
job_success = 1
|
||||||
(( fail_counter )) && fail_counter--
|
(( fail_counter )) && fail_counter=0
|
||||||
else
|
else
|
||||||
job_success = 0
|
job_success = 0
|
||||||
fail_counter++
|
fail_counter++
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Record the exit code
|
||||||
printf '%s' "$job_exit" > "$service_exit_file"
|
printf '%s' "$job_exit" > "$service_exit_file"
|
||||||
|
|
||||||
# Back off if the service exits too much AND too quickly.
|
# Back off if the service exits too much AND too quickly.
|
||||||
service_respawn_force || {
|
if ! service_respawn_force; then
|
||||||
if (( fail_counter >= 3 )); then
|
if (( fail_counter >= 3 )); then
|
||||||
printf -v date '%(%s)T'
|
printf -v date '%(%s)T'
|
||||||
|
|
||||||
(( (date - last_respawn) <= 5 )) && break
|
(( (date - last_respawn) <= 5 )) && break
|
||||||
fi
|
fi
|
||||||
}
|
fi
|
||||||
|
|
||||||
# Respawn, if necessary
|
# Respawn, if necessary
|
||||||
service_respawn_flag || break
|
service_respawn_flag || break
|
||||||
|
@ -284,7 +278,7 @@ svc() {
|
||||||
}; readonly -f svc
|
}; readonly -f svc
|
||||||
|
|
||||||
## Run a command with its output discarded
|
## Run a command with its output discarded
|
||||||
nullexec() { "$@" &>/dev/null; }
|
nullexec() { eval "$@" &>/dev/null; }
|
||||||
readonly -f nullexec
|
readonly -f nullexec
|
||||||
|
|
||||||
## Wait for a pid, indefinitely
|
## Wait for a pid, indefinitely
|
||||||
|
@ -400,13 +394,14 @@ result() {
|
||||||
|
|
||||||
[[ "${msgs[$rc]}" ]] || msgs["$rc"]="Failed!"
|
[[ "${msgs[$rc]}" ]] || msgs["$rc"]="Failed!"
|
||||||
|
|
||||||
printf '%s\n' "${msgs[$rc]}"
|
printf '%s\n' "${msgs[$rc]}" >&2
|
||||||
}; readonly -f result
|
}; readonly -f result
|
||||||
|
|
||||||
# Overloadable functions
|
# Overloadable functions
|
||||||
## Start the service, write down the svc pid
|
## Start the service, write down the svc pid
|
||||||
start() {
|
start() {
|
||||||
service_running && return 3
|
service_running && return 3
|
||||||
|
service_enabled && return 19
|
||||||
service_command is file || return 9
|
service_command is file || return 9
|
||||||
|
|
||||||
# Preform cgroup checks
|
# Preform cgroup checks
|
||||||
|
@ -419,25 +414,25 @@ start() {
|
||||||
depend "${service_depends[@]}" || return 7
|
depend "${service_depends[@]}" || return 7
|
||||||
depend_ready "${service_depends_ready[@]}" || return 7
|
depend_ready "${service_depends_ready[@]}" || return 7
|
||||||
|
|
||||||
mktmpfiles || return 13
|
|
||||||
|
|
||||||
rm -f "$service_stopped_flag"
|
rm -f "$service_stopped_flag"
|
||||||
|
|
||||||
|
mktmpfiles || return 13
|
||||||
|
|
||||||
|
svc "${service_command[@]}" & job=$!
|
||||||
|
|
||||||
if service_oneshot; then
|
if service_oneshot; then
|
||||||
spawn "${service_command[@]}"; res=$?
|
wait "$job"; res=$?
|
||||||
|
|
||||||
(( res )) && {
|
(( res )) && {
|
||||||
printf '%s' "$res" > "$service_exit_file"
|
printf '%s' "$res" > "$service_exit_file"
|
||||||
return "$res"
|
return 17
|
||||||
}
|
}
|
||||||
printf '1' > "$service_enabled_flag"
|
fi
|
||||||
else
|
|
||||||
svc "${service_command[@]}" &
|
|
||||||
|
|
||||||
if timer "$service_ready_timeout" run_service_action 'ready'; then
|
if timer "$service_ready_timeout" run_service_action 'ready'; then
|
||||||
set_ready
|
set_ready
|
||||||
else
|
else
|
||||||
return 5
|
return 5
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
@ -447,8 +442,7 @@ start() {
|
||||||
## Usually just sends HUP
|
## Usually just sends HUP
|
||||||
reload() {
|
reload() {
|
||||||
service_running || return 3
|
service_running || return 3
|
||||||
|
nullexec kill -n "$service_reload_signal" "$service_pid"
|
||||||
kill -n 1 "$svc_pid"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
## Stop the service
|
## Stop the service
|
||||||
|
@ -456,11 +450,7 @@ reload() {
|
||||||
## 3: Service is not running.
|
## 3: Service is not running.
|
||||||
stop() {
|
stop() {
|
||||||
if service_oneshot; then
|
if service_oneshot; then
|
||||||
service_enabled || return 3
|
return 7
|
||||||
|
|
||||||
rm -f "$service_enabled_flag"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
else
|
else
|
||||||
service_running || return 3
|
service_running || return 3
|
||||||
|
|
||||||
|
@ -492,47 +482,58 @@ stop() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info.item() { printf "%16s: %s\n" "$1" "$2"; }
|
||||||
info() {
|
info() {
|
||||||
declare _status_label _status _type _info_items
|
declare -a _info_items
|
||||||
var _status_label = 'Running'
|
declare _status_code _status _show_pid
|
||||||
var _status = 'no'
|
var _info_items _status_code _status _show_pid
|
||||||
var _type = 'daemon'
|
|
||||||
var _info_items
|
|
||||||
|
|
||||||
service_oneshot && {
|
info.item Name "$service_name"
|
||||||
_status_label = 'Enabled'
|
|
||||||
_type = 'oneshot'
|
|
||||||
}
|
|
||||||
|
|
||||||
status && _status = 'yes'
|
status; _status_code = "$?"
|
||||||
|
if service_oneshot; then
|
||||||
|
infoinfo.item Oneshot 'yes'
|
||||||
|
|
||||||
_info_items = \
|
case $_status_code in
|
||||||
"Name" "$service_name" \
|
(0) _status = 'success';;
|
||||||
"Type" "$_type" \
|
(1) _status = 'not enabled';;
|
||||||
"$_status_label" "$_status" \
|
(9) _status = "failed ($service_exit_last)";;
|
||||||
"Exec" "${service_command[*]}" \
|
(*) _status = 'unknown';;
|
||||||
"Respawn" "$service_respawn" \
|
esac
|
||||||
"Config path" "$service_config" \
|
else
|
||||||
"Output log" "$service_logfile_out"
|
info.item Restart "$service_respawn"
|
||||||
|
|
||||||
service_logfile_out == "$service_logfile_err" || {
|
case $_status_code in
|
||||||
_info_items+=( "Error log" "$service_logfile_err" )
|
(0) _status = 'running';;
|
||||||
}
|
(1) _status = 'down';;
|
||||||
|
(7) _status = 'stopped';;
|
||||||
if _status == 'yes'; then
|
(9) _status = "failed ($service_exit_last)";;
|
||||||
_info_items += \
|
(11) _status = "exited ($service_exit_last)";;
|
||||||
"PIDfile" "${service_pidfile:-none}" \
|
(*) _status = 'unknown';;
|
||||||
"PID" "${service_pid:-none}"
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Show the cgroup
|
info.item Status "$_status"
|
||||||
if cgroups; then
|
|
||||||
_info_items += "Cgroup" "$cgroup_home/$service_cgroup_name" \
|
info.item Exec "${service_command[*]}"
|
||||||
"Cgroup procs" "${#service_cgroup_procs[@]}"
|
info.item Config "$service_config"
|
||||||
|
info.item Workdir "$service_workdir"
|
||||||
|
|
||||||
|
info.item 'Output log' "$service_logfile_out"
|
||||||
|
service_logfile_out == "$service_logfile_err" || \
|
||||||
|
info.item 'Error log' "$service_logfile_err"
|
||||||
|
|
||||||
|
if service_running; then
|
||||||
|
info.item PID "$service_pid"
|
||||||
|
info.item PIDFile "$service_pidfile"
|
||||||
|
|
||||||
|
cgroups && {
|
||||||
|
info.item Cgroup "$cgroup_home/$service_cgroup_name"
|
||||||
|
info.item 'Cgroup procs' "${#service_cgroup_procs[@]}"
|
||||||
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf "%16s: %s\n" "${_info_items[@]}"
|
return 0
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
## Restart just calls the script twice by default
|
## Restart just calls the script twice by default
|
||||||
|
@ -553,8 +554,10 @@ logs() {
|
||||||
## Status is a bit of a special case. It's talkative.
|
## Status is a bit of a special case. It's talkative.
|
||||||
status() {
|
status() {
|
||||||
service_running && return 0
|
service_running && return 0
|
||||||
service_enabled && return 2
|
service_enabled && return 0
|
||||||
|
|
||||||
service_stopped && return 7
|
service_stopped && return 7
|
||||||
|
|
||||||
service_failed && return 9
|
service_failed && return 9
|
||||||
service_exit_last is empty || return 11
|
service_exit_last is empty || return 11
|
||||||
|
|
||||||
|
@ -575,6 +578,7 @@ reset-exit() { rm -f "$service_exit_file"; }
|
||||||
var service_pid \
|
var service_pid \
|
||||||
service_pidfile \
|
service_pidfile \
|
||||||
service_type \
|
service_type \
|
||||||
|
service_depends \
|
||||||
service_depends_ready \
|
service_depends_ready \
|
||||||
service_command \
|
service_command \
|
||||||
service_config \
|
service_config \
|
||||||
|
@ -584,14 +588,13 @@ var service_pid \
|
||||||
service_logfile_out \
|
service_logfile_out \
|
||||||
service_logfile_err \
|
service_logfile_err \
|
||||||
service_ready_flag \
|
service_ready_flag \
|
||||||
service_enabled_flag \
|
|
||||||
service_stopped_flag \
|
service_stopped_flag \
|
||||||
service_exit_file \
|
service_exit_file \
|
||||||
service_exit_last \
|
service_exit_last \
|
||||||
service_cgroup_name \
|
service_cgroup_name \
|
||||||
service_cgroup_procs \
|
service_cgroup_procs \
|
||||||
service_cgroup_path \
|
service_cgroup_path \
|
||||||
service_signals_passthru \
|
service_config_current \
|
||||||
cgroup_home \
|
cgroup_home \
|
||||||
failed_deps \
|
failed_deps \
|
||||||
svc_pidfile \
|
svc_pidfile \
|
||||||
|
@ -601,12 +604,18 @@ var service_pid \
|
||||||
cfg_dir \
|
cfg_dir \
|
||||||
rundir \
|
rundir \
|
||||||
logdir \
|
logdir \
|
||||||
_self
|
action \
|
||||||
|
_self \
|
||||||
|
res
|
||||||
|
|
||||||
## Internal defaults
|
## Internal defaults
|
||||||
var flag_list_services = 0
|
var flag_list_services = 0
|
||||||
var flag_edit_service = 0
|
var flag_edit_service = 0
|
||||||
var flag_reset_exit = 0
|
var flag_reset_exit = 0
|
||||||
|
var flag_reread_service = 0
|
||||||
|
var flag_forget_service = 0
|
||||||
|
var flag_load_service = 0
|
||||||
|
var flag_no_netns = 0
|
||||||
|
|
||||||
## check for some environment stuff
|
## check for some environment stuff
|
||||||
var EDITOR := 'vim'
|
var EDITOR := 'vim'
|
||||||
|
@ -619,8 +628,8 @@ var XDG_RUNTIME_DIR := "/run/user/$UID"
|
||||||
var service_respawn = 'no' # Respawn the service if it exits
|
var service_respawn = 'no' # Respawn the service if it exits
|
||||||
var service_workdir = '/'
|
var service_workdir = '/'
|
||||||
var service_stop_timeout = 30
|
var service_stop_timeout = 30
|
||||||
|
var service_stop_command = kill -n "\$service_stop_signal" "\$job_pid"
|
||||||
var service_ready_timeout = 15
|
var service_ready_timeout = 15
|
||||||
var service_signals = 1 10 12
|
|
||||||
var service_reload_signal = 1
|
var service_reload_signal = 1
|
||||||
var service_stop_signal = 15
|
var service_stop_signal = 15
|
||||||
var service_cgroup_exclusive = 0 # Refuse to start the service if its cgroup is not empty
|
var service_cgroup_exclusive = 0 # Refuse to start the service if its cgroup is not empty
|
||||||
|
@ -632,13 +641,14 @@ var service_cgroup_cleanup = 0 # Clean up the cgroup when the main PID exits. Us
|
||||||
var service_success_exit = 0 # Array, takes exit codes that are to be treated as successful termination.
|
var service_success_exit = 0 # Array, takes exit codes that are to be treated as successful termination.
|
||||||
var service_pidfile_timeout = 15 # How long to wait for unmanaged services to create their pidfiles.
|
var service_pidfile_timeout = 15 # How long to wait for unmanaged services to create their pidfiles.
|
||||||
var service_pidfile_remove_stale = 1 # Remove stale pidfiles from unmanaged services.
|
var service_pidfile_remove_stale = 1 # Remove stale pidfiles from unmanaged services.
|
||||||
|
var service_remember = 1 # Copy the config into ssm's runtime dir.
|
||||||
|
|
||||||
# Global config
|
# Global config
|
||||||
var cgroups = 0 # Enable cgroup-related functions
|
var cgroups = 0 # Enable cgroup-related functions
|
||||||
var usrdir = '/usr/share/ssm'
|
var usrdir = '/usr/share/ssm'
|
||||||
|
|
||||||
# These are not
|
# These are not
|
||||||
var service_managed = 1
|
var service_own_pidfile = 0
|
||||||
var service_oneshot = 0
|
var service_oneshot = 0
|
||||||
var service_running = 0
|
var service_running = 0
|
||||||
var service_enabled = 0
|
var service_enabled = 0
|
||||||
|
@ -667,14 +677,6 @@ case "$0" in
|
||||||
(*) _self = "$PWD/$0";;
|
(*) _self = "$PWD/$0";;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Warn the user of deprecated stuff.
|
|
||||||
for p in "/etc/ssm/init.d" "$XDG_CONFIG_HOME/ssm/init.d"; do
|
|
||||||
if [[ -d "$p" ]]; then
|
|
||||||
printf 'WARNING: `%s` was renamed to `%s`! Please move your scripts accordingly!\n' "$p" "${p%init.d}services" >&2
|
|
||||||
service_path += "$p"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Source the config
|
# Source the config
|
||||||
if cfg_file is file; then
|
if cfg_file is file; then
|
||||||
source "$cfg_file" || die 37 "Failed to load config: $cfg_file"
|
source "$cfg_file" || die 37 "Failed to load config: $cfg_file"
|
||||||
|
@ -704,9 +706,24 @@ while (( $# )); do
|
||||||
(--reset-exit) # Reset last exit status
|
(--reset-exit) # Reset last exit status
|
||||||
flag_reset_exit = 1;;
|
flag_reset_exit = 1;;
|
||||||
|
|
||||||
(-e|--edit-service) # Edit the service file.
|
(-e|--edit) # Edit the service file.
|
||||||
flag_edit_service = 1;;
|
flag_edit_service = 1;;
|
||||||
|
|
||||||
|
(-r|--reread) # Reload the service file.
|
||||||
|
flag_reread_service = 1;;
|
||||||
|
|
||||||
|
(-f|--forget) # Unload a service.
|
||||||
|
flag_forget_service = 1;;
|
||||||
|
|
||||||
|
(-l|--load) # Load a service.
|
||||||
|
flag_load_service = 1;;
|
||||||
|
|
||||||
|
(-i|--info)
|
||||||
|
action = 'info';;
|
||||||
|
|
||||||
|
(--no-netns)
|
||||||
|
flag_no_netns = 1;;
|
||||||
|
|
||||||
(--) shift; break;;
|
(--) shift; break;;
|
||||||
(-*) printf 'Unknown key: %s\n' "$1" >&2; exit 1;;
|
(-*) printf 'Unknown key: %s\n' "$1" >&2; exit 1;;
|
||||||
(*) break;;
|
(*) break;;
|
||||||
|
@ -716,21 +733,23 @@ while (( $# )); do
|
||||||
done
|
done
|
||||||
|
|
||||||
# Now create the needed runtime stuff
|
# Now create the needed runtime stuff
|
||||||
for d in "$rundir" "$logdir"; do
|
for d in "$rundir" "$rundir/current" "$logdir"; do
|
||||||
mkdir -p "$d" || die 3 "Failed to create runtime dir: $d"
|
mkdir -p "$d" || die 3 "Failed to create runtime dir: $d"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Common service path
|
# Common service path
|
||||||
service_path += "$XDG_CONFIG_HOME/ssm/services" '/etc/ssm/services' "$rundir/services" "$usrdir/services"
|
if (( UID )); then
|
||||||
|
service_path += "$rundir/current" "$XDG_CONFIG_HOME/ssm/services" '/etc/ssm/services' "$usrdir/services"
|
||||||
|
else
|
||||||
|
service_path += "$rundir/current" '/etc/ssm/services' "$usrdir/services"
|
||||||
|
fi
|
||||||
|
|
||||||
# Special actions
|
# Special actions
|
||||||
if flag_list_services; then
|
if flag_list_services; then
|
||||||
var known_services
|
var known_services
|
||||||
for i in "$rundir"/*.{pid,exit,stopped,enabled}; do
|
for i in "$rundir"/current/*; do
|
||||||
i_fname="${i##*/}"
|
i_name="${i##*/}"
|
||||||
i_sname="${i_fname%.*}"
|
known_services u "$i_name" || known_services += "$i_name"
|
||||||
|
|
||||||
known_services u "$i_sname" || known_services += "$i_sname"
|
|
||||||
done
|
done
|
||||||
|
|
||||||
for s in "${known_services[@]}"; do
|
for s in "${known_services[@]}"; do
|
||||||
|
@ -744,32 +763,50 @@ fi
|
||||||
# This script requires at least one argument
|
# This script requires at least one argument
|
||||||
(( $# >= 1 )) || { usage; exit 2; }
|
(( $# >= 1 )) || { usage; exit 2; }
|
||||||
|
|
||||||
# If $1 is a full path, source it.
|
# Unless otherwise specified
|
||||||
# If not, search for it in the service path.
|
service_name = "$1"; shift
|
||||||
if [[ $1 == /* ]]; then
|
|
||||||
service_config = "$1"
|
# Find the current loaded service and remove it if necessary
|
||||||
|
service_config_current = "$rundir/current/$service_name"
|
||||||
|
if flag_reread_service; then
|
||||||
|
rm -vf "$service_config_current" >&2 || die "$?" "Abort!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find the service
|
||||||
|
if [[ $service_name == /* ]]; then
|
||||||
|
service_config = "$service_name"
|
||||||
|
service_name = "${service_name##*/}"
|
||||||
else
|
else
|
||||||
for i in "${service_path[@]/%//$1}"; do
|
for i in "${service_path[@]/%//$service_name}"; do
|
||||||
[[ -f "$i" ]] && {
|
[[ -f "$i" ]] && {
|
||||||
service_config = "$i"
|
service_config = "$i"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
done
|
done
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
# We really don't want this overriden
|
||||||
|
readonly service_name
|
||||||
|
|
||||||
|
# Unload the service and leave if asked.
|
||||||
|
if flag_forget_service; then
|
||||||
|
if service_config_current is file; then
|
||||||
|
rm -f "$service_config_current" || die "$?" "Abort!"
|
||||||
|
die 0 "Forgot service: $service_name"
|
||||||
|
else
|
||||||
|
die 90 "Service not currently known: $service_name"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Die if there is no service config file
|
# Die if there is no service config file
|
||||||
service_config || die 19 "Service not found: $1"
|
service_config || die 19 "Service not found: $service_name"
|
||||||
|
|
||||||
# Edit the service config
|
# Edit the service config
|
||||||
flag_edit_service && { edit; die $?; }
|
flag_edit_service && { edit; die $?; }
|
||||||
|
|
||||||
# Service name is the basename
|
|
||||||
service_name = "${1##*/}"
|
|
||||||
readonly service_name
|
|
||||||
|
|
||||||
# These depend on the service_name and make little sense to reconfigure.
|
# These depend on the service_name and make little sense to reconfigure.
|
||||||
service_ready_flag := "$rundir/$service_name.ready"
|
service_ready_flag := "$rundir/$service_name.ready"
|
||||||
service_enabled_flag := "$rundir/$service_name.enabled"
|
|
||||||
service_stopped_flag := "$rundir/$service_name.stopped"
|
service_stopped_flag := "$rundir/$service_name.stopped"
|
||||||
service_exit_file := "$rundir/$service_name.exit"
|
service_exit_file := "$rundir/$service_name.exit"
|
||||||
service_cgroup_name := "$service_name"
|
service_cgroup_name := "$service_name"
|
||||||
|
@ -788,6 +825,21 @@ flag_reset_exit && { reset-exit; die $?; }
|
||||||
# Get the service config
|
# Get the service config
|
||||||
source -- "$service_config" "${@:3}" || die 7 "Failed to read the service config: $service_config"
|
source -- "$service_config" "${@:3}" || die 7 "Failed to read the service config: $service_config"
|
||||||
|
|
||||||
|
# Rexec ourselves in the requested netns
|
||||||
|
flag_no_netns || {
|
||||||
|
[[ $service_netns ]] && exec ip netns exec "$service_netns" "$0" --no-netns "$service_name" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# “Load” the service into memory
|
||||||
|
if ! service_config == "$service_config_current" && service_remember; then
|
||||||
|
cat "$service_config" > "$rundir/current/$service_name"
|
||||||
|
printf 'Loaded %s from: %s\n' "$service_name" "$service_config" >&2
|
||||||
|
service_config = "$rundir/current/$service_name"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Die if we only needed to load a service
|
||||||
|
flag_load_service && die 0
|
||||||
|
|
||||||
# Legacy
|
# Legacy
|
||||||
service_args && service_command += "${service_args[@]}"
|
service_args && service_command += "${service_args[@]}"
|
||||||
service_type == 'oneshot' && service_oneshot = 1
|
service_type == 'oneshot' && service_oneshot = 1
|
||||||
|
@ -801,8 +853,12 @@ if ! service_respawn == 'no'; then
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Unset the managed flag on services with their own pidfile
|
if service_respawn_flag && service_oneshot; then
|
||||||
service_pidfile && service_managed = 0
|
die 89 'Cowardly refusing to respawn a oneshot service'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Catch services with their own pidfile, set the appropriate flag.
|
||||||
|
service_pidfile && service_own_pidfile = 1
|
||||||
|
|
||||||
# Semi-hardcoded stuff
|
# Semi-hardcoded stuff
|
||||||
svc_pidfile = "$rundir/$service_name.svc_pid"
|
svc_pidfile = "$rundir/$service_name.svc_pid"
|
||||||
|
@ -846,17 +902,11 @@ if svc_pidfile is file; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remove the stale svc pidfile
|
# Remove the stale svc pidfile
|
||||||
printf 'WARNING: Removing a stale svc pidfile: %s\n' "$svc_pidfile"
|
printf 'WARNING: Removing a stale svc pidfile: %s\n' "$svc_pidfile" >&2
|
||||||
rm -f "$svc_pidfile"
|
rm -f "$svc_pidfile"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Maybe the service is enabled?
|
|
||||||
if service_enabled_flag is file; then
|
|
||||||
# Yay, it is!
|
|
||||||
service_enabled = 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Let's see if the service was deliberately stopped
|
# Let's see if the service was deliberately stopped
|
||||||
if service_stopped_flag is file; then
|
if service_stopped_flag is file; then
|
||||||
# Ooh, it was.
|
# Ooh, it was.
|
||||||
|
@ -867,7 +917,9 @@ fi
|
||||||
if service_exit_file is file; then
|
if service_exit_file is file; then
|
||||||
read -r service_exit_last < "$service_exit_file"
|
read -r service_exit_last < "$service_exit_file"
|
||||||
|
|
||||||
if ! service_success_exit u "$service_exit_last"; then
|
if service_success_exit u "$service_exit_last"; then
|
||||||
|
service_oneshot && service_enabled = 1
|
||||||
|
else
|
||||||
# :(
|
# :(
|
||||||
service_failed = 1
|
service_failed = 1
|
||||||
fi
|
fi
|
||||||
|
@ -885,8 +937,14 @@ if cgroups; then
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Action!
|
||||||
|
[[ "$1" ]] && action = "$1"
|
||||||
|
action || {
|
||||||
|
flag_reread_service && die 0 # Not a mistake if the service was to be re-read
|
||||||
|
usage; die 2; }
|
||||||
|
|
||||||
# Do we have such a function?
|
# Do we have such a function?
|
||||||
if_service_action "$2" || die 17 "Function $2 is not defined for $service_name."
|
if_service_action "$action" || die 17 "Function $action is not defined for $service_name."
|
||||||
|
|
||||||
# cd into the workdir, if defined.
|
# cd into the workdir, if defined.
|
||||||
service_workdir && {
|
service_workdir && {
|
||||||
|
@ -894,17 +952,18 @@ service_workdir && {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Run pre_$action function
|
# Run pre_$action function
|
||||||
run_service_action "pre_$2" || die 13 "pre_$2 failed!"
|
run_service_action "pre_$action" || die 13 "pre_$action failed!"
|
||||||
|
|
||||||
# Run the main action
|
# Run the main action
|
||||||
run_service_action "$2"; res=$?
|
run_service_action "$action"; res = "$?"
|
||||||
|
|
||||||
case "$2" in
|
case "$action" in
|
||||||
stop)
|
stop)
|
||||||
result "$res" \
|
result "$res" \
|
||||||
0 "Stopped $service_name" \
|
0 "Stopped $service_name" \
|
||||||
3 "$service_name is not running" \
|
3 "$service_name is not running" \
|
||||||
5 "Operation timed out"
|
5 "Operation timed out" \
|
||||||
|
7 "Can't “stop” a oneshot service. Use reset-exit if you want to “start” the service again"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
start)
|
start)
|
||||||
|
@ -915,7 +974,9 @@ case "$2" in
|
||||||
7 "Failed to start dependencies for $service_name: ${failed_deps[@]}" \
|
7 "Failed to start dependencies for $service_name: ${failed_deps[@]}" \
|
||||||
9 "service_command does not exist: ${service_command[0]}" \
|
9 "service_command does not exist: ${service_command[0]}" \
|
||||||
13 "Failed to create temporary files for $service_name" \
|
13 "Failed to create temporary files for $service_name" \
|
||||||
15 "Refusing to start $service_name: the service cgroup is not empty and \$service_cgroup_exclusive is set"
|
15 "Refusing to start $service_name: the service cgroup is not empty and \$service_cgroup_exclusive is set" \
|
||||||
|
17 "$service_name failed" \
|
||||||
|
19 "$service_name is already enabled"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
reload)
|
reload)
|
||||||
|
@ -924,17 +985,23 @@ case "$2" in
|
||||||
;;
|
;;
|
||||||
|
|
||||||
status)
|
status)
|
||||||
result "$res" \
|
if service_oneshot; then
|
||||||
0 "$service_name is running" \
|
result "$res" \
|
||||||
2 "$service_name was successful" \
|
0 "$service_name was successful" \
|
||||||
1 "$service_name is not running" \
|
1 "$service_name is not enabled" \
|
||||||
7 "$service_name was stopped" \
|
9 "$service_name has failed ($service_exit_last)"
|
||||||
9 "$service_name has failed with code: $service_exit_last" \
|
else
|
||||||
11 "$service_name has exited with code: $service_exit_last"
|
result "$res" \
|
||||||
|
0 "$service_name is running" \
|
||||||
|
1 "$service_name is not running" \
|
||||||
|
7 "$service_name was stopped" \
|
||||||
|
9 "$service_name has failed with code: $service_exit_last" \
|
||||||
|
11 "$service_name has exited with code: $service_exit_last"
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
(( res )) && exit "$res"
|
(( res )) && exit "$res"
|
||||||
|
|
||||||
# Run post_$action function
|
# Run post_$action function
|
||||||
run_service_action "post_$2" || die 15 "post_$2 has failed!"
|
run_service_action "post_$action" || die 15 "post_$action has failed!"
|
||||||
|
|
49
ssm.conf
49
ssm.conf
|
@ -1,49 +0,0 @@
|
||||||
# This is actually a bash script
|
|
||||||
|
|
||||||
# Enable cgroup-related functions
|
|
||||||
#cgroups = 0
|
|
||||||
|
|
||||||
# Where to search for services, works as a PATH-like array.
|
|
||||||
#service_path = "$XDG_CONFIG_HOME/ssm/services" /etc/ssm/services "$rundir/services" /usr/share/ssm/services
|
|
||||||
|
|
||||||
# Service defaults
|
|
||||||
|
|
||||||
## Respawn the service if it exits
|
|
||||||
#service_respawn = 0
|
|
||||||
|
|
||||||
## Working directory
|
|
||||||
#service_workdir = '/'
|
|
||||||
|
|
||||||
## How long do we wait for the service to stop
|
|
||||||
#service_stop_timeout = 30
|
|
||||||
|
|
||||||
## How long do we wait for the service to get ready
|
|
||||||
#service_ready_timeout = 15
|
|
||||||
|
|
||||||
## Signals to pass through to the service under the respawner
|
|
||||||
#service_signals = 1 10 12
|
|
||||||
|
|
||||||
## The signal to send to reload the service
|
|
||||||
#service_reload_signal = 1
|
|
||||||
|
|
||||||
## The signal to send to stop the service
|
|
||||||
#service_stop_signal = 15
|
|
||||||
|
|
||||||
## These only work with cgroups = 1:
|
|
||||||
|
|
||||||
## Check if the recorded PID of the service is in the correct cgroup.
|
|
||||||
#service_cgroup_strict = 1
|
|
||||||
|
|
||||||
## Refuse to start the service if its cgroup is not empty
|
|
||||||
#service_cgroup_exclusive = 0
|
|
||||||
|
|
||||||
## Wait on all the members of the cgroup to exit when stopping the service.
|
|
||||||
#service_cgroup_wait = 0
|
|
||||||
|
|
||||||
## Send a kill signal to the members of cgroup when
|
|
||||||
## stopping the service, and the signal to send:
|
|
||||||
#service_cgroup_kill = 0
|
|
||||||
#service_cgroup_kill_signal = 15
|
|
||||||
|
|
||||||
# Clean up the cgroup when the service exits for any reason
|
|
||||||
#service_cgroup_cleanup = 0
|
|
Loading…
Reference in New Issue