T5 Déploiement
Deux couches, dans cet ordre
Mettre une passerelle en service, c'est deux travaux différents, et les confondre est la première cause de perte de temps.
| A. La CloudGate | Réseau, carte SIM, heure, comptes, tunnel. Rien de tout cela n'est dans le dépôt et ne peut y être : ce sont les réglages de l'appareil, saisis dans son interface web. |
| B. L'applicatif | Le flux, le cockpit, les capteurs du site. Tout vient du dépôt, assemblé pour ce site, déployé par un script qui porte les garde-fous. |
La couche B se refait en trois commandes autant de fois qu'on veut. La couche A se saisit à la main, une fois par passerelle, et c'est elle qui coûte. D'où l'étape A.7, qui la capture pour ne pas la resaisir.
A. La CloudGate
A.1 Accès local et mot de passe
Câble Ethernet entre le poste et la passerelle, sur eth0.
L'adresse n'est pas la même selon que la passerelle sort d'usine ou a
déjà été mise en service, et la différence ne se devine pas.
| Sortie d'usine | Déjà mise en service | |
|---|---|---|
| Adresse | 192.168.1.1 | 192.168.42.1, ou celle du site |
| Adresses distribuées | oui | non |
| Le poste | en automatique, il reçoit une adresse | en adresse fixe, par exemple 192.168.42.10/24 |
Comment trancher sans rien savoir : brancher le poste en automatique. S'il reçoit une adresse en une trentaine de secondes, la passerelle sort d'usine. Sinon, lui poser une adresse fixe dans le réseau du site.
De l'image d'origine elle-même. Le système est un OpenWrt dont la partition
de départ reste montée en lecture seule : /rom/etc/config/network
et /rom/etc/config/dhcp disent ce que la passerelle faisait avant
toute configuration, quelle que soit celle qu'elle porte aujourd'hui. À
reconsulter plutôt qu'à croire sur parole si un jour le doute revient.
| Interface d'administration | http://<adresse> |
| Canvas LuvitRED | http://<adresse>:8080 |
| Shell | ssh admin@<adresse> avec les algorithmes anciens (voir plus bas) |
Changer le mot de passe d'administration tout de suite. Il
sera repris dans CG_ADMIN_PASS et se retrouve, substitué, dans le
flux assemblé : il doit être propre à cette passerelle.
A.2 Identité et heure
Nom de la machine, fuseau UTC, serveur de temps
pool.ntp.org, journal en fichier.
La garde des six heures qui protège les jetons se calcule sur l'horodatage inscrit dans le fichier d'état. Une horloge fausse fait consommer un jeton à chaque redémarrage, et un jeton perdu se récupère auprès d'un administrateur de la plateforme, pas depuis la passerelle.
A.3 Réseau local
Passer eth0 de 192.168.1.1 à l'adresse retenue
pour le site, en statique, et la reporter dans CG_IP du fichier de
site. La passerelle en service utilise 192.168.42.1/24.
Elle ne répond plus à l'ancienne adresse, et elle ne distribue plus rien si la distribution a été coupée. Prévoir l'adresse fixe du poste avant d'enregistrer, sinon il faut se rebrancher à l'aveugle.
A.4 Carte SIM et accès data
SIM M2M insérée, APN de l'opérateur renseigné. Vérifier ensuite que l'interface cellulaire obtient bien une adresse.
Pas de 4G classique, et la 2G est fermée. Une SIM grand public ne s'attache pas. Il faut une SIM M2M dont l'opérateur déploie le M1.
A.5 Radio LoRa : rien à faire ici
Le forwarder, le serveur et les applications LoRa sont des nodes de notre flux : ils arrivent avec le déploiement de la couche B, avec leurs réglages. Aucun passage par le Cloud Wizard n'est nécessaire.
Cette étape a longtemps été décrite comme obligatoire. Elle ne l'est plus depuis que le flux déployé porte la pile radio complète, et les nodes correspondants ne contiennent rien de propre au matériel. Tant qu'une vierge n'a pas été mise en service, c'est une déduction, pas une observation.
A.6 Tunnel, si le site en a besoin
IKEv1, identité locale de type FQDN, sous-réseau local
192.168.42.0/24, sous-réseau distant fourni par le client. Le type
d'identité est le réglage qui a bloqué l'établissement du tunnel pendant des
semaines : ce n'était pas la cryptographie.
Le firmware le traduit en deux routes qui capturent tout le trafic, y compris vers le poste branché en Ethernet. La passerelle devient injoignable, même en local.
A.7 Capturer le gabarit, avant tout déploiement
C'est l'étape qui évite de refaire A.1 à A.6 à la main sur la passerelle suivante.
SITE=mon-site bash scripts/gabarit.sh fabriquer
Elle produit deux fichiers dans backups/, qui ne servent pas au
même usage :
gabarit_<site>_<date>.tar.gz | Les fichiers de configuration eux-mêmes. Candidat à une réimportation sur une autre passerelle. |
gabarit_<site>_<date>.txt | L'inventaire lisible des réglages, valeurs sensibles masquées. Celui-là sert toujours : il dit quoi saisir. |
Le flux applicatif vit dans /etc/config/luvitred et porte,
substitués, le mot de passe d'administration, les deux mots de passe du cockpit
et l'authentification Asystom. Un gabarit pris après un déploiement les
emporterait tous, et les installerait sur le site suivant. Le script les exclut
et le vérifie, mais un gabarit capturé sur une passerelle vierge de tout flux
n'a même pas la question à se poser.
B. L'applicatif
B.0 Ce qu'il faut sur le poste d'administration
La couche A se fait dans un navigateur, donc depuis n'importe quel poste. La couche B, non : tout passe par des commandes lancées depuis la machine d'administration, jamais depuis la passerelle.
bash | Les scripts en dépendent, un shell POSIX ne suffit pas. |
curl | Tous les échanges avec la passerelle et la plateforme. |
python3 | 3.6 ou plus. Assemblage du flux et contrôles. |
sshpass | La passerelle s'authentifie par mot de passe, elle n'accepte pas de clé. |
ssh, scp | OpenSSH 8.5 ou plus, voir ci-dessous. |
tar | Capture du gabarit. |
jq | Uniquement pour inject_dap_state.sh, la restauration manuelle d'état. |
L'option PubkeyAcceptedAlgorithms, nécessaire pour parler au
système ancien de la passerelle, s'appelait PubkeyAcceptedKeyTypes
avant la version 8.5. Sur un poste plus ancien, chaque script s'arrête sur
Bad configuration option, un message qui ne ressemble pas à un
problème de version et qu'on va chercher du côté du réseau.
Cette liste suppose un poste Unix : Linux, macOS, ou Windows avec WSL. Un opérateur sous Windows sans WSL peut faire toute la couche A, et rien de la couche B. Soit le poste lui est fourni avec cet outillage, soit la couche B reste à la charge de qui maintient le flux.
B.1 Déployer le flux et le cockpit
Le flux ne s'importe pas à la main : il s'assemble depuis le dépôt, puis se déploie par un script qui porte les garde-fous. Deux fichiers décrivent le site, et aucun des deux n'est versionné :
cp config/exemple.env config/mon-site.env # puis remplir les valeurs
python3 flow/build.py mon-site # assemble le flux
bash scripts/deploy.sh mon-site check # contrôles, lecture seule
bash scripts/deploy.sh mon-site backup # filet
bash scripts/deploy.sh mon-site deploy # POST /flows
bash scripts/deploy_www.sh mon-site # interface du cockpit
config/exemple.env liste les valeurs à fournir : nom de la
passerelle, mots de passe d'administration et du cockpit, plateforme Asystom,
et l'AppEUI de l'application qui accueille les capteurs non-Asystom.
L'assemblage échoue si l'une manque, plutôt que de déployer un
flux à trous.
Les capteurs se déclarent soit au cockpit une fois la passerelle en service,
soit dans config/<site>.balises.json à partir de
config/balises.modele.json. Sans ce fichier, la passerelle démarre
sans aucun capteur : c'est l'état voulu d'une passerelle neuve.
Le modèle porte deux entrées, une par application LoRa :
Asystom App ouvre la chaîne Advisor et les commandes à distance,
Generic App ne fait que transiter vers IoBase. Le rattachement
décide aussi du réseau que le capteur peut rejoindre : une balise d'une autre
marque déclarée sous l'application Asystom ne fera jamais de join, son AppEUI ne
correspondant pas. Les annotations _lisez_moi du modèle sont
retirées à l'assemblage.
check refuseUn déploiement qui supprimerait un node présent sur la
passerelle mais absent du dépôt — une balise déclarée à la main n'existe que
là-bas, et un déploiement distrait l'efface. C'est arrivé le 29/08. Une
suppression voulue se nomme :
SUPPRESSIONS_VOULUES='nom du node', séparés par des virgules.
Il refuse aussi de déployer si un renouvellement de jeton est en vol, et signale l'âge des jetons : au-delà de six heures, le redémarrage en consomme un.
Enfin, il vérifie que le flux assemblé porte bien tous les capteurs
déclarés par le site dans config/<site>.balises.json.
Aucun fichier de balises, aucune exigence : une passerelle neuve passe le
contrôle avec zéro capteur. Jusqu'au 18/09 ce contrôle nommait une balise d'un
client en dur, et refusait donc toute passerelle neuve.
Une passerelle qui sort d'usine porte le flux du constructeur, complété par ce que le Cloud Wizard a créé. Aucun de ces nodes n'est dans le dépôt : la comparaison au flux en service les voit tous comme des suppressions. Or les remplacer est précisément le but d'une mise en service.
check reconnaît ce cas : si la passerelle ne porte aucune de nos
fonctions, il annonce un premier déploiement, dénombre par type ce qui
sera remplacé et s'arrête. On le confirme en relançant avec
PREMIER_DEPLOIEMENT=1. Le garde-fou des suppressions, lui, ne
protège que ce qui a été déclaré à la main sur notre propre flux : il reprend
son rôle dès le deuxième déploiement.
Avant l'enrôlement, l'absence de dap_state.json est normale et
check le dit. Ne pas lancer refresh à ce moment : il
n'a aucun jeton à rafraîchir.
Le contenu exact d'un flux d'usine reste inconnu tant qu'une CloudGate vierge n'a pas été branchée. C'est pour cela que le contrôle l'affiche au lieu de le supposer.
B.2 Renseigner la configuration
Depuis le cockpit, onglet Globals avec le rôle admin. Aucune modification du flux n'est nécessaire, et les valeurs sont persistées puis rechargées à chaque démarrage.
| Variable | Valeur |
|---|---|
cloudgate_device_name | nom de cette passerelle |
iobase_host | adresse de l'API IoBase |
dap_host, dap_client_id, dap_api_key |
service d'authentification et identifiants, fournis par la plateforme |
asystom_url, asystom_auth_b64,
asystom_network, asystom_client |
plateforme Asystom, fournis par Asystom |
Ils se règlent par balise depuis le 2 août 2026 (onglet Balises → ✎ Modifier),
et sont obligatoires : sans eux la mesure n'est pas envoyée. Les anciennes variables globales
correspondantes ne sont plus lues, et iobase_metrics_default n'existe plus.
Voir la référence des variables.
B.3 Enrôlement auprès de la plateforme
[OFF] DAP register et [OFF] DAP get token sont
volontairement débranchés : leur fil de sortie est coupé. Un clic
de trop sur un enrôlement coûte plusieurs jours, parce que le jeton de
renouvellement est à usage unique et qu'un nouveau /register exige une
validation d'administrateur côté plateforme.
Le flux ne sait pas non plus s'amorcer seul : sans jeton de renouvellement,
build_dap_refresh s'arrête, et build_dap_register se garde
derrière un drapeau que seule une réponse d'erreur pose. Sur une passerelle
vierge, aucun des deux ne part. L'enrôlement se fait donc depuis la machine
d'administration.
SITE=mon-site bash scripts/enrolement.sh etat # ne modifie rien
SITE=mon-site bash scripts/enrolement.sh register # demande un device_code
# → un administrateur valide l'équipement sur le portail de la plateforme
# (« Services et équipements » → « Valider l'équipement »)
SITE=mon-site bash scripts/enrolement.sh token # échange le code
SITE=mon-site bash scripts/enrolement.sh injecter # pose l'état sur la passerelle
SITE=mon-site bash scripts/sante.sh # recette
Chaque étape refuse de s'exécuter si l'état ne s'y prête pas :
register s'arrête net devant un enrôlement déjà valide,
token devant l'absence de device_code,
injecter devant une réponse incomplète.
Tant que l'administrateur n'a pas validé, token répond
425 ou authorization_pending : il faut simplement
réessayer plus tard. Ne pas relancer register — le
device_code reste valide, et une nouvelle demande repart à zéro.
B.4 Recette
- Une balise Asystom à proximité, magnet × 3, vérifier Join
- Une trame uplink réelle → tracer la chaîne dans le debug
- POST IoBase 200 visible côté Indaba
- POST Asystom 201 visible côté Advisor
- Test de coupure : débrancher la liaison une minute, vérifier que la passerelle se reconnecte seule et renouvelle son jeton. Les mesures émises pendant la coupure sont perdues — il n'y a pas de file de rejeu.
Refaire une passerelle
La deuxième passerelle ne repasse pas par A.1 à A.6 : elle reçoit le gabarit capturé en A.7, puis la couche B comme la première.
- Remise à zéro de la passerelle neuve, puis accès local en Ethernet.
- Import du gabarit de la couche CloudGate.
- Reprendre les trois valeurs qui ne doivent pas s'hériter (ci-dessous).
- Couche B :
build.py,deploy.sh,enrolement.sh, puissante.sh. - Déclarer les capteurs du site, voir Chap. 05.
Le mot de passe d'administration. Il fait partie de la configuration de la CloudGate : sans reprise, toutes les passerelles importées partagent le même.
L'identité du tunnel. Elle est propre à une passerelle. Deux passerelles qui présentent la même identité se coupent mutuellement, et la panne ne ressemble pas à sa cause.
Le numéro de série. Il est dans la configuration système. On ne sait pas encore si l'import le préserve ou l'écrase. À vérifier au premier import, avant de considérer la méthode acquise : une passerelle qui ment sur son identité matérielle est un problème avec le constructeur, pas avec nous.
Il n'existe à ce jour qu'une seule passerelle. Le gabarit est fabriqué, vérifié exempt de secret applicatif, mais personne ne l'a jamais reposé sur une autre CloudGate. Tant que ce n'est pas fait, la couche A reste une saisie manuelle guidée par l'inventaire, et le gabarit n'est qu'un candidat. C'est le premier test à faire sur la première vierge, avant tout déploiement.
Le cloud du constructeur sait distribuer un profil de configuration à une
flotte, et c'était la méthode décrite ici jusqu'au 18/09. Elle a été écartée
pour deux raisons. La première tient au contenu : le flux vit dans
/etc/config/luvitred, donc un export de configuration l'emporte,
avec le mot de passe d'administration, les deux mots de passe du cockpit et
l'authentification Asystom, déposés chez un tiers puis redescendus sur chaque
clone. La seconde tient à la dépendance : le compte est à demander à
l'administrateur de la flotte, et une attente externe de plus est précisément
ce qui a coûté le plus cher sur ce projet.
Accès distant
La passerelle n'expose aucun port entrant et son réseau local
(192.168.42.0/24) n'est pas routable depuis Internet. Sa carte SIM est en outre
derrière un partage d'adresses opérateur : son adresse publique change en permanence, et
personne ne peut initier une connexion vers elle. Seule la passerelle peut ouvrir
un tunnel sortant.
Jusqu'au 24 août 2026, l'accès passait par un tunnel IPsec vers un VPS qui servait de rebond. Ce tunnel a été remplacé ; le VPS n'a plus de route vers la passerelle. Toute procédure qui passe par lui échoue aujourd'hui.
La configuration IPsec de la passerelle pointe désormais vers l'infrastructure du client
(remote_subnet 169.254.244.14/32). Le tunnel s'établit et transporte, mais son
exploitation dépend de deux éléments à fournir côté client : le préfixe source autorisé
et la route statique vers 192.168.42.0/24. Tant qu'ils manquent, ce chemin ne permet
pas d'atteindre la passerelle depuis un poste d'administration.
Accès local — le chemin qui fonctionne toujours
Câble Ethernet entre le poste et la passerelle. C'est le seul accès qui ne dépende de personne, et celui à utiliser pour toute intervention sensible : déploiement de flux, enrôlement, diagnostic.
| Interface d'administration | http://192.168.42.1 |
|---|---|
| Canvas LuvitRED | http://192.168.42.1:8080 |
| Cockpit | http://192.168.42.1:8888 (opérateur) ·
:8889 (admin) — voir chap. 05 |
| Shell | ssh admin@192.168.42.1 avec les algorithmes anciens
(voir ci-dessous) |
Shell : algorithmes anciens obligatoires
Le système de la passerelle est ancien (OpenWrt Backfire, BusyBox) : un client SSH récent refuse de négocier avec lui tant qu'on ne réactive pas explicitement les algorithmes hérités.
ssh -o HostKeyAlgorithms=+ssh-rsa \
-o PubkeyAcceptedAlgorithms=+ssh-rsa \
-o KexAlgorithms=+diffie-hellman-group14-sha1 \
admin@192.168.42.1
Le shell est BusyBox : pas de bash, et
grep y est limité. Les classes comme \s ne sont pas reconnues.
Garder grep -a sur les journaux par précaution : selon ce que le journal
contient à cet instant, grep peut le prendre pour un binaire et ne répondre
que « binary file matches ». Ce n'est pas systématique, le 21/09 il n'était pas nécessaire,
mais -a n'est jamais faux.
Points de vigilance
- Le ping, selon par où l'on passe. Sur le câble local, la passerelle
répond : vérifié le 21/09, deux paquets, zéro perte, 1 ms. C'est à distance,
par la liaison cellulaire, qu'un ping qui échoue ne prouve rien, l'ICMP y étant filtré en
entrée (constaté le 24/06). Dans les deux cas,
curl http://<adresse>/tranche. - Un « Enregistrer » de l'interface d'administration n'enregistre pas toujours (session expirée, accordéon replié). Toujours revérifier le fichier de configuration après modification.
- Ne jamais mettre
0.0.0.0/0comme sous-réseau distant du tunnel. Le firmware le traduit en deux routes qui capturent tout le trafic de la passerelle, y compris vers le poste connecté en Ethernet : la passerelle devient injoignable, même en local.