examples, oneshot services rework

Signed-off-by: fbt <fbt@fleshless.org>
This commit is contained in:
Jack L. Frost 2018-03-09 02:31:14 +03:00
parent 5506a922c6
commit e227235f46
9 changed files with 104 additions and 94 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.
@ -41,4 +44,4 @@ Optional settings (incomplete list):
* `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.
Quote your expansions btw. This is still BASH. 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"

11
examples/ssm.conf Normal file
View File

@ -0,0 +1,11 @@
# This is actually a bash script
# Everything in ssm is a bash script.
# Enable cgroup-related functions.
# This requires quite specific setup that is undocumented as of yet, sorry.
#cgroups = 0
# You can also set service-level options here, as local defaults.
# I really do not advise doing that, but you *can*.
# For example:
#service_workdir = /

88
ssm
View File

@ -189,7 +189,7 @@ 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() { svc::reload() {
@ -229,11 +229,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,10 +242,16 @@ 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--
@ -258,16 +260,17 @@ svc() {
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
@ -419,25 +422,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
@ -457,10 +460,7 @@ reload() {
stop() { stop() {
if service_oneshot; then if service_oneshot; then
service_enabled || return 3 service_enabled || return 3
return 7
rm -f "$service_enabled_flag"
return 0
else else
service_running || return 3 service_running || return 3
@ -499,12 +499,12 @@ info() {
var _type = 'daemon' var _type = 'daemon'
var _info_items var _info_items
service_oneshot && { if service_oneshot; then
_status_label = 'Enabled' _status_label = 'Enabled'
_type = 'oneshot' service_enabled && _status = 'yes'
} else
service_running && _status = 'yes'
status && _status = 'yes' fi
_info_items = \ _info_items = \
"Name" "$service_name" \ "Name" "$service_name" \
@ -584,7 +584,6 @@ 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 \
@ -638,7 +637,7 @@ 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
@ -769,7 +768,6 @@ 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"
@ -801,8 +799,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"
@ -851,12 +853,6 @@ if svc_pidfile is file; then
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 +863,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
@ -904,7 +902,8 @@ case "$2" in
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 +914,8 @@ 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"
;; ;;
reload) reload)

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