Lancer une application au démarage de Linux avec systemd

Lancer une application au démarage de Linux avec systemd

Cette semaine, nous allons parler d'un sujet qui n'est pas uniquement lié aux modules Yoctopuce mais qui va sûrement intéresser nos lecteurs qui utilisent des Raspberry Pi ou d'autres systèmes Linux: Comment lancer une application automatiquement au démarrage de Linux.





Pour illustrer les différentes solutions possibles, nous allons écrire un petit programme qui affiche l'heure courante sur un YoctoDisplay connecté par USB. Pour cet article, nous allons utiliser un Raspberry Pi avec une image de Raspbian, mais n'importe quelle machine Linux avec une distribution récente devrait fonctionner de manière identique.

Note: Si vous n'êtes pas à l'aise avec Python ou notre librairie, nous avons un article qui explique comment utiliser nos modules depuis Python.

Ce petit programme est écrit en Python et utilise notre package pip "yoctopuce". Pour l'installer, il suffit d’exécuter la commande suivante:

sudo pip install yoctopuce



Le script Python est basique, on affiche quelques informations au démarrage de l'application et on initialise l'API Yoctopuce avec YAPI.RegisterHub(). Ensuite, on regarde s'il y a un YoctoDisplay connecté, si c'est le cas, on entre dans une boucle sans fin qui affiche l'heure au centre de l'écran.

Le script Python yclock.py:

#!/usr/bin/python
# -*- coding: utf-8 -*-

# import Yoctopuce Python library (installed form PyPI)
from yoctopuce.yocto_api import *
from yoctopuce.yocto_display import *
import getpass


def get_current_time():
    now = datetime.datetime.now()
    res = now.strftime('%a %d %b %Y / %X')
    return res


print("YClock v1.0")
print("Linux user : " + getpass.getuser())
print("Yoctopuce API : " + YAPI.GetAPIVersion())
print("Start time : " + get_current_time())

# Sets up the API to use local USB devices
errmsg = YRefParam()
if YAPI.RegisterHub("usb", errmsg) != YAPI.SUCCESS:
    sys.exit("RegisterHub error" + str(errmsg))

display = YDisplay.FirstDisplay()
if display is None:
    sys.exit("No YoctoDisplay connected")

try:
    display.resetAll()

    layer1 = display.get_displayLayer(1)  # type: YDisplayLayer
    layer2 = display.get_displayLayer(2)  # type: YDisplayLayer
    layer1.hide()
    layer2.unhide()

    w = display.get_displayWidth()
    h = display.get_displayHeight()
    while True:
        msg = get_current_time()
        layer1.clear()
        layer1.drawText(w / 2, h / 2, YDisplayLayer.ALIGN.CENTER, msg)
        display.swapLayerContent(1, 2)
        time.sleep(1)

except Exception as error:
    sys.exit(error)




Notez que, volontairement, ce script ne gère pas les déconnexions / reconnexions du YoctoDisplay. Si aucun écran n'est détecté, ou qu'il est débranché en cours d’exécution, la fonction sys.exit() est appelée avec le message d'erreur correspondant. Le but ici est d'illustrer comment gérer des erreurs du programme lors du boot de Linux. Dans une application utilisée en production, il serait plus efficace de gérer la connexion/déconnexion des modules Yoctopuce, par exemple à l'aide des fonctions YAPI.RegisterDeviceArrivalCallback() et YAPI.RegisterDeviceRemoveCallback().

Pour finir, il faut donner les droits d’exécution à notre script et vérifier qu'il fonctionne.

pi@raspberrypi:~/autostart $ chmod +x yclock.py pi@raspberrypi:~/autostart $ sudo ./yclock.py YClock v1.0 Linux user : root Yoctopuce API : 1.10.30760 (1.10.30760) Start time : Wed 23 May 2018 / 15:05:01 Start never ending loop



Maintenant que notre application de test fonctionne, il reste à configurer le Raspberry Pi pour qu'il exécute notre programme yclock.py au démarrage.

La solution basique: /etc/rc.local


La solution la plus basique pour lancer automatiquement un programme au démarrage est d’ajouter la ligne suivante dans le fichier /etc/rc.local, juste avant la ligne exit(0).

/home/pi/autostart/yclock.py > /dev/null 2>&1 &



Décortiquons ce que cette ligne veut dire...

  • /home/pi/autostart/yclock.py : C'est l’exécutable qui doit être lancé. Dans notre cas, il s’agit de notre fichier yclock.py. Attention à bien utiliser le path absolu du fichier.
  • > /dev/null : Redirige la sortie standard vers /dev/null
  • 2>&1 : redirige les messages d'erreurs sur la sortie standard (qui est déjà redirigée vers /dev/null)
  • & : Lance l’exécutable en tache de fond


Cette solution est acceptable pour les petits projets internes, car elle est très facile à configurer, nous l'utilisons du reste assez souvent en interne. Cependant, cette solution a quelques limitations qui la rendent difficilement recommandable pour un projet professionnel qui doit fonctionner en continu pendant des mois ou des années.

Premièrement, les messages de log et les éventuels messages d'erreurs sont ignorés, ce qui rend le débogage très compliqué. Une solution serait de rediriger les messages vers un fichier au lieu de /dev/null, mais ce fichier va grossir indéfiniment et si votre application est très verbeuse, il n'est pas impossible que le fichier grossisse jusqu'à ce que le disque soit complètement plein et fasse planter Linux.

Deuxièmement, si une erreur se produit, par exemple si le YoctoDisplay est débranché, le programme s’arrête et ne redémarre pas. Il faudra rebooter la machine pour que le système soit de nouveau utilisable.

Finalement, l'application est démarrée avec l'utilisateur root. Pour cet exemple, ce n'est pas très grave, mais pour des applications plus complexes qui sont connectées à Internet, cela peut poser des problèmes de sécurité.

Il est possible de contourner ces limitations avec des shell script mais c'est compliqué, et il existe une solution plus propre...


Une meilleure solution: systemd


La meilleure solution est d'utiliser le "nouveau" système d'initialisation de Linux systemd. Depuis 2015, ce système est utilisé par la grande majorité des distributions Linux, dont Debian, Ubuntu et Raspbian. Systemd remplace avantageusement le vieux system V, car il est à la fois plus simple à utiliser et offre plus de possibilités, comme par exemple de gérer les logs d’exécutions.

Chaque service généré par systemd est configuré par un fichier .service qui se trouve dans le répertoire /etc/systemd/system.

Pour notre exemple, il faut donc créer un fichier /etc/systemd/system/yclock.service avec au minimum le contenu suivant:

[Unit] Description=Affiche l'heure sur un YoctoDisplay [Service] Type=simple ExecStart=/home/pi/autostart/yclock.py [Install] WantedBy=multi-user.target



Le paramètre Description permet de définir une description qui est affichée lors de certaines commandes de systemctl.

Le paramètre ExecStart de la section Service définit la commande à exécuter pour démarrer l'application, dans notre cas il s'agit de notre script /home/pi/autostart/yclock.py. Le paramètre Type permet de choisir le type de service, à moins que votre application ne fasse des forks, la valeur "simple" est suffisante.

Enfin le paramètre WantedBy définit à quel moment du boot le script doit être démarré. La valeur "multi-user.target" est la valeur qui devrait correspondre à 99% des cas, c'est-à-dire juste avant l'écran de login quand le réseau et les autres services critiques ont déjà été démarrés.

L'utilitaire systemctl permet de contrôler systemd. Les principales commandes sont :

  • enable: active le service, c'est-à-dire que le service sera démarré lors des prochains boots.
  • disable: désactive le service, c'est-à-dire que le service sera ignoré lors des prochains boots.
  • status: affiche l’état courant du service.
  • start: démarre immédiatement le service.
  • stop: stope immédiatement le service.


Donc, pour que notre script démarre automatiquement lors des prochains boots, il faut exécuter la commande suivante:

sudo systemctl enable yclock.service



Tout de suite, on comprend un premier avantage de ce système: il est possible de stopper et redémarrer le service sans redémarrer l'OS. Ce qui permet par exemple de modifier le script Python sur un système en production. Mais ce n'est pas tout.

La commande journalctl permet d'afficher les logs d’exécution de notre programme. En plus, ces logs ne peuvent pas remplire complètement le disque car systemd stocke de la même manière tous les logs de l'OS. Donc, en fonction de la configuration de votre distribution, systemd gardera les X logs les plus récents du service, de manière à ne pas saturer le disque.

La commande pour afficher les logs de notre application yclock.py:

sudo journalctl -u yclock.service



D'autres paramètres du fichier permettent de configurer très finement comment et quand le service doit être démarré. Par exemple, le paramètre Restart permet de spécifier si le service doit être redémarré automatiquement lorsque l’exécutable se termine avec une erreur. Les paramètres User et Group permettent de choisir avec quel utilisateur et quel groupe le script sera exécuté.


Pour reprendre notre exemple, voici le fichier complet :

[Unit] Description=Affiche l'heure sur un YoctoDisplay [Service] Type=simple ExecStart=/home/pi/autostart/yclock.py Restart=on-failure RestartSec=30 User=pi Group=pi [Install] WantedBy=multi-user.target



Ce fichier précise en plus que le script doit être exécuté par l'utilisateur "pi" et le groupe "pi". De plus, si le script se termine avec un code d'erreur, par exemple si aucun YoctoDisplay n'est connecté, il faut essayer de le relancer après 30 secondes.

Si vous avez besoin de configuration plus pointue, vous pouvez toujours consulter la documentation du projet à cette adresse:https://freedesktop.org/wiki/Software/systemd

Conclusion


Nous avons réalisé un certain nombre de bricolages autonomes à base de Raspberry Pi et de modules Yoctopuce mais nous n'avions jamais expliqué comment démarrer automatiquement l'application. Nous espérons que ce petit article sur systemd vous facilitera la vie lors de la réalisation de systèmes Headless à base de Raspberry Pi, ou autres PC sous Linux.




Commenter un commentaire Retour au blog



1 - filax Mardi 30 octobre 2018 20H19

Merci pour cette infos ! Je cherche un truc dans le genre depuis des jours....





Yoctopuce, get your stuff connected.