Compare commits

...

20 Commits

Author SHA1 Message Date
Jack L. Frost 7c584eb8ee service path
Signed-off-by: fbt <fbt@fleshless.org>
2021-01-24 19:36:37 +03:00
Jack L. Frost 608e7d4d9d Why
Signed-off-by: fbt <fbt@fleshless.org>
2020-12-03 19:14:21 +03:00
Jack L. Frost be3d90ea79 A simpler approach
Signed-off-by: fbt <fbt@fleshless.org>
2020-03-28 19:39:54 +03:00
Jack L. Frost c5bbf92022 experimental netns support and some fixes
Signed-off-by: fbt <fbt@fleshless.org>
2020-03-28 19:12:15 +03:00
Jack L. Frost bba7eef3d9 There is no passthru anymore
Signed-off-by: fbt <fbt@fleshless.org>
2020-03-07 18:50:02 +03:00
Jack L. Frost a0ef06810c shaky, abort
Signed-off-by: fbt <fbt@fleshless.org>
2020-02-11 03:00:24 +03:00
Jack L. Frost a47b00f665 We always know the mainpid, why the cloak & dagger
Signed-off-by: fbt <fbt@fleshless.org>
2019-11-17 17:48:51 +03:00
Jack L. Frost b67fec6f16 Another experiment
Signed-off-by: fbt <fbt@fleshless.org>
2019-11-17 17:35:19 +03:00
Jack L. Frost c6bba36f94 this works always
Signed-off-by: fbt <fbt@fleshless.org>
2018-12-19 19:07:39 +03:00
Jack L. Frost d07fd3362b tiny QoL improvement
Signed-off-by: fbt <fbt@fleshless.org>
2018-08-07 23:04:35 +03:00
Jack L. Frost e948f6985a a flag for remembering service configs
Signed-off-by: fbt <fbt@fleshless.org>
2018-04-08 16:54:23 +03:00
Jack L. Frost 0d95965b5b oops
Signed-off-by: fbt <fbt@fleshless.org>
2018-03-09 07:58:34 +03:00
Jack L. Frost e433bdccdd consistency
Signed-off-by: fbt <fbt@fleshless.org>
2018-03-09 07:50:20 +03:00
Jack L. Frost a4bd260bfe These should go into stderr
Signed-off-by: fbt <fbt@fleshless.org>
2018-03-09 07:23:57 +03:00
Jack L. Frost 7afeb29bf2 Some ideas were dumb
Signed-off-by: fbt <fbt@fleshless.org>
2018-03-09 07:19:18 +03:00
Jack L. Frost 924324aa10 I'm lazy
Signed-off-by: fbt <fbt@fleshless.org>
2018-03-09 06:57:26 +03:00
Jack L. Frost 2aff1db42b Keep the state yo
Signed-off-by: fbt <fbt@fleshless.org>
2018-03-09 05:57:26 +03:00
Jack L. Frost 0dd00077a5 info fixes
Signed-off-by: fbt <fbt@fleshless.org>
2018-03-09 03:25:49 +03:00
Jack L. Frost e227235f46 examples, oneshot services rework
Signed-off-by: fbt <fbt@fleshless.org>
2018-03-09 02:31:14 +03:00
Jack L. Frost 5506a922c6 quote expansions
Signed-off-by: fbt <fbt@fleshless.org>
2018-03-08 23:57:44 +03:00
9 changed files with 293 additions and 188 deletions

View File

@ -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.

11
examples/services/iptables Executable file
View File

@ -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
}

8
examples/services/nginx Executable file
View File

@ -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 "$@"; }

9
examples/services/rc.local Executable file
View File

@ -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
}

10
examples/services/sshd Executable file
View File

@ -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
}

7
examples/services/tinc Executable file
View File

@ -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"

37
examples/ssm.conf Normal file
View File

@ -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
View File

@ -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!"

View File

@ -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