.. -*- mode: rst -*- .. -*- coding: utf-8 -*- =========================================== Découverte de buildroot avec un raspberrypi =========================================== :date: 2021-11-15 :summary: Comment construire un système complet pour un raspberry pi avec Buildroot ? Présentation détaillée d’un projet permettant de construire un serveur de son. .. _`configs/config`: https://git.chimrod.com/piaudio.git/tree/configs/config .. _`build.sh`: https://git.chimrod.com/piaudio.git/tree/build.sh .. _`post-build.sh`: https://git.chimrod.com/piaudio.git/tree/board/post-build.sh .. _`post-image.sh`: https://git.chimrod.com/piaudio.git/tree/board/post-image.sh Présentation ============ Il y a quelque temps, j’avais monté un système audio avec un raspberrypi pi 0, permettant de brancher la carte à ma chaine hifi et diffuser ainsi la musique dans le salon. En équipant la carte d’un DAC de qualité, je centralise la musique qui peut etre jouée depuis n’importe quel PC, et comme la carte est équipée d’une connexion bluetooth, cela permet aussi d’y connecter les smartphones. Dans mon `montage originel`_, je partais d’une distribution raspbian que j’avais modifié manuellement. À la suite d’une erreur de manipulation, j’ai dû refaire le paramétrage une seconde fois, et c’est depuis quelque chose qui me préoccupait : il faudrait que je puisse disposer d’un système déjà configuré qui fonctionnerait « out of the box », sans que j’aie à toucher à la configuration. .. _`montage originel`: https://linuxfr.org/users/chimrod/journaux/utiliser-un-pi-zero-comme-serveur-de-son-et-lecteur-bluetooth J’ai découvert entre-temps buildroot_ (via le projet showmewebcam_) qui est un ensemble de scripts permettant de construire un système complet. Il prend en charge un grand nombre d’architectures différentes, et possède une configuration de base pour construire les applications les plus courantes. Il ne reste plus qu’à l’utiliser pour reconstruire un client pulseaudio sur une carte raspberrypi 0, qui offre un service via le wifi et le bluetooth. .. _buildroot: https://buildroot.org/ .. _showmewebcam: https://github.com/showmewebcam/showmewebcam/ Je vous propose ici de suivre le détail des modifications apportées pour construire le système (le projet est disponible sur mon `dépôt git`_ – mais pas l’image finale). .. _`dépôt git`: https://git.chimrod.com/piaudio.git/about/ .. admonition:: Article avancé :class: warning Je ne pensais pas qu'un jour j'écrirais ce genre d'en tête, mais il s'agit ici d'un article avancé, qui nécessite de connaître le fonctionnement du système linux. Nous allons en effet construire un système de zéro, pour un usage spécifique, ce qui oblige à sélectionner les services dont nous avons besoin (et savoir lesquels sélectionner), et comment les configurer pour obtenir le système attendu. Aucune interface graphique ne sera installée sur la carte, et toute la configuration devra être anticipée pour que la carte démarre dès son branchement. Construction du système ======================= Initialisation -------------- Je fournis un script `build.sh`_ qui prends en paramètres : 1. une architecture 2. une commande buildroot (`all` pour lancer la création complète du système) pour l’instant, seules deux architectures ont été testées : un raspberrypi3 (modèle A), et un raspberrypi0w : .. code:: bash ./build.sh usage: BUILDROOT_DIR=../buildroot ./build.sh {boardname} all boardname: raspberrypi0w, raspberrypi3 Lors de l’exécution de la commande, le système va fusionner le paramétrage par défaut de la carte, qui est chargé dans la configuration de buildroot, ainsi qu’une liste de paquets spécifiques qui sont propres à notre installation, et listés dans le fichier `configs/config`_ Sélection des paquets --------------------- Cette configuration par défaut inclue les paquets nécessaires pour faire fonctionner le réseau (wifi), pulseaudio, et le bluetooth (ainsi que les dépendances pour mettre toute cette architecture en place). Si l’on souhaite modifier un paquet, on peut regarder ceux disponibles en lançant une interface de sélection : .. code:: bash ./build.sh raspberrypi3 menuconfig .. image:: {static}/images/buildroot/buildroot.png :alt: L’interface menuconfig :align: center Toutefois, je n’aime pas cette manière de procéder : la configuration ainsi générée se retrouve spécifique à une carte donnée, et l’on perd la possibilité d’appliquer cette même configuration sur un matériel différent. Aussi, et pour être sûr d’avoir un système qui soit reproductible, je préfère noter les paquets dont j’ai besoin, et je les reporte dans le fichier `configs/config`_ qui sera utilisé lors de la prochaine construction. Configuration du système ------------------------ En parallèle de la construction du système, nous avons à nous occuper de sa configuration initiale. Cela est réalisé à travers deux scripts, le premier, `post-build.sh`_, est exécuté après l’installation des paquets, le second, `post-image.sh`_ après la finalisation de l’image finale. Dans le premier nous allons modifier les fichiers de configuration du système, alors que dans le second nous traiterons les paramètres donnés lors du boot. Je présente ci-dessous quelques modifications apportées au système pour le faire correspondre exactement au besoin : Carte en lecture seule ~~~~~~~~~~~~~~~~~~~~~~ Étant donné que nous n’aurons pas d’interface pour nous connecter à la carte, elle va etre éteinte « sauvagement » régulièrement. Je préfère donc la basculer en lecture seule, ce qui permet aussi d’etre sûr que tout le système restera immuable sur la durée. .. code-block:: bash sed -ie '/^\/dev\/root/ s/rw 0 1/ro 0 0/' "${TARGET_DIR}/etc/fstab" La modification est faite avec `sed`, et permet de suite de voir un peu comment on va modifier le système : * Chaque modification sera faite à l’aide des outils disponibles dans bash (sed, cp, cat, …), il faut donc déjà etre à l’aise avec le terminal avant d’aller modifier la configuration * Chaque command nécessite de savoir comment sont construits les fichiers que l’on veut modifier * Dans le cas où nous ajoutons des nouvelles entrées dans un fichier, il faudra contrôler *avant* que cette entrée n’est pas déjà présente * Et enfin, nous allons avoir du boulot pour modifier chaque élément un à un… La variable `${TARGET_DIR}` est fournie par buildroot et permet de cibler l’image que nous sommes en train de construire (par opposition au système hote dans lequel nous compilons le système). Ici, nous modifions bien le fichier `/etc/fstab` final, et non pas notre système linux ! .. admonition:: Bonne pratique :class: hint Au vu des risques en cas d’erreur de saisie, il est bon de rajouter cette ligne en début de script : .. code-block:: bash # Traite les variables non définies comme des erreurs lors de la substitution. set -u Nous serons ainsi sûrs de ne pas modifier notre système par erreur (ce qui ne devrait pas arriver, car nous ne travaillons pas sous `root`) Ajout du wifi ~~~~~~~~~~~~~ Pour activer le wifi, nous allons laisser systemd faire le travail, `wpa_supplicant` propose en effet un service paramétré, qui active l’interface spécifiée dans le nom (ici wlan0), il suffit donc de créer un lien symbolique pour que celui-ci soit activé au démarrage : .. code-block:: bash # Create the link to interface wlan0 directly in the system configuration ln -sf /usr/lib/systemd/system/wpa_supplicant@.service "${TARGET_DIR}/usr/lib/systemd/system/multi-user.target.wants/wpa_supplicant@wlan0.service" (par contre, cela implique d’installer systemd sur la carte, ce qui surcharge les dépendances, mais cela simplifie notre travail finalement…) La configuration de la carte est comme d’habitude dans un fichier `wpa_supplicant.conf` qui n’est pas versionné, mais qui sera copié s’il est présent dans le répertoire. .. code-block:: bash if [ -f "$BR2_EXTERNAL_PIAUDIO_PATH/wpa_supplicant.conf" ]; then cat "$BR2_EXTERNAL_PIAUDIO_PATH/wpa_supplicant.conf" > "${TARGET_DIR}/etc/wpa_supplicant/wpa_supplicant-wlan0.conf" … fi La variable `$BR2_EXTERNAL_PIAUDIO_PATH` correspond au répertoire de base du système, c’est-à-dire notre répertoire racine. Il s’agit également d’une autre variable d’environnement fournie par buildroot qui nous permet de ne pas avoir à nous soucier de l’emplacement du script quand nous lançons notre compilation. Pulseaudio ~~~~~~~~~~ Vient ensuite pulseaudio. Nous n’avons pas à nous occuper de le démarrer avec le système car cela est fourni par un paquet ayant été sélectionné. Par contre, puisque le système est en lecture seule, nous avons quelques modifications à réaliser pour que le service fonctionne : nous allons créer un répertoire, et le monter en mémoire au démarrage afin que pulseaudio puisse y stocker ses fichiers : .. code-block:: bash create_missing_dir "/var/lib/pulse" if ! grep -qE '/var/lib/pulse' "${TARGET_DIR}/etc/fstab"; then cat << __EOF__ >> "${TARGET_DIR}/etc/fstab" tmpfs /var/lib/pulse tmpfs rw 0 0 __EOF__ fi Ensuite, nous allons modifier le fichier `/etc/pulse/system.pa` afin d’y ajouter les modules bluetooth, et la connexion distante : .. code-block:: bash if ! grep -qE '^load-module module-native-protocol-tcp' "${TARGET_DIR}/etc/pulse/system.pa"; then cat << __EOF__ >> "${TARGET_DIR}/etc/pulse/system.pa" load-module module-bluetooth-policy load-module module-bluetooth-discover load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1;192.168.0.0/24;2a01:e35:8ac8:0e00::/64 auth-anonymous=1 __EOF__ fi Bluetooth ~~~~~~~~~ D’abord, pour activer le bluetooth, nous allons indiquer au noyau de le charger au démarrage : .. code-block:: bash cat << __EOF__ >> "${BINARIES_DIR}/rpi-firmware/config.txt" dtparam=krnbt=on __EOF__ Cette option ajoutée récemment dans les raspberrypi va nous éviter d’avoir à lancer les commande `bt-attach` ou `hciattach`. le bluetooth est automatiquement branché sur le port uart, à la vitesse maximale prise en charge par la carte. Cela résoud en une ligne énormément de problèmes rencontrés lors de la configuration du bluetooth si l’on avait voulu faire cela manuellement. Voir la liste des options possibles sur la page suivante : https://github.com/raspberrypi/firmware/tree/master/boot/overlays Par contre, il faut libérer pour cela la connexion série, qui est configurée par défaut sur le port `ttyAMA0`. Il nous faut supprimer la ligne suivante dans le fichier `cmdline.txt` : `console=ttyAMA0,115200` Ensuite, comme précédemment, nous allons également créer un répertoire en mémoire pour pallier le système en lecture seule de la carte : .. code-block:: bash create_missing_dir "/var/lib/bluetooth/" if ! grep -qE '/var/lib/bluetooth' "${TARGET_DIR}/etc/fstab"; then cat << __EOF__ >> "${TARGET_DIR}/etc/fstab" tmpfs /var/lib/bluetooth tmpfs rw 0 0 __EOF__ fi La configuration du bluetooth est lue dans le fichier `/etc/bluetooth/main.conf`, c’est donc là que nous allons y stocker les paramètres génériques : classe de l’appareil et timeouts : .. code-block:: ini [General] Class = 200428 DiscoverableTimeout = 0 PairableTimeout = 0 [Policy] AutoEnable=true Enfin, nous avons besoin d’autoriser les connexions externes, et ce, sans aucun accès au système. Cela s’effectue par le biais d’un *agent* qui demande la confirmation d’un code auprès des deux parties (celui qui veut se connecter, et celui qui accepte les connexions) pour éviter les erreurs. Ici, nous faire fi de la sécurité, et ouvrir les connexions à n’importe qui. Les risques sont limités (le système est en lecture seule, aucune des applications de base permettant de détourner l’ordinateur n’est installée). C’est un problème que j’avais rencontré dans mon système d’origine, et qui nécessitait de se connecter sur le raspberrypi pour autoriser le périphérique. J’ai finalement trouvé une solution (qui se résume en quelques lignes, mais qui m’a occupé quelques heures avant d’en arriver là). On va se créer un service *systemd* (puisqu’il est là…) qui lance un agent perpétuel chargé d’accepter toutes les connexions : .. code-block:: bash cat << __EOF__ > "${TARGET_DIR}/etc/systemd/system/bt-agent.service" [Unit] Description=Bluetooth Agent After=bt-audio.service Requires=bt-audio.service [Service] Type=simple ExecStartPre=bt-adapter --set Discoverable 1 ExecStart=bt-agent -c NoInputNoOutput RestartSec=5 Restart=always KillSignal=SIGUSR1 [Install] WantedBy=bluetooth.target __EOF__ Au passage, ce service déclare le bluetooth en mode « découvrable », ce qui rend vraiment le service ouvert à tous. Connexion série ~~~~~~~~~~~~~~~ Enfin, il faut que nous puissions nous connecter à la carte pour contrôler qu’elle démarre correctement. Or, nous n’avons installé aucun serveur ssh : celle-ci est donc vérouillée. Par contre, le raspberrypi 0 nous offre une chance, puisqu’il est possible de s’y connecter en le branchant directement en usb, mais encore faut-il activer cette option au démarrage… .. note:: Cela fonctionne également avec un raspberrypi 4 ou un raspberrypi 3 A, mais pour ce dernier, il faut trouver un cable USB male/male pour faire la connexion. Dans mes tests avec ce dernier, j’ai triché et installé dropbear qui est un serveur ssh ! Il suffit d’ajouter une ligne dans le fichier `cmdline.txt` et le tour est joué : .. code-block:: bash if ! grep -qE 'modules-load=dwc2,g_serial' "${BINARIES_DIR}/rpi-firmware/cmdline.txt"; then sed '/^root=/ s/$/ modules-load=dwc2,g_serial/' -i "${BINARIES_DIR}/rpi-firmware/cmdline.txt" fi Build ----- Il ne nous reste plus qu’à lancer la compilation du système, et attendre le résultat : .. code:: bash ./build.sh raspberrypi0w all Une fois terminé, on peut copier l’image générée et la brancher dans le raspberry pi .. code:: bash sudo dd if=output/raspberrypi0w/images/sdcard.img of=/dev/XXX bs=4k Démarrage et premier lancement ============================== .. image:: {static}/images/buildroot/connexion.jpeg :alt: La carte connectée en USB :align: center Une fois la carte branchée, on peut se connecter dessus à l’aide de la commande suivante : .. code:: bash screen /dev/ttyACM0 Récupérer le nom de la connexion pulseaudio ------------------------------------------- .. code:: console # su -s /usr/bin/sh pulse $ pactl info … Default Sink: alsa_output.0.stereo-fallback C'est cette valeur qui est à utiliser dans le tunnel que nous allons mettre en place. Sur notre pc de bureau, nous allons modifier le fichier `/etc/pulse/default.pa` et ajouter cette ligne : .. code:: bash load-module module-tunnel-sink sink_name=rpi_tunnel server=tcp:${IP}:4713 sink=alsa_output.0.stereo-fallback Comme notre raspberrypi se connecte en DHCP, son adresse IP est fournie par le routeur. Je conseille de lui associer une IP fixe afin que la configuration ne change pas :) .. image:: {static}/images/buildroot/pulseaudio.png :alt: La connexion pulseaudio :align: center Nommage du service bluetooth ---------------------------- Un dernier point que je n’ai pas réussi à comprendre, le nom du service bluetooth est `Bluez` au lieu du nom de la carte (`piaudio`). Cela semble être du au fait que la carte soit en lecture seule. La solution consiste à basculer la carte en écriture, relancer le service bluetooth, et redémarrer : .. code:: bash mount -o remount,rw / systemctl restart bluetooth reboot Cette opération est à faire une fois seulement, il n’est pas nécessaire d’y revenir ensuite. Se connecter en bluetooth ------------------------- .. image:: {static}/images/buildroot/bluetooth.png :alt: La connexion bluetooth :align: center Comme nous avons tout automatisé, c’est encore plus simple ! en quelques clics l’association est faite !