====== Custom User Services ======
Since i am running lots of various shaped and managed services, i needed an efficient and **simple** way to automatically start and stop them.
Gentoo already provides a good approach to starting and stopping services called [[https://wiki.gentoo.org/wiki/Sysvinit|SysVInit]] that can easily take care of all the services installed by the system, like the NGINX reverse proxy and similar stuff. In general, it can handle directly anything you have installed via emerge.
What about thos (lots of) services you are installing manually because they are //not// in portage or will never be in portage (ex: docker/podman based services...)? Gentoo give you a neat way to add your own scripts under **/etc/local.d**.
Let me show how i am managing this approach.
*NOTE:* this approach _do not applies_ to podman containers. See [[gentoo:containers|Using Containers on Gentoo]] for more info on how i manage to autostart containers on boot.
===== local.d =====
Local.d is the last service running in the SysVInit world just before the system is all ready. You can add your own custom startup scripts under the folder **/etc/local.d** with the following syntax:
10-myservice.start
10-myservice.stop
and, if those script are executable (+x), then will be called at startup and shutdown respectively.
See [[https://wiki.gentoo.org/wiki//etc/local.d|here]] for more details.
Indeed this is __not__ considered officially a good place to do what i will show you should do, but YMMV and i find it a safer and less invasive way to start all our non-system services.
===== The Approach =====
You want to streamline deployment of services as much as possible, thus you do not want to manually manage tons of start/stop scripts.
One way would be to create your own init scripts and put them under /etc/init.d, which is mostly overkill for most of the services you will be installing. Moreover, i prefer to keep my system and my services as much separated as possible.
The other way is to use local.d service leveraging a specific script with symlinks: the idea is to use one script for most of the services, and just create a symlink to the start and stop scripts themselves.
For a simple non-containerized service called **mynormalservice** running as user **myotheruser**:
cd /etc/local.d
ln -s _servicer.sh 50-mynormalservice-myotheruser-service.start
ln -s _servicer.sh 50-mynormalservice-myotheruser-service.stop
This will require a file called //service_mynormalservice_start// //myotheruser// folder, like this:
COMMAND=/path/to/my/service/binary
ARGUMENTS=(my service arguments)
===== The Script =====
Leveraging great bash lore, here is the beast **/etc/local.d/_servicer.sh**:
#!/bin/bash
#
# Make a symlink to _servicer.sh with this syntax:
# - XX-myservice-user-type.action
#
# where:
# - XX is a number (ex: 10)
# - myservice will be the name of the service (it will create /var/run/myservice.pid for checking service status)
# - user is the user the service will run as. Can be omitted, in this case use two "--", and user=service
# - type is one of:
# - script: the service is managed by a script ($user_home/myservice_start.sh or $user_home/myservice.sh, and $user_home/myservice_stop.sh (optional))
# - service: the service command is specified inside a service_${service}_start file in the user home folder
# - action is either start or stop
#
#
LOG_PATH=/var/log/servicer
test ! -d "${LOG_PATH}" && mkdir "${LOG_PATH}"
actions_logs="${LOG_PATH}/servicer.log"
service_user_type_action=${0#*-}
SERVICE=${service_user_type_action%%-*}
user_type_action=${service_user_type_action#*-}
USER=${user_type_action%-*}
test -z ${USER} && USER=${SERVICE}
type_action=${user_type_action#*-}
TYPE=${type_action%.*}
ACTION=${type_action#*.}
passwd_entry=$(getent passwd ${USER}) || exit 255
USER_HOME=$(echo ${passwd_entry} | cut -d: -f 6)
echo "$(date): ${ACTION}-ing service '${SERVICE}' for user '${USER}', as '${TYPE}' (${USER_HOME})" >> ${actions_logs}
if [ "${ACTION}" = "start" ]
then
test -e "${LOG_PATH}/${SERVICE}" || {
mkdir "${LOG_PATH}/${SERVICE}"
} && chown -R ${USER} "${LOG_PATH}/${SERVICE}"
extra_opts=(-1 "${LOG_PATH}/${SERVICE}/${SERVICE}.out.log" -2 "${LOG_PATH}/${SERVICE}/${SERVICE}.err.log")
if [ "${TYPE}" = "script" ]
then
echo " ... checking for start scripts ..." >> ${actions_logs}
start_script="${USER_HOME}/${SERVICE}_start.sh"
test -e ${start_script} || start_script="${USER_HOME}/${SERVICE}.sh"
echo " ... detected '${start_script}' ..." >> ${actions_logs}
COMMAND="${start_script}"
ARGUMENTS=""
elif [ "${TYPE}" = "service" ]
then
echo " ... checking for config settings ..." >> ${actions_logs}
source_script="${USER_HOME}/service_${SERVICE}_start"
test -e "${source_script}" || {
echo "Error, missing '${source_script}'" >> ${actions_logs}
exit 255
}
echo " ... sourcing '${source_script}'..." >> ${actions_logs}
source "${source_script}"
fi
action=(-b -m --start "${COMMAND}" -- ${ARGUMENTS[@]})
elif [ "${ACTION}" = "stop" ]
then
extra_opts=()
if [ "${TYPE}" = "script" ]
then
echo " ... checking for stop script ..." >> ${actions_logs}
stop_script="${USER_HOME}/${SERVICE}_stop.sh"
test -e "${stop_script}" && {
echo " ... running '${stop_script}' ..." >> ${actions_logs}
su - ${USER} -c "${stop_script}"
}
elif [ "${TYPE}" = "service" ]
then
true
fi
action=(--stop ${SERVICE})
fi
echo start-stop-daemon -p /var/run/${SERVICE}.pid ${extra_opts[@]} -u ${USER} -d ${USER_HOME} ${action[@]} >> ${actions_logs}
start-stop-daemon -p /var/run/${SERVICE}.pid ${extra_opts[@]} -u ${USER} -d ${USER_HOME} ${action[@]}
echo "$(date): ${ACTION}-ed service '${SERVICE}' for user '${USER}', as '${TYPE}' (${USER_HOME})" >> ${actions_logs}
Save the script and make it executable.
===== How it works =====
The script takes the name of itself ($0) and dissects it to extract:
- The service name
- The user that must run the service
- The service type
- The action
Based on this information, it will automatically start or stop your services.
**Service name:** needs to be unique, will be used to create **/var/run/.pid** so that you can check your service status with other tools.
**User name:** which user shall run the service. Can be omitted (just leave two "--") and in this case a user with the same name as the service will be used. The user __must__ exist.
**Service type:** can be //service// or //script//:
- //service//: source the file $HOME/service__start and execute COMMAND ARGUMENTS (see example below)
- //script//: will run $HOME/_start.sh / $HOME/_stop.sh
Example **service__start**:
COMMAND="/home/myservice/bin/myservice_executable"
ARGUMENTS=(param1 param2 param3)
Please note that ARGUMENTS should be a bash array.
===== Adding a container-based service =====
It's easy, just create the symlink in /etc/local.d.
===== Logrotate =====
If you use (and you sohuld) LogRotate to keep your logs sanely rotated and trimmed, add the following **/etc/logrotate.d/servicer** file:
/var/log/servicer/*/.log{
missingok
}
/var/log/servicer/servicer.log{
missingok
}