T5 Déploiement

Configuration initiale, industrialisation multi-passerelles, accès local et à distance

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 CloudGateRé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'applicatifLe 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'usineDéjà mise en service
Adresse192.168.1.1192.168.42.1, ou celle du site
Adresses distribuéesouinon
Le posteen automatique, il reçoit une adresseen 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.

D'où viennent ces valeurs

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'administrationhttp://<adresse>
Canvas LuvitREDhttp://<adresse>:8080
Shellssh 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.

L'heure n'est pas un détail de confort

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.

Après ce changement, le poste perd la passerelle

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.

Le modem est Cat M1 / NB1 uniquement

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.

À confirmer sur la première passerelle vierge

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.

Jamais 0.0.0.0/0 en sous-réseau distant

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.gzLes fichiers de configuration eux-mêmes. Candidat à une réimportation sur une autre passerelle.
gabarit_<site>_<date>.txtL'inventaire lisible des réglages, valeurs sensibles masquées. Celui-là sert toujours : il dit quoi saisir.
Pourquoi avant le déploiement

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.

bashLes scripts en dépendent, un shell POSIX ne suffit pas.
curlTous les échanges avec la passerelle et la plateforme.
python33.6 ou plus. Assemblage du flux et contrôles.
sshpassLa passerelle s'authentifie par mot de passe, elle n'accepte pas de clé.
ssh, scpOpenSSH 8.5 ou plus, voir ci-dessous.
tarCapture du gabarit.
jqUniquement pour inject_dap_state.sh, la restauration manuelle d'état.
OpenSSH plus ancien que 8.5 : tout échoue d'un coup

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.

Conséquence sur qui peut mettre en service

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.

Ce que check refuse

Un 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.

Le premier déploiement se déclare

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.

VariableValeur
cloudgate_device_namenom de cette passerelle
iobase_hostadresse 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
Site, source de données et métriques ne sont plus globaux

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

Les injects du canvas ne font rien

[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

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.

  1. Remise à zéro de la passerelle neuve, puis accès local en Ethernet.
  2. Import du gabarit de la couche CloudGate.
  3. Reprendre les trois valeurs qui ne doivent pas s'hériter (ci-dessous).
  4. Couche B : build.py, deploy.sh, enrolement.sh, puis sante.sh.
  5. Déclarer les capteurs du site, voir Chap. 05.
Trois valeurs ne doivent jamais s'hériter

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.

La réimportation n'est pas prouvée

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.

Pourquoi pas CloudGate Universe

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.

L'ancien rebond par le VPS ne fonctionne plus

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'administrationhttp://192.168.42.1
Canvas LuvitREDhttp://192.168.42.1:8080
Cockpithttp://192.168.42.1:8888 (opérateur) · :8889 (admin) — voir chap. 05
Shellssh 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