From e227235f4655956c9db3bea9cc11a7cb40d62d02 Mon Sep 17 00:00:00 2001 From: fbt Date: Fri, 9 Mar 2018 02:31:14 +0300 Subject: [PATCH] examples, oneshot services rework Signed-off-by: fbt --- README.md | 5 ++- examples/services/iptables | 11 +++++ examples/services/nginx | 8 ++++ examples/services/rc.local | 9 ++++ examples/services/sshd | 10 +++++ examples/services/tinc | 7 +++ examples/ssm.conf | 11 +++++ ssm | 88 +++++++++++++++++++------------------- ssm.conf | 49 --------------------- 9 files changed, 104 insertions(+), 94 deletions(-) create mode 100755 examples/services/iptables create mode 100755 examples/services/nginx create mode 100755 examples/services/rc.local create mode 100755 examples/services/sshd create mode 100755 examples/services/tinc create mode 100644 examples/ssm.conf delete mode 100644 ssm.conf diff --git a/README.md b/README.md index 4764a49..ad49294 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ Services 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. +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 * `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_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. diff --git a/examples/services/iptables b/examples/services/iptables new file mode 100755 index 0000000..745743f --- /dev/null +++ b/examples/services/iptables @@ -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 +} diff --git a/examples/services/nginx b/examples/services/nginx new file mode 100755 index 0000000..8389843 --- /dev/null +++ b/examples/services/nginx @@ -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 "$@"; } diff --git a/examples/services/rc.local b/examples/services/rc.local new file mode 100755 index 0000000..f22822d --- /dev/null +++ b/examples/services/rc.local @@ -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 +} diff --git a/examples/services/sshd b/examples/services/sshd new file mode 100755 index 0000000..ddea5d3 --- /dev/null +++ b/examples/services/sshd @@ -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 +} diff --git a/examples/services/tinc b/examples/services/tinc new file mode 100755 index 0000000..a3c8366 --- /dev/null +++ b/examples/services/tinc @@ -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" diff --git a/examples/ssm.conf b/examples/ssm.conf new file mode 100644 index 0000000..1fe5987 --- /dev/null +++ b/examples/ssm.conf @@ -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 = / diff --git a/ssm b/ssm index 3b0847b..4bc44d2 100755 --- a/ssm +++ b/ssm @@ -189,7 +189,7 @@ svc() { rm -f "$svc_pidfile" "$service_pidfile" "$service_ready_flag" - die 0 + die "$job_exit" }; trap 'svc::cleanup' TERM svc::reload() { @@ -229,11 +229,7 @@ svc() { # Wait for the process to exit and record the exit code # This depends on a few things - if service_managed; then - printf '%s' "$job_pid" > "$service_pidfile" - - wait "$job_pid"; job_exit=$? - else + if service_own_pidfile; then # We need to wait for the service to write down its pidfile until service_pidfile is file; do (( counter >= service_pidfile_timeout*10 )) && { @@ -246,10 +242,16 @@ svc() { 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 + else + printf '%s' "$job_pid" > "$service_pidfile" + + wait "$job_pid"; job_exit=$? fi + # One service failure, two service failures... if service_success_exit u "$job_exit"; then job_success = 1 (( fail_counter )) && fail_counter-- @@ -258,16 +260,17 @@ svc() { fail_counter++ fi + # Record the exit code printf '%s' "$job_exit" > "$service_exit_file" # Back off if the service exits too much AND too quickly. - service_respawn_force || { + if ! service_respawn_force; then if (( fail_counter >= 3 )); then printf -v date '%(%s)T' (( (date - last_respawn) <= 5 )) && break fi - } + fi # Respawn, if necessary service_respawn_flag || break @@ -419,25 +422,25 @@ start() { depend "${service_depends[@]}" || return 7 depend_ready "${service_depends_ready[@]}" || return 7 - mktmpfiles || return 13 - rm -f "$service_stopped_flag" + mktmpfiles || return 13 + + svc "${service_command[@]}" & job=$! + if service_oneshot; then - spawn "${service_command[@]}"; res=$? + wait "$job"; res=$? + (( res )) && { printf '%s' "$res" > "$service_exit_file" - return "$res" + return 17 } - printf '1' > "$service_enabled_flag" - else - svc "${service_command[@]}" & + fi - if timer "$service_ready_timeout" run_service_action 'ready'; then - set_ready - else - return 5 - fi + if timer "$service_ready_timeout" run_service_action 'ready'; then + set_ready + else + return 5 fi return 0 @@ -457,10 +460,7 @@ reload() { stop() { if service_oneshot; then service_enabled || return 3 - - rm -f "$service_enabled_flag" - - return 0 + return 7 else service_running || return 3 @@ -499,12 +499,12 @@ info() { var _type = 'daemon' var _info_items - service_oneshot && { + if service_oneshot; then _status_label = 'Enabled' - _type = 'oneshot' - } - - status && _status = 'yes' + service_enabled && _status = 'yes' + else + service_running && _status = 'yes' + fi _info_items = \ "Name" "$service_name" \ @@ -584,7 +584,6 @@ var service_pid \ service_logfile_out \ service_logfile_err \ service_ready_flag \ - service_enabled_flag \ service_stopped_flag \ service_exit_file \ service_exit_last \ @@ -638,7 +637,7 @@ var cgroups = 0 # Enable cgroup-related functions var usrdir = '/usr/share/ssm' # These are not -var service_managed = 1 +var service_own_pidfile = 0 var service_oneshot = 0 var service_running = 0 var service_enabled = 0 @@ -769,7 +768,6 @@ readonly service_name # These depend on the service_name and make little sense to reconfigure. service_ready_flag := "$rundir/$service_name.ready" -service_enabled_flag := "$rundir/$service_name.enabled" service_stopped_flag := "$rundir/$service_name.stopped" service_exit_file := "$rundir/$service_name.exit" service_cgroup_name := "$service_name" @@ -801,8 +799,12 @@ if ! service_respawn == 'no'; then esac fi -# Unset the managed flag on services with their own pidfile -service_pidfile && service_managed = 0 +if service_respawn_flag && service_oneshot; then + 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 svc_pidfile = "$rundir/$service_name.svc_pid" @@ -851,12 +853,6 @@ if svc_pidfile is file; then 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 if service_stopped_flag is file; then # Ooh, it was. @@ -867,7 +863,9 @@ fi if service_exit_file is file; then 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 fi @@ -904,7 +902,8 @@ case "$2" in result "$res" \ 0 "Stopped $service_name" \ 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) @@ -915,7 +914,8 @@ case "$2" in 7 "Failed to start dependencies for $service_name: ${failed_deps[@]}" \ 9 "service_command does not exist: ${service_command[0]}" \ 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) diff --git a/ssm.conf b/ssm.conf deleted file mode 100644 index b959db7..0000000 --- a/ssm.conf +++ /dev/null @@ -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