2026-08-12
alias, tag et match
Imaginons le scénario suivant:
- je dois me connecter à 3 domaines: foo.fr, bar.org et baz.com
- chaque domaine est divisé en 2 sous-domaines: .dev et .prod
- je dois me connecter avec mon login actuel, un login spécifique (“pouet”) et en “root” (pas la peine de gnagna root gnagna pabien: je sais)
- le nommage des machines sur lesquelles je dois me connecter est identique: ‘vm’ + ‘-’ + service (où service est dans la liste http, cache, db)
Un extrait des connexions possibles:
$ ssh vm-http.dev.foo.fr
$ ssh pouet@vm-cache.prod.bar.org
$ ssh root@vm-db.dev.baz.com
La complétion
On peut se simplifier la vie en créant des fichiers correspondant aux connexions:
$ mkdir .ssh/completion/ && cd .ssh/completion
$ touch vm-http.dev.foo.fr pouet@vm-cache.prod.bar.org root@vm-db.dev.baz.com ...
et compléter la commande ssh avec la liste de ces fichiers. Inconvénient: la touche Tab va passer un sale quart d’heure.
Mon idéal
Il faudrait avoir une “commande” par domaine: foo pour se connecter à foo.fr, bar pour bar.org et baz pour baz.com . En rajoutant une lettre (minuscule et/ou majuscule ?) pour le sous-domaine, on aurait Pfoo pour se connecter à .prod.foo.fr et dbaz pour .dev.baz.com .
Pour les logins et les machines, il ne faudrait préciser que le service et utiliser une lettre pour le login: Rdb pour root@vm-db, pcache pour pouet@vm-cache, http pour vm-http .
En assemblant le tout, l’extrait des connexions possibles deviendrait:
$ dfoo http
$ Pbar pcache
$ dbaz Rdb
Le mixte minuscule/majuscule n’est là que pour illustrer le propos et pourrait ici se traduire par “si c’est important/risqué/dangereux alors c’est en majuscule”.
Match
Une de mes utilisations de cette directive de mon .ssh/config est de définir des “raccourcis”:
Match originalhost owrt
HostName openwrt.mon.domaine.que.j.ai
mais si je veux pouvoir préciser un éventuel login, je dois être un peu plus générique:
Match originalhost *db
HostName vm-db
qu’on traduira par “si le nom de machine de la commande ssh se termine par db alors le nom de machine à utiliser est vm-db”. J’utilise la même chose pour les logins:
Match originalhost p*
User pouet
Match originalhost R*
User root
À noter que le “p” passerait en majuscule (pour bien le distinguer du nom de machine) dans le cas où un service commencerait par “p” (proxy, pgsql …).
Tag
C’est avec cette fonctionnalité que je vais résoudre en partie la simplification domaine / sous-domaine. Il est possible de passer un “tag” à une connexion à l’aide du paramètre -P tag et de définir des valeurs par “tag”:
Match tagged old
KexAlgorithms diffie-hellman-group1-sha1
Pour vérifier si la directive est prise en compte:
$ ssh -P old -G pouet | grep ^kexalgorithms
kexalgorithms diffie-hellman-group1-sha1
Chaque domaine / sous-domaine va correspondre à un “tag” qui va définir le nom de domaine:
Match tagged devfoo
CanonicalDomains .dev.foo.fr
...
Match tagged prodbaz
CanonicalDomains .prod.baz.com
Je dois aussi forcer les machines commençant par “vm-” à utiliser un nom de domaine:
Match host vm-*
CanonicalizeHostname yes
Je vérifie mes combinaisons “tag” / “host”:
$ ssh -G -P devfoo Rdb | grep -e '^user ' -e '^hostname '
user root
hostname vm-db.dev.foo.fr
Attention, une connexion ne peut avoir qu’un seul “tag” et l’ordre des directives est important !
Alias
Je n’ai plus qu’à définir les alias:
alias dfoo ssh -P devfoo
alias Pfoo ssh -P prodfoo
...
alias Pbaz ssh -P prodbaz
et attendre que mes doigts mémorisent tout ça.
Un petit pour la route
Parce que la vie n’est qu’une longue suite d’exceptions, imaginons qu’une machine d’un sous-domaine ne respecte pas la convention de nommage:
# putain de vs-database de mes couilles
Match tagged devbar originalhost *db
HostName vs-database
Match originalhost *db
HostName vm-db
Tiré d’un exemple @TAF (et oui, le commentaire est d’origine).
Commentaires: https://github.com/bsdsx/blog_posts/issues/24
2026-08-08
Bienvenue à lowdown !
Mon générateur de blog statique utilisait jusqu’ici un script awk pour transformer du markdown en html. Cela fait quelques temps déjà que j’utilise l’excellent lowdown et son option -tterm pour documenter des projets. Ainsi mes Makefile contiennent désormais une cible doc qui exécute
lowdown -tterm README.md | less -R
lowdown ne manque pas de fonctionnalités parmis lesquelles une sortie html bien plus complète que mon script awk. Métadonnées et gabarits vont clouer définitivement le cercueil de mon générateur.
Générer du html
lowdown a le bon goût d’utiliser l’entrée standard (bien utile pour vérifier le comportement d’une option):
$ echo '## titre de niveau 2' | lowdown -thtml
<h2 id="titre-de-niveau-2">titre de niveau 2</h2>
$ echo '## titre de niveau 2' | lowdown -thtml --html-no-head-ids
<h2>titre de niveau 2</h2>
Générer une page html
Pour obtenir une page, il faut utiliser l’option -s (ou --out-standalone) qui va utiliser un gabarit par défaut:
$ printf '## titre de niveau 2\nbla bla\nbla bla' | lowdown -thtml --html-no-head-ids -s
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title></title>
</head>
<body>
<h2>titre de niveau 2</h2>
<p>bla bla
bla bla</p>
</body>
</html>
On peut préciser un autre gabarit à l’aide de l’option --template:
$ cat /tmp/tmpl.tmpl
<!DOCTYPE html>
<html>
<body>
$body$
</body>
</html>
$ printf '## titre de niveau 2\nbla bla\nbla bla' | lowdown -thtml --html-no-head-ids -s --template /tmp/tmpl.tmpl
<!DOCTYPE html>
<html>
<body>
<h2>titre de niveau 2</h2>
<p>bla bla
bla bla</p>
</body>
</html>
La magie opère avec les métadonnées:
$ cat /tmp/tmpl.tmpl
<!DOCTYPE html>
<html>
<head><title>$title$</title></head>
<body>
$body$
</body>
</html>
$ cat /tmp/md.md
---
title: mon super titre
---
## Niveau 2
bla bla bla
$ lowdown -thtml --html-no-head-ids -s --template /tmp/tmpl.tmpl /tmp/md.md
<!DOCTYPE html>
<html>
<head><title>mon super titre</title></head>
<body>
<h2>Niveau 2</h2>
<p>bla bla bla</p>
</body>
</html>
Mon blog
Une page du blog est composée de 3 parties:
- liste des mots clefs et quelques liens
- un (des) billet(s)
- archives et quelques liens
Le blog est physiquement organisé de la façon suivante:
- ./assets: css et logos (et non, pas de javascript)
- ./src: billets au format markdown
- ./tmpl: gabarits
- ./www/YYYY/MM: pages des billets
- ./www/tags: répertoires des mots clefs et liens symboliques
Un fichier blog.md contient les métadonnées du blog:
$ cat blog.md
---
blog_title: Mon ch'ti blog
blog_description: Unix, libre, toussa ...
blog_url: http://blog.bsdsx.fr
blog_author: bsdsx
blog_root: /
---
Ajouter un billet
L’ajout d’un billet va:
- extraire sa date depuis le nom du fichier et la liste des mots clefs depuis ses métadonnées
- créer des répertoires (un par date et par mot clef)
- générer le rendu des différentes parties d’une page du blog
- générer les pages html (billet, archive, mots clefs)
- générer les flux atom et rss
Le rendu du billet
Le rendu du billet src/2026-08-08_orke.md sera stocké dans www/2026/08/2026-08-08_orke.md.htm et généré avec la commande:
lowdown -thtml $LOWDOWN_OPTS -M "date: $YYYY-$MM-$DD" -M "post_url: /$YYYY/$MM/$SRC.html" -s --template tmpl/post.tmpl src/$SRC > $ARCHIVES/$SRC.htm
Le gabarit est très simple:
$ cat tmp.post.tmpl
<p class="date">$date$</p>
<p class="tags">[$for(tags.split)$ <a href="/tags/$this$">$this$</a>$endfor$ ]</p>
<h2>$title$</h2>
$body$
<p>Commentaires: <a target="_blank" href="https://github.com/bsdsx/blog_posts/issues/$id_issue$">https://github.com/bsdsx/blog_posts/issues/$id_issue$</a></p>
<p class="post"><a href="$post_url$" target="_blank">Lien vers ce billet</a></p>
<hr>
Je distingue le rendu du billet (.htm) de la page du billet (.html). Faire cette distinction va me permettre de concaténer plusieurs rendus de billet au sein d’une même page.
Les mots clefs
Pour chaque mot clef, créer un répertoire et un lien vers le rendu du billet:
TAGS=$(lowdown -X tags src/$SRC)
for tag in $TAGS; do
mkdir -p www/tags/$tag
(cd www/tags/$tag; ln -fs ../../$YYYY/$MM/$SRC.htm .)
done
Le rendu des mots clefs (partie gauche d’une page):
OUT=www/.tags.htm
echo '<div id="left"><div id="cloud_tags"><p>Tags</p>' > $OUT
for tag in $(find www/tags -type d -mindepth 1 -printf '%f\n' | sort); do
printf '<a href="/tags/%s/index.html">%s</a>\n' $tag $tag >> $OUT
done
echo '</div>' >> $OUT
lowdown -thtml -s --template tmpl/left_links.tmpl blog.md >> $OUT
echo '</div><!-- end left -->' >> $OUT
Les archives
Le rendu de la partie droite:
OUT=www/.archives.htm
echo '<div id="right"><div id="archives"><p>Archives</p>' > $OUT
for yyyy_mm in $(find www/2* -type d -mindepth 1 | sort -r); do
yyyy_mm=${yyyy_mm#www/}
printf '<a href="/%s/index.html">%s</a>\n' $yyyy_mm $yyyy_mm >> $OUT
done
echo '</div>' >> $OUT
lowdown -thtml -s --template tmpl/right_links.tmpl blog.md >> $OUT
echo '</div><!-- end right -->' >> $OUT
Début et fin de page
Génération du début d’une page à l’aide des métadonnées du blog:
OUT=www/.before_main.htm
lowdown -thtml -s --template tmpl/top.tmpl blog.md > $OUT
cat www/.tags.htm www/.archives.htm >> $OUT
echo '<div id="main">' >> $OUT
OUT=www/.after_main.htm
echo '</div><!-- end main -->' > $OUT
lowdown -thtml -s --template tmpl/bottom.tmpl blog.md >> $OUT
Arrivé ici le rendu des différentes parties est terminé, reste à générer les pages html complètes.
Page des mots clefs
Concaténer le contenu des liens de chaque mot clef:
for tag in $TAGS; do
OUT=www/tags/$tag/.index.html
HTMS=$(find www/tags/$tag/ -type l | sort -r | tr '\n' ' ')
cat www/.before_main.htm $HTMS www/.after_main.htm > $OUT
mv $OUT www/tags/$tag/index.html
done
Page d’archive
Concaténer les rendus de billet:
OUT=$ARCHIVES/.index.html
cat www/.before_main.htm $ARCHIVES/*.htm www/.after_main.htm > $OUT
mv $OUT $ARCHIVES/index.html
Page d’un billet
Identique à une page d’archive mais avec un seul billet:
OUT=$ARCHIVES/.$SRC.html
cat www/.before_main.htm $ARCHIVES/$SRC.htm www/.after_main.htm > $OUT
mv $OUT $ARCHIVES/$SRC.html
Page d’accueil
Concaténer les derniers billets:
[ -f www/.posts ] || touch www/.posts
grep -q --max-count 1 $ARCHIVES/$SRC.htm www/.posts || echo $ARCHIVES/$SRC.htm >> www/.posts
...
latest_posts=$(tail -n $POSTS_PER_PAGE www/.posts | sort -r | tr '\n' ' ')
OUT=www/.index.html
cat www/.before_main.htm $latest_posts www/.after_main.htm > $OUT
mv $OUT www/index.html
Les flux atom et rss
Pour générer ces fichers, j’ai besoin des métadonnées du blog et des billets. Je ne peux pas simplement faire:
$ cat blog.md src/billet.md | lowdown ...
car lowdown ne prendra en compte que les métadonnées du premier fichier comme le montre la commande suivante:
$ cat blog.md src/2026-08-08_orke.md | lowdown -L
blog_title
blog_description
blog_url
blog_author
blog_root
Je ruse en supprimant les marqueurs (“---”) en trop (le dernier du premier fichier et le premier du deuxième fichier) à l’aide d’un script awk:
BEGIN { ff = 1 } # First File
NF == 1 && $1 == "---" {
if (ff && FNR != 1) { ff = 0; nextfile } # skip last mark
if (!ff && FNR == 1) { getline } # skip first mark
}
{ print }
Chaque flux utilise 2 gabarits: un pour le début de fichier et un par “entrée”:
awk -f concat.awk blog.md src/$SRC | lowdown -thtml $LOWDOWN_OPTS -M "date: $YYYY-$MM-$DD" -M "post_url: /$YYYY/$MM/$SRC.html" -s --template tmpl/atom_entry.tmpl > $ARCHIVES/$SRC.htm.atom
OUT=www/.feed.atom
[ -f www/.atom ] || lowdown -thtml -s --template tmpl/atom.tmpl blog.md > www/.atom
cat www/.atom > $OUT
echo "<updated>${YYYY}-${MM}-${DD}T08:00:00Z</updated>" >> $OUT
for post in $latest_posts; do
cat $post.atom >> $OUT
done
echo '</feed>' >> $OUT
mv $OUT www/feed.atom
Le blog
Je peux facilement repartir de zéro:
$ cat reset.sh
#!/bin/sh
set -eu
rm -rf www
for src in src/2*.md; do
./orke.sh $src
done
cp assets/* www/
ou prévisualiser un billet:
$ cat preview.sh
#!/bin/sh
set -eu
cat www/.before_main.htm
lowdown -thtml --html-no-head-ids --parse-no-intraemph -s --template tmpl/post.tmpl $1
cat www/.after_main.htm
$ ./preview.sh src/2026-08-08_orke.md > www/preview.html
Cette nouvelle version d’**orke** méritait bien un petit quelque chose, j’ai donc pris le temps de faire (enfin !) un thème sombre.
Commentaires: https://github.com/bsdsx/blog_posts/issues/23
2025-04-16
VPS chez Contabo
Pour commencer:
- je ne suis pas affilié à Contabo
- je ne suis pas un gourou de python
- je ne suis pas un ardent défenseur de l’informatique nuagique
Virtual Private Server
Multi-coeurs, gavés de RAM, un vps d’aujourd’hui n’a rien à envier à un serveur dédié. Pour peu que l’on sache être économe sur l’espace de stockage, un vps peut rendre de multiples services:
- DNS secondaire
- MX secondaire
- web statique
- repo git
et j’en passe. Mes pré-requis concernant un serveur n’ont pas changé d’un iota: FreeBSD et ipv6. FreeBSD parce que je le vaux bien et l’ipv6 parce que l’ipv4 c’est pour les gueux. Il ne doit pas être simple de trouver un founisseur de vps qui ne propose pas de connectivité ipv6 par contre, question BSD, c’est pas gagné. C’est pourquoi j’ai choisi il y a quelques temps déjà Contabo qui malheureusement ne propose plus l’option FreeBSD. Mais tout n’est pas perdu.
API
Le principe est simple: commander un vps sans se soucier du système d’exploitation et le réinstaller à l’aide de l’api. La documentation est claire et une fois mes identifiants récupérés j’ai pû commencer l’écriture de quelques lignes de code Python. On peut trouver sur les nains ternets moultes implémentations autrement plus complètes que mon code mais mes besoins étant simples j’ai préféré “faire à ma sauce” (qui a dit NIH ?).
Le script
Le script contabo.py utilise le module api_contabo.py qui permet de:
- lister les images
- gérer les instances
- gérer les secrets
Je suis un adepte du “–run”, lancer le script sans aucun argument revient à afficher l’aide:
$ python contabo.py
usage: contabo.py [-h] [--debug] [--run] [--config CONFIG] methode [args ...]
Contabo - api
positional arguments:
methode
args
options:
-h, --help show this help message and exit
--debug debug requests
--run run this script
--config CONFIG .ini config file
Exemple:
contabo.py --run [--config {config.ini}] images [query]
contabo.py --run [--config {config.ini}] image {imageId}
contabo.py --run [--config {config.ini}] instances
contabo.py --run [--config {config.ini}] instance {instanceId}
contabo.py --run --config {config.ini} reset_instance {instanceId} user_data.yml [{imageId} [secretId [secretId...]]
contabo.py --run [--config {config.ini}] secrets
contabo.py --run [--config {config.ini}] secret {secretId}
contabo.py --run [--config {config.ini}] create_secret {secret} {name}
contabo.py --run [--config {config.ini}] delete_secret {secretId}
contabo.py --run [--config {config.ini}] token
Réinstaller un vps
Avant de réinstaller un vps, il faut commencer par s’occuper du “secret”: clef ssh plutôt que maux de passe (maux, pas mot). Pour se faire, placer les identifiants dans des variables d’environnement ou dans un fichier “.ini” (on peut se baser sur le fichier example.ini fourni) :
$ cat contabo.ini
[Api]
CLIENT_ID =
CLIENT_SECRET =
API_USERNAME =
API_PASSWORD =
L’accès à l’api requiert un token:
$ python --run --config contabo.ini token
[ snip token ]
Pour ne pas générer un token à chaque appel, je l’enregistre dans une variable API_CONTABO_TOKEN. Je crée une clef ssh:
$ ssh-keygen -t ed25519 -N '' -f contabo_ed25519
et j’ajoute mon secret (le nom du secret permet de l’identifier plus facilement) :
$ python contabo.py --run --config contabo.ini create_secret "`cat contabo_ed25519.pub|tr -d '\n'`" nom_de_mon_secret
<Response [201]>
Je peux maintenant lister mes secrets:
$ python contabo.py --run --config contabo.ini secrets
et afficher un secret
$ python contabo.py --run --config contabo.ini secret 123456
Je liste les images disponibles (FreeBSD par défaut) :
$ python contabo.py --run --config contabo.ini images
La version la plus récente proposée (14.0) n’est pas vraiment la plus à jour. Si cela pose problème il reste l’option (payante) ‘custom image’ qui propose un espace de 25 Go pour stocker ses images “aux petits oignons”.
Il est temps de lister les instances:
$ python contabo.py --run --config contabo.ini instances
Pour me simplifier la vie, je rajoute une section ‘DEFAULT’ à mon fichier “.ini"
[DEFAULT]
imageId = 67a63682-6817-41a1-9b87-c8adb4072f27 # freebsd-14.0
sshKeys = 123456 # contabo_ed25519
et une section pour mon instance:
[123456789]
productId = V45 # VPS 1 SSD (no setup)
et je réinstalle mon vps:
$ python3 reset_instance {instanceId} user_data.yml
cloudinit
Il n’y a pas très longtemps que j’ai compris l’usage de ce machin nuagique pour enfin y trouver un intéret. Pour les ceussent qui sachent, je ne vais rien vous apprendre. Pour les autres, un exemple de fichier que j’utilise:
#cloud-config
ssh_pwauth: false
user: CHANGE_THIS
packages:
- net/rsync
- security/doas
- sysutils/tmux
write_files:
- path: /etc/rc.conf.d/background_fsck
content: |
background_fsck_delay="10"
- path: /etc/rc.conf.d/hostname
content: |
hostname="CHANGE_THIS"
- path: /etc/rc.conf.d/local_unbound
content: |
local_unbound_enable="YES"
- path: /etc/rc.conf.d/network
content: |
ifconfig_vtnet0='inet CHANGE_THIS netmask 255.255.255.0'
ifconfig_vtnet0_ipv6="inet6 CHANGE_THIS prefixlen 64"
- path: /etc/rc.conf.d/ntpd
content: |
ntpd_enable="YES"
ntpd_sync_on_start="YES"
ntpd_flags="--novirtualips"
ntp_leapfile_sources="https://hpiers.obspm.fr/iers/bul/bulc/ntp/leap-seconds.list"
- path: /etc/rc.conf.d/routing
content: |
defaultrouter=CHANGE_THIS
ipv6_defaultrouter=fe80::1%vtnet0
- path: /etc/rc.conf.d/sshd
content: |
sshd_enable="YES"
sshd_flags="-6 -o UseDNS=yes -o UsePAM=no -o AllowGroups=wheel -o PermitRootLogin=without-password"
- path: /etc/rc.conf.d/syslogd
content: |
syslogd_flags="-ss"
- path: /usr/local/etc/doas.conf
content: |
permit nopass setenv { -ENV PS1=$DOAS_PS1 SSH_AUTH_SOCK } :wheel
runcmd:
- pw groupmod unbound -m CHANGE_THIS
- chmod -R g+w /etc/rc.conf.d /etc/unbound/conf.d
- rm /etc/rc.conf
- rm -rf /usr/lib/debug /usr/lib32
- chflags -R 0 /usr/lib32
- rm -rf /usr/lib32
power_state:
mode: reboot
Et parce que l’ipv6 n’est pas un long fleuve tranquille, on peut avoir besoin de rajouter:
- path: /etc/sysctl.conf.local
content: |
net.inet6.icmp6.nd6_onlink_ns_rfc4861=1 # because fe80::1 don't respond to neighbor solicitation
- path: /etc/rc.conf.d/netwait
content: |
netwait_enable="YES"
netwait_ip="2a02:c207::1:53"
netwait_timeout="5"
Conclusion
Je suis très satisfait de mes 3 vps et si la version (pas très fraiche) de FreeBSD peut en rebuter certains, je suis persuadé que les images BASIC-CLOUDINIT (un énorme merci à bapt@ pour nuageinit ) seront bientôt disponibles.
Commentaires: https://github.com/bsdsx/blog_posts/issues/22
2024-10-30
Prises connectées
Alors que je baguenaudais sur un site de vente en ligne à une période propice à moultes réductions et dans un énième moment d’égarement j’ai fait l’acquisition de “prises connectées”. Par ‘égarement’ j’entends qu’aucune recherche préalable de compatibilité avec mon environnement n’a été faite et que le besoin à ce moment était loin d’être justifié. Bref, j’ai craqué.
Le matos
Lot de 2 prises Meross MSS305ZF Smart Wi-Fi Plug 16A
Première impression
Autant le dire tout de suite: port série, Tasmota, firmware alternatif et autres bidouilleries ne seront pas de la partie. Et comme ce genre de bestiole s’accompagne toujours d’une application pour débilophone, l’oubli au fond du tiroir ‘okazou’ semblait être la destinée finale de cet achat compulsif. Après quelques (tardives) recherches, je m’aperçois que tout n’est pas perdu.
Configuration initiale
Je ne suis pas spécialiste de ce genre de gadget mais la séquence suivante me semble la façon usuelle de procéder:
- installer une application sur le débilophone
- dire au revoir à sa vie privée
- brancher l’appareil
- passer l’appareil en mode “comment c’est-y que j’me connecte à la wifi pour tout balancer dans le claoude” (sic)
- pousser l’accès au réseau sans fil à l’aide de l’application
- dire au revoir à sa vie privée (bis)
Problème: mon débilophone est un Samsung S9 sous LineageOS (ce qui le rend tout de suite un peu moins con) qui n’a pas accès aux magasins d’applications.
Solution: Grâces soient rendues à Fabi019 et l’apk qu’il met à disposition depuis https://github.com/Fabi019/MerossBLE et qui s’installe sans problème. 2 minutes plus tard, une première prise est connectée. Le truc à retenir qui servira plus tard: la valeur du champ ‘Key’ du cartouche ‘1. Server Configuration’ .
Faire mumuse
Je n’ai pas (encore ?) de solution domotique, j’ai vaguement lancé un serveur mqtt (la prise envoie bien des données dessus) mais ce qui m’intérresse c’est d’interagir directement avec la prise. Pour se faire j’ai trouvé des projets en go, nodejs et python. Comme chacun sait, le plus important c’est la doc. Et la doc, pour mon cas, elle est là: https://github.com/arandall/meross/blob/main/doc/protocol.md .
Le protocole n’est pas très compliqué et consiste à l’envoi d’un JSON à une url fixe. J’ai longtemps buté sur un élément:
sign == Signing value equal to md5(messageId + key + timestamp)
car, de ce que j’avais lu, cette ‘key’ semblait être fournie après l’ouverture d’un compte chez meross. J’ai finalement compris que je pouvais fixer cette valeur depuis l’application du débilophone, comme vu un peu plus haut (le truc à retenir).
Je me suis fendu de quelques lignes de python (car on n’est jamais aussi bien servi que par soi-même et non c’est pas du nih) et je peux désormais contrôler mes prises depuis la VM FreeBSD de ma Freebox Delta.
Reste à faire
La prise en mode “appairage” propose un point d’accès wifi à partir duquel il est possible de pousser la configuration. Je n’ai pas encore testé cette possibilité. Le point d’accès auquel se connectent les prises ne propose pas d’ipv6 et je ne trouve pas trace des adresses MAC des prises dans mon voisinage ipv6. Je pense qu’on peut faire des trucs rigolos avec les ‘.TriggerX’ . Quant à ‘Appliance.Control.Upgrade’, si bucket-meross-static.meross.com existe, je n’ai pas encore trouvé le moyen d’obtenir le chemin vers une éventuelle mise à jour du firmware.
Conclusion
Est-ce que je regrette mon achat ? Non. Est-ce que je vais surveiller ma consommation électrique afin de la faire baisser ? Je ne crois pas. Est-ce que je vais faire du mqtt ? Je ne sais pas. Est-ce que je vais finir par installer une solution de domotique ? J’en doute mais si j’ai bien compris c’est un engrenage. Et comme j’ai aussi 2 prises ‘Amazon smart plug’, le mal est peut-être déjà fait.
Commentaires: https://github.com/bsdsx/blog_posts/issues/21
2024-02-29
wifibox à la sauce OpenWrt
J’ai pendant très longtemps utilisé un mini routeur sous OpenWrt car le wifi de mon ancien nuc n’était ni stable ni performant. Les ressources de ma nouvelle machine de travail (AMD Ryzen 7 3750H) étant bien supérieures, l’idée m’est venue de mimer FreeBSD wifibox à la sauce OpenWrt. Un portable Lenovo T450 en a aussi fait les frais.
wifibox
Le principe est assez simple: si la plateforme matérielle le permet, attribuer le périphérique wifi à une machine virtuelle dont le pilote a une meilleure prise en charge que celui de FreeBSD. Le projet a fait le choix l’Alpine Linux.
wifi et bhyve
Je commence par identifier le périphérique:
$ pciconf -lv
...
iwm0@pci0:2:0:0: class=0x028000 rev=0x59 hdr=0x00 vendor=0x8086 device=0x095a subvendor=0x8086 subdevice=0x5010
vendor = 'Intel Corporation'
device = 'Wireless 7265'
class = network
...
Il ne doit plus être pris en charge par le pilote iwm mais par le Pci PassThru (ppt). Je dois aussi charger le module vmm et parce que j’ai un processeur AMD activer un sysctl comme indiqué sur le wiki FreeBSD)
$ cat /boot/loader.conf.d/vmm.conf
vmm_load="YES"
pptdevs="2/0/0" # à adapter suivant pciconf -lv
hw.vmm.amdvi.enable=1
Un redémarrage plus tard, on obtient:
$ pciconf -lv
...
ppt0@pci0:2:0:0: class=0x028000 rev=0x59 hdr=0x00 vendor=0x8086 device=0x095a subvendor=0x8086 subdevice=0x5010
vendor = 'Intel Corporation'
device = 'Wireless 7265'
class = network
...
Machine virtuelle
Je récupère la dernière image d’OpenWrt:
$ fetch https://downloads.openwrt.org/releases/23.05.2/targets/x86/64/openwrt-23.05.2-x86-64-generic-ext4-combined-efi.img.gz
$ gunzip openwrt-23.05.2-x86-64-generic-ext4-combined-efi.img.gz
gunzip: openwrt-23.05.2-x86-64-generic-ext4-combined-efi.img.gz: trailing garbage ignored
Pour que bhyve puisse utiliser une image efi je dois ajouter le paquet suivant:
$ doas pkg install bhyve-firmware
et je vérifie que la machine virtuelle démarre bien:
$ doas bhyve -H -P -m 192M -s 0:0,hostbridge -s 1:0,lpc -s 2:0,virtio-net,tap0 -s 3:0,virtio-blk,./openwrt-23.05.2-x86-64-generic-ext4-combined-efi.img -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd -l com1,stdio -W vm0
Un halt plus tard je peux détruire cette machine virtuelle:
$ doas bhyvectl --destroy --vm=vm0
Utile quand on joue au plus malin et qu’avec 128 Mo de mémoire on obtient:
error: out of memory.
error: out of memory.
Press any key to continue...Press any key to continue...
OpenWrt
Léger problème avec l’image fournie: elle ne contient absolument rien (module, pilote, outil …) concernant le wifi (c’est voulu par le projet pour la plate-forme x86). Je prépare un disque à ajouter à mon image avec le contenu nécessaire:
$ truncate -s 5M pkgs.img # oui, 5 Mo c'est bien assez
$ newfs_msdos ./pkgs.img
newfs_msdos: warning, ./pkgs.img is not a character device
./pkgs.img: 10192 sectors in 1274 FAT12 clusters (4096 bytes/cluster)
BytesPerSec=512 SecPerClust=8 ResSectors=1 FATs=2 RootDirEnts=512 Sectors=10240 Media=0xf0 FATsecs=4 SecPerTrack=63 Heads=255 HiddenSecs=0
$ doas mdconfig ./pkgs.img
md0
$ doas mount -t msdosfs /dev/md0 /media
Depuis https://downloads.openwrt.org/releases/23.05.2/packages/x86_64/base/ je récupère les paquets:
hostapd-common_2023-09-08-e5ccbfc6-6_x86_64.ipk
iw_4.14-1_x86_64.ipk
iwinfo_2023-07-01-ca79f641-1_x86_64.ipk
iwlwifi-firmware-iwl7265d_20230804-1_x86_64.ipk
libnl-tiny1_2023-07-27-bc92a280-1_x86_64.ipk
ucode-mod-nl80211_2023-11-07-a6e75e02-1_x86_64.ipk
ucode-mod-rtnl_2023-11-07-a6e75e02-1_x86_64.ipk
ucode-mod-uloop_2023-11-07-a6e75e02-1_x86_64.ipk
wireless-regdb_2024.01.23-1_all.ipk
wpa-cli_2023-09-08-e5ccbfc6-6_x86_64.ipk
wpa-supplicant-mini_2023-09-08-e5ccbfc6-6_x86_64.ipk
et depuis https://downloads.openwrt.org/releases/23.05.2/targets/x86/64/packages/ les modules:
kmod-cfg80211_5.15.137+6.1.24-3_x86_64.ipk
kmod-crypto-aead_5.15.137-1_x86_64.ipk
kmod-crypto-cbc_5.15.137-1_x86_64.ipk
kmod-crypto-ccm_5.15.137-1_x86_64.ipk
kmod-crypto-cmac_5.15.137-1_x86_64.ipk
kmod-crypto-ctr_5.15.137-1_x86_64.ipk
kmod-crypto-gcm_5.15.137-1_x86_64.ipk
kmod-crypto-gf128_5.15.137-1_x86_64.ipk
kmod-crypto-ghash_5.15.137-1_x86_64.ipk
kmod-crypto-hmac_5.15.137-1_x86_64.ipk
kmod-crypto-manager_5.15.137-1_x86_64.ipk
kmod-crypto-null_5.15.137-1_x86_64.ipk
kmod-crypto-rng_5.15.137-1_x86_64.ipk
kmod-crypto-seqiv_5.15.137-1_x86_64.ipk
kmod-crypto-sha512_5.15.137-1_x86_64.ipk
kmod-iwlwifi_5.15.137+6.1.24-3_x86_64.ipk
kmod-mac80211-hwsim_5.15.137+6.1.24-3_x86_64.ipk
kmod-mac80211_5.15.137+6.1.24-3_x86_64.ipk
Je démarre la machine virtuelle avec ce nouveau disque:
$ doas umount /media
$ doas mdconfig -du md0
$ doas bhyve -H -P -m 192M -s 0:0,hostbridge -s 1:0,lpc -s 2:0,virtio-net,tap0 -s 3:0,virtio-blk,./openwrt-23.05.2-x86-64-generic-ext4-combined-efi.img -s 4:0,virtio-blk,./pkgs.img -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd -l com1,stdio -W vm0
et j’installe les paquets:
root@OpenWrt:/# mount -t vfat /dev/vdb /mnt/
root@OpenWrt:/# opkg install /mnt/libnl-tiny1_2023-07-27-bc92a280-1_x86_64.ipk
Package libnl-tiny1 (2023-07-27-bc92a280-1) installed in root is up to date.
root@OpenWrt:/# ln /usr/lib/libnl-tiny.so.1 /usr/lib/libnl-tiny.so # sinon iw va râler
root@OpenWrt:/# opkg install /mnt/hostapd-common_2023-09-08-e5ccbfc6-6_x86_64.ipk /mnt/iw_4.14-1_x86_64.ipk /mnt/iwinfo_2023-07-01-ca79f641-1_x86_64.ipk /mnt/iwlwifi-firmware-iwl7265d_20230804-1_x86_64.ipk /mnt/ucode-mod-* /mnt/wireless-regdb_2024.01.23-1_all.ipk /mnt/wpa-cli_2023-09-08-e5ccbfc6-6_x86_64.ipk /mnt/wpa-supplicant-mini_2023-09-08-e5ccbfc6-6_x86_64.ipk
Installing hostapd-common (2023-09-08-e5ccbfc6-6) to root...
Installing iw (4.14-1) to root...
Installing iwinfo (2023-07-01-ca79f641-1) to root...
Installing iwlwifi-firmware-iwl7265d (20230804-1) to root...
Installing ucode-mod-nl80211 (2023-11-07-a6e75e02-1) to root...
Installing ucode-mod-rtnl (2023-11-07-a6e75e02-1) to root...
Installing ucode-mod-uloop (2023-11-07-a6e75e02-1) to root...
Installing wireless-regdb (2024.01.23-1) to root...
Installing wpa-cli (2023-09-08-e5ccbfc6-6) to root...
Installing wpa-supplicant-mini (2023-09-08-e5ccbfc6-6) to root...
Configuring iwinfo.
Configuring iw.
Configuring ucode-mod-uloop.
Configuring hostapd-common.
Configuring ucode-mod-nl80211.
Configuring ucode-mod-rtnl.
Configuring wpa-supplicant-mini.
Configuring iwlwifi-firmware-iwl7265d.
Configuring wpa-cli.
Configuring wireless-regdb.
root@OpenWrt:/# opkg install /mnt/kmod-*
...
root@OpenWrt:/# umount /mnt
root@OpenWrt:/# halt
Il est temps d’affecter le périphérique wifi à la machine virtuelle (adapter 2/0/0 au pciconf -lv et ne pas oublier l’option -S):
$ doas bhyve -H -P -S -m 192M -s 0:0,hostbridge -s 1:0,lpc -s 2:0,virtio-net,tap0 -s 3:0,virtio-blk,./openwrt-23.05.2-x86-64-generic-ext4-combined-efi.img -s 7:0,passthru,2/0/0 -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd -l com1,stdio -W vm0
Configuration OpenWrt
Pas besoin du délai de grub:
root@OpenWrt:/# grep timeout /boot/grub/grub.cfg
set timeout="0"
Quelques modules sont chargés au démarrage, je ne garde que le minimum (et encore, je pense que seul 30-fs-vfat est utile pour récupérer la configuration de grub):
root@OpenWrt:/# ls -1 /etc/modules-boot.d/
02-crypto-hash
04-crypto-crc32c
09-crypto-aead
09-crypto-manager
30-fs-vfat
Pareil pour les modules chargés après le démarrage (je doit pourvoir encore en supprimer quelques uns):
root@OpenWrt:/# ls -1 /etc/modules.d/
02-crypto-hash
04-crypto-crc32c
09-crypto-acompress
09-crypto-aead
09-crypto-cbc
09-crypto-ccm
09-crypto-cmac
09-crypto-ctr
09-crypto-gcm
09-crypto-gf128
09-crypto-ghash
09-crypto-hmac
09-crypto-manager
09-crypto-null
09-crypto-rng
09-crypto-seqiv
09-crypto-sha512
25-nls-cp437
25-nls-iso8859-1
25-nls-utf8
30-fs-vfat
iwlwifi
lib-crc-ccitt
lib-crc32c
lib-lzo
mac80211-hwsim
Je désactive les services inutiles:
root@OpenWrt:/# /etc/init.d/dnsmasq stop && /etc/init.d/dnsmasq disable
root@OpenWrt:/# /etc/init.d/dropbear stop && /etc/init.d/dropbear disable
root@OpenWrt:/# /etc/init.d/firewall stop && /etc/init.d/firewall disable
root@OpenWrt:/# /etc/init.d/gpio_switch stop && /etc/init.d/gpio_switch disable
root@OpenWrt:/# /etc/init.d/led stop && /etc/init.d/led disable
root@OpenWrt:/# /etc/init.d/sysntpd stop && /etc/init.d/sysntpd disable
root@OpenWrt:/# /etc/init.d/uhttpd stop && /etc/init.d/uhttpd disable
Je configure le wifi:
root@OpenWrt:/# cat /etc/config/wireless
...
config wifi-device 'radio2'
option type 'mac80211'
option path 'pci0000:00/0000:00:07.0'
option channel '36'
option band '5g'
option htmode 'VHT80'
option disabled '0'
config wifi-iface 'default_radio2'
option device 'radio2'
option network 'wan wan6'
option mode 'sta'
option ssid 'mon_super_ssid'
option encryption 'psk2'
option key 'ma_super_key'
et le réseau:
root@OpenWrt:/# cat /etc/config/network
config interface 'loopback'
option device 'lo'
option proto 'static'
option ipaddr '127.0.0.1'
option netmask '255.0.0.0'
config interface 'lan'
option device 'eth0'
option proto 'static'
option ipaddr '172.20.99.1'
option netmask '255.255.255.0'
option ip6addr '2db8:xxxx:xxxx:xx3:172:20:99:1/112'
config interface 'wan'
option proto 'static'
option ipaddr '172.20.62.99'
option netmask '255.255.255.0'
option gateway '172.20.62.1'
list dns '172.30.63.20'
list dns '172.30.63.10'
config interface 'wan6'
option proto 'static'
option ip6addr '2db8:xxxx:xxxx:xx2::99/64'
option ip6gw '2db8:xxxx:xxxx:xx2::1'
list dns '2db8:xxxx:xxxx:xx1:172:30:63:20'
list dns '2db8:xxxx:xxxx:xx1:172:30:63:10'
Configuration FreeBSD
Le bridge:
$ cat /etc/rc.conf.d/bridge
autobridge_interfaces="bridge0"
autobridge_bridge0="tap0"
Le réseau:
$ cat /etc/rc.conf.d/network
cloned_interfaces="bridge0 tap0"
ifconfig_bridge0="inet 172.20.99.10 netmask 255.255.255.0"
ifconfig_bridge0_ipv6="inet6 auto_linklocal"
ifconfig_bridge0_alias0="inet6 2db8:xxxx:xxxx:xx3:172:20:99:10 prefixlen 112"
$ cat /etc/rc.conf.d/routing
defaultrouter="172.20.99.1"
ipv6_defaultrouter="2db8:xxxx:xxxx:xx3:172:20:99:1"
L’accès à la console de la machine virtuelle à l’aide de nmdm:
$ cat /etc/rc.conf.d/kld
kld_list="/boot/modules/amdgpu.ko nmdm"
Le démarrage de la machine virtuelle:
$ cat /etc/rc.local
/usr/sbin/bhyve -H -P -S -m 192M -s 0:0,hostbridge -s 1:0,lpc -s 3:0,virtio-blk,/chemin/complet/vers/openwrt-23.05.2-x86-64-generic-ext4-combined-efi.img -s 7:0,passthru,2/0/0 -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd -l com1,/dev/nmdm0A vm0 &
L’accès à la console de la machine virtuelle se fera à l’aide de cu:
$ doas cu -l /dev/nmdm0B # oui "B", man 4 nmdm
Mon réseau
La configuration réseau présentée est loin d’être la plus commune (pas de dhcp ni de nat, ipv6 sans autoconf). Pour une version plus “traditionnelle” (dhcp, pas d’**ipv6**), la machine virtuelle devra:
- faire du dhcp sur l’interface wan
- activer le firewall pour faire du nat
Ce qui devrait peu ou prou correspondre à (pas encore testé):
root@OpenWrt:/# cat /etc/config/network
....
config interface 'wan'
option proto 'dhcp'
root@OpenWrt:/# /etc/init.d/firewall enable && /etc/init.d/firewall start
Au pire la documentation d’OpenWrt https://openwrt.org/docs/guide-user/network/start est plus que fournie.
Pour conclure
Le iperf est sans appel:
$ iperf3 -6 -c proxy
...
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 226 MBytes 190 Mbits/sec 19 sender
[ 5] 0.00-10.01 sec 226 MBytes 189 Mbits/sec receiver
$ iperf3 -6 -c proxy
...
[ 5] 0.00-10.00 sec 263 MBytes 221 Mbits/sec 0 sender
[ 5] 0.00-10.02 sec 263 MBytes 220 Mbits/sec receiver
C’est loin d’être parfait au vu de la configuration du réseau:
freebox delta [ vm proxy sous FreeBSD ] <-- rj45 --> ap wifi Mikrotik hAP ac^2 <- -> nuc
mais c’est toujours mieux que la dizaine de Mbits/sec (dont je me contentais très bien) de mon ancienne installation.
Le mini routeur qui trônait fièrement au dessus du nuc devrait finir au fin fond d’un tirroir. Mais comme il possède un lien série, j’en ferais bien un Frankenstein à base de bluetooth …
Correction 2024-03-17
J’ai oublié ‘-s 2:0,virtio-net,tap0’ à chaque commande bhyve.
Commentaires: https://github.com/bsdsx/blog_posts/issues/20