#!/usr/bin/bash

# ssrv
# start/stop/enable/disable systemd or init.d services
# ntsysv replacement which can work on CentOS and Fedora systems
# (c) 2013-2025 Jan ONDREJ (SAL) <ondrejj(at)salstar.sk>
# License: GPLv2+

declare -a services srv_args
declare -A service_descr service_state enabled_service 2>/dev/null

show_help() {
cat << EOF
ssrv, version 1.1
(c) 2013-2024 Jan ONDREJ (SAL) <ondrejj(at)salstar.sk>

Usage: ssrv [start|stop|restart|reload|status|log|watch|files|enable|disable] service_names
   or: ssrv [list|edit [--skip-start]]
EOF
}

# init.d function
##################

initd_read_services() {
  init_dir="/etc/init.d"
  if [ ! -d $init_dir ]; then
    return
  fi
  # return if no runlevel command
  which runlevel > /dev/null 2>&1 || return
  runlevel=$(runlevel | cut -d' ' -f2)
  rc_dir="/etc/rc.d/rc${runlevel}.d"
  # read enabled services
  for i in $rc_dir/S*; do
    if [ -L "$i" ]; then
      name=$(basename $(readlink $i))
      if [ -f $init_dir/$name ]; then
        service_state[$name]="on"
      fi
    fi
  done
  # read init scripts
  for name in $(ls -1 $init_dir | grep -v '^functions$'); do
    if [ -x "$init_dir/$name" -a ! -d "$init_dir/$name" ]; then
      services+=($name)
      service_descr[$name]="$(
        grep '^# Short-Description: ' $init_dir/$name | cut -d' ' -f3-
      )"
      if [ -z "${service_descr[$name]}" ]; then
        service_descr[$name]="$name service"
      fi
      if [ -z ${service_state[$name]} ]; then
        service_state[$name]="off"
      fi
      srv_args+=("$name" "${service_descr[$name]}" "${service_state[$name]}")
    fi
  done
}

initd_reload_daemon() {
  true # not required for init.d
}

initd_service() {
  C=$1
  shift
  for S in "$@"; do
    service $S $C
    if [ "$C" = "status" ]; then
      chkconfig --list $S
    fi
  done
}

initd_enable_service() {
  for S in "$@"; do
    chkconfig $S on
    if [ "$SSRV_SKIP_START" = "" ]; then
      service $S start
    fi
  done
}

initd_disable_service() {
  for S in "$@"; do
    service $S stop
    chkconfig $S off
  done
}

# systemd functions
####################

__ssrv_systemctl() {
  systemctl --full --no-pager --no-legend --plain "$@"
}

get_systemd_services() {
  if [ "$1" = "--short" ]; then
    (
      __ssrv_systemctl list-unit-files --type=service "$2*"
      __ssrv_systemctl list-units --type=service --all "$2*"
    ) | awk '{ sub(".service$", "", $1); print $1 }'
  else
    __ssrv_systemctl list-units --type=service --all "$@" \
      | awk '
          $1~/@\.service$/ { next }
          $2~/^not-found$/ { next }
          $1~/\.service$/ {
            "systemctl is-enabled "$1 | getline state
            if (state=="enabled") {
              state = "on"
            } else if (state=="disabled") {
              state = "off"
            } else {
              next
            }
            sub(".service$", "", $1)
            descr = $0
            gsub(/^([^ ]+ +){4}/, "", descr)
            gsub(" *$", "", descr)
            gsub("-", " ", descr)
            #gsub("[^a-zA-Z0-9]", "_", descr)
            print $1, state, descr
          }
        '
    __ssrv_systemctl list-unit-files --type=service --all "$@" \
      | awk '
          $2!="enabled" && $2!="disabled" { next }
          $2=="enabled" { state = "on" }
          $2=="disabled" { state = "off" }
          $1~/@\.service$/ { next }
          $1~/\.service$/ {
            ss="systemctl status "$1" | head -1 | cut -d\\  -f3-"
            ss | getline descr
            #close(ss)
            sub(".service$", "", $1)
            gsub(" *$", "", descr)
            gsub("-", " ", descr)
            #gsub("[^a-zA-Z0-9]", "_", descr)
            print $1, state, descr
          }
        '
  fi | sort -u -k1,1
}

systemd_read_services() {
  if [ "$1" = "--short" ]; then
    SHORT="$1"
    shift
  else
    SHORT=""
  fi
  while read name state descr; do
    services+=($name)
    service_descr[$name]="$descr"
    service_state[$name]=$state
    srv_args+=("$name" "${service_descr[$name]}" "${service_state[$name]}")
  done < <(get_systemd_services $SHORT "$@")
  # also read init.d services for systemd, but only if there is no search arg
  if [ -z "$1" ]; then
    initd_read_services
  else
    initd_read_services | grep --basic-regexp "^$1"
  fi
}

systemd_reload_daemon() {
  systemctl --system daemon-reload
}

systemd_service() {
  C=$1
  shift
  if [ "$C" = "files" ]; then
    for S in /usr/lib/systemd/system/$1.service \
             /etc/systemd/system/$1.service \
             /etc/systemd/system/$1.service.d
    do
      if [ -f "$S" ]; then
        echo "### $S:"
        cat "$S"
        echo
      elif [ -d "$S" ]; then
        for file in `ls -A "$S"`; do
          echo "### $S/$file:"
          cat "$S/$file"
          echo
        done
      fi
    done
  else
    for S in "$@"; do
      # -l - don't ellipsize output
      systemctl -l --no-pager $C $S || \
        if [ "$?" != "3" ]; then # Job type is not applicable for this service
          if [ "$C" = "restart" -o "$C" = "reload" ]; then
            systemctl -l --no-pager status $S
          fi
        fi
    done
  fi
}

systemd_enable_service() {
  for S in "$@"; do
    systemctl enable $S
    if [ "$SSRV_SKIP_START" = "" ]; then
      systemctl start $S
    fi
  done
}

systemd_disable_service() {
  for S in "$@"; do
    systemctl stop $S
    systemctl disable $S
  done
}

systemd_log() {
  if [ "$1" = "-u" -o "$1" = "--user" ]; then
    SERVICE="$2"
    shift 2
    journalctl --user --user-unit "$SERVICE".service "$@"
  else
    SERVICE="$1"
    shift
    journalctl --unit "$SERVICE".service "$@"
  fi
}

# Main functions
#################

select_services() {
  if [ -x /usr/bin/whiptail ]; then
    whiptail --checklist --separate-output --clear \
      "Select services" 23 80 16 "${srv_args[@]}"
    ret=$?
  elif [ -x /usr/bin/dialog ]; then
    dialog --separate-output --shadow --scrollbar \
      --checklist "Select services" 22 78 16 "${srv_args[@]}"
    ret=$?
  else
    echo "No whiptail or dialog found! Please install it."
    exit 1
  fi
  return $ret
}

edit_services() {
  ${SRV}_read_services "$@"
  # clear all service states to off
  for name in ${services[@]}; do
    enabled_service[$name]="off"
  done
  # and update changed service
  tmp=$(mktemp --suffix "ssrv")
  trap "rm -f '$tmp'" exit
  select_services 2> $tmp
  if [ "$?" = "0" ]; then
    echo ""
  else
    echo "Canceled."
    return
  fi
  for name in $(cat $tmp); do
    enabled_service[$name]="on"
  done
  rm -f "$tmp"
  trap EXIT

  for name in ${services[@]}; do
    if [ "${enabled_service[$name]}" != "${service_state[$name]}" ]; then
      if [ "${enabled_service[$name]}" = "on" ]; then
        echo "Enabling and starting service $name ..."
        ${SRV}_enable_service $name
      else
        echo "Disabling and stopping service $name ..."
        ${SRV}_disable_service $name
      fi
    fi
  done
}

# detect systemd/init.d
if [ -z "$SRV" ]; then
  if [ -d /etc/systemd ]; then
    SRV="systemd"
  else
    SRV="initd"
  fi
fi

CMD="$1"
case "$CMD" in
	list)
		shift
		srv_args=()
		${SRV}_read_services "$@"
		for name in ${services[@]}; do
			if [ "$1" = "--short" ]; then
				echo $name
			else
				echo $name - ${service_state[$name]}
			fi
		done
		;;
	start|run)
		shift
		${SRV}_service start "$@"
		;;
	stop|end)
		shift
		${SRV}_service stop "$@"
		;;
	restart|r)
		shift
		${SRV}_reload_daemon
		${SRV}_service restart "$@"
		;;
	reload|status|files)
		${SRV}_service "$@"
		;;
	log)
		shift
		${SRV}_log "$@"
		;;
	watch)
		shift
		${SRV}_log "$@" --follow
		;;
	enable)
		shift
		${SRV}_enable_service "$@"
		;;
	disable)
		shift
		${SRV}_disable_service "$@"
		;;
	edit)
		shift
		if [ "$1" = "--skip-start" ]; then
			SSRV_SKIP_START=1
			shift
		fi
		edit_services "$@"
		;;
	*)
		show_help
		;;
esac
