Créer un daemon sous FreeBSD

Transformer un programme en daemon (service) avec un script en utilisant le framework rc.subr

Il peut être parfois utile de transformer un programme en daemon (service) afin de le lancer automatiquement au démarrage et de le gérer comme n'importe quel autre service.

Ce programme n'est pas prévu au départ pour gérer les commandes start, stop, status, etc., tout comme il ne sait pas retourner son PID.

Il convient donc de créer le script qui va s'en charger.

Principe

FreeBSD propose en standard un framework rc.subr qui permet d'appeler un certain nombre de fonctions standard. Ces fonctions sont start, stop, status, usage, etc.

Cependant, dans le cas d'un programme qui n'a pas été pensé pour être un daemon, ces fonctionnalités n'existent pas. Il faut donc les surcharger par de nouvelles fonctions.

Par exemple, la commande start sera surchargée de la façon suivante :

start_cmd="app_start"

app_start()
{
echo "Démarrage de mon application..."
[ mes commandes ici ]
}

Et ainsi de suite pour chaque fonction à surcharger.

Il ne reste plus qu'à placer le script dans le répertoire rc.d.

astuce !

Le programme daemon est très utile pour transformer exécuter un programme standard en tâche de fond.

Il possède les options :

  • -f : cacher le mode console et rediriger toutes les sorties vers /dev/null.

  • -p <pidfile> : créer un fichier contenant le pid du service exécuté.

Mise en oeuvre

Un script générique

Par défaut, nous considérerons que le programme à lancer ne possède pas de fonction avancée de gestion de service. Pour le lancer, il faut exécuter le programme. Pour l'arrêter, il suffit de tuer le processus.

Pour transformer ce programme en service, il convient de créer le script de la façon suivante :

Réalisation du script

Tout d'abord, l'appel du framework :

. /etc/rc.subr

puis la définition des nouvelles fonctions :

getpid()
{
ps -aux | grep "${search_pattern}" | awk -v pid="${1:-0}" '$2==pid {print $2}'
}

app_status()
{
[ -f ${pidfile} ] && pid_file=$(cat ${pidfile})
pid_run=$(getpid ${pid_file})
if [ ${pid_file:-0} -eq ${pid_run:-1} ]
then
echo "STATUS : ${name} is running (on PID ${pid_run})"
else
echo "STATUS : ${name} is NOT running"
fi
}

app_start()
{
echo "Hello !"
echo "Starting ${name}..."
daemon ${flags}
}

app_start_postcmd()
{
[ -f ${pidfile} ] && pid_file=$(cat ${pidfile})
pid_run=$(getpid ${pid_file})
if [ ${pid_file:-0} -eq ${pid_run:-1} ]
then
echo "${name} started !"
else
echo "${name} cannot be started"
fi
}

app_stop()
{
echo "Stopping ${name}..."
[ -f ${pidfile} ] && pid_file=$(cat ${pidfile})
pid_run=$(getpid ${pid_file})
if [ ${pid_file:-0} -eq ${pid_run:-1} ]
then
kill -9 ${pid_run}
fi
}

app_stop_postcmd() {
[ -f ${pidfile} ] && pid_file=$(cat ${pidfile})
pid_run=$(getpid ${pid_file})
if [ "${pid_run}" = "" ]
then
[ -f ${pidfile} ] && rm -f ${pidfile}
echo "${name} stopped"
echo "Goodbye !"
else
echo "${name} cannot be stopped"
fi
}

Astuce !

Vous pouvez également étendre les possibilités de votre script en ajoutant des fonctions personnelles :

mdm_kiss()
{
echo -n "A ghost gives you a kiss"
}

qui sera affecté de la façon suivante  :

extra_commands="kiss"
kiss_cmd="${name}_kiss"

il ne restera qu'à l'appeler avec l'option kiss.

ensuite, la redéfinition des fonctions :

start_cmd="app_start"
stop_cmd="app_stop"
start_postcmd="app_start_postcmd"
stop_postcmd="app_stop_postcmd"
status_cmd="app_status"

puis la définition des variables applicatives spécifiques au programme à lancer :

app_home="..."
name="..."
runcmd="${app_home}/bin/..."
search_pattern="..."

et les variables du framework :

pidfile="/var/run/${name}.pid"
rcvar="${name}_enable"
command="daemon"
flags="-f -p ${pidfile} ${runcmd}"

Achever par les fonctions d'appel standard du framework :

load_rc_config ${name}
run_rc_command "$1"

Le script générique est achevé !

Mise en place

Copier et renommer le script générique, puis définir les variables applicatives, par exemple pour Talend MDM :

app_home="/usr/local/TOS_MDM-Server-r82787-V5.1.0/jboss-4.2.2.GA"
name="mdm"
runcmd="${app_home}/bin/run.sh"
search_pattern="TOS_MDM"

Copier le script dans le répertoire rc.d et ajouter la ligne suivante dans le fichier /etc/rc.conf :

mdm_enable="YES"

L'appel mdm_enable doit correspondre à la définition du script :

name="mdm"
...
rcvar="${name}_enable"
...

Vous pouvez dès à présent démarrer votre service en appelant le script :

mdm start

Quelques exemples

Le script générique permet de ne définir que les variables applicatives suivantes.

Talend MDM (mdm)

Le Talend MDM Server est un programme JAVA qui s'exécute en mode console et qu'il faut lancer manuellement. Notre script générique peut donc le gérer, en définissant les variables suivantes :

app_home="/usr/local/TOS_MDM-Server-r82787-V5.1.0/jboss-4.2.2.GA"
name="mdm"
search_pattern="TOS_MDM"
runcmd="${app_home}/bin/run.sh"

Apache Directory Server (apacheds)

app_home="/usr/local/apacheds-2.0.0-M6"
name="apacheds"
search_pattern="apacheds"
runcmd="${app_home}/bin/apacheds.sh"

Apache ActiveMQ

ActiveMQ a déjà tout ce qu'il faut pour le gérer comme un service : start, stop, restart, status... Il suffit juste de l'encapsuler dans un script de service, avec les variables :

app_home="/usr/local/apache-activemq-5.6.0"
name="activemq"
search_pattern="\[java\]"
pidfile="${app_home}/data/activemq-minix.pid"
runcmd="${app_home}/bin/activemq"

Et de redéfinir les fonctions complémentaires app_start() :

app_start()
{
echo "Hello !"
echo "Starting ${name}..."
nohup ${runcmd} start > /tmp/smlog 2>&1 &
sleep 3
}

et app_stop() :

app_stop()
{
echo "Stopping ${name}..."
[ -f ${pidfile} ] && pid_file=$(cat ${pidfile})
pid_run=$(getpid ${pid_file})
if [ ${pid_file:-0} -eq ${pid_run:-1} ]
then
nohup ${runcmd} stop > /tmp/smlog 2>&1
fi
}

yacy

yacy est un programme JAVA qui s'exécute en mode daemon et qu'il faut lancer manuellement. L'exécution nécessite un compte non administrateur à votre convenance qu'il faudra éventuellement créer (ici yacyuser).

app_home="/usr/local/yacy"
name="yacy"
search_pattern="\[java\]"
pidfile="${app_home}/${name}.pid"
exec_user="yacyuser"

Et de redéfinir les fonctions complémentaires app_start() :

app_start() {
echo "Hello !"
echo "Starting ${name}..."
su -l ${exec_user} -c ${app_home}/startYACY.sh
}

app_stop() :

app_stop() {
echo "Stopping ${name}..."
${app_home}/stopYACY.sh
}

app_stop_postcmd() {
[ -f ${pidfile} ] && rm -f ${pidfile}
echo "${name} stopped"
echo "Goodbye !"
}

app_restart() :

app_kill() {
#${app_home}/killYACY.sh
echo "Stopping ${name}..."
cat ${pidfile} | xargs kill -9
}

app_restart() {
echo "Restarting ${name}..."
app_kill
app_start
}

télécharger un exemple complet.