feat: redémarrer sans redémarrer Windows — quatre gestes de reprise pour un poste sous kiosque - #39
Merged
Merged
Conversation
Un poste sous kiosque n'a aucune sortie : ni relire un config.json édité à la main, ni relancer le service, ni redémarrer la machine. La seule réponse connue est de couper le courant. ADR-027 n'est pas rouvert : il refuse un redémarrage EXIGÉ PAR UN RÉGLAGE, ce qui reste vrai. Le geste ajouté ici est du dépannage, et il passe par le mécanisme que l'ADR désigne lui-même comme le seul légitime — l'arrêt propre avec un code non nul, que le SCM ou systemd rattrape.
Chaque tâche livre quelque chose de testable seul, dans l'ordre où les suivantes en dépendent : le garde d'abord, l'écran en avant-dernier, les compteurs de SUIVI.md mesurés en dernier.
…e trois arrêts Il refusait de couper le poste pour installer une version. Il refusera aussi de le couper pour redémarrer le service et pour redémarrer l'ordinateur : la règle n'a jamais dépendu de ce qui venait après l'arrêt, elle protège la pesée en cours et le catalogue pas encore en service. Renommage seul. Aucune règle touchée, aucun test réécrit — seules les phrases qui disaient « la mise à jour » disent maintenant « l'arrêt du poste ».
…poste Le _readme du fichier demandait d'arrêter le service, d'éditer, puis de redémarrer — trois gestes qu'un poste sous kiosque ne permet pas. La relecture rejoue le chemin de restoreConfig : validation complète, rechargement à chaud, compte à rebours de 60 s. Elle n'écrit rien — le document EST le fichier — et sur retour arrière elle laisse le fichier tel quel : il n'en existe aucune copie, contrairement à ce que la page envoie. Elle refuse un fichier venu d'un export. Config.Export retire les deux empreintes, le contrôle 31 accepte leur absence à juste titre, et personne ne rattraperait une administration fermée sur une fiche d'installation perdue.
Un poste sous kiosque n'a aucune sortie quand quelque chose est figé : ni console, ni bureau, seulement l'interrupteur. Le bouton refait l'arrêt ordonné de §13.4 — le même que Ctrl+C et que l'arrêt du SCM, pas un second chemin — et rend un code non nul. C'est le code qui fait tout : le SCM applique alors les reprises de §15.2, systemd son Restart=always. Aucun script neuf. Sans superviseur, la route répond 501 : « openscale serve » dans un terminal n'est relancé par personne, et le bouton arrêterait un poste que rien ne rallume.
shutdown.exe /r /t 0 et systemctl reboot. Pas d'API Win32 : activer SeShutdownPrivilege demande trois appels qu'aucune machine de compilation ne peut exercer, et dont l'échec serait un poste qui ne dit rien. Le délai n'est pas ici. Il vit dans l'application, sur l'horloge injectée, pour être le même des deux côtés — systemctl reboot est immédiat et n'a rien à annuler — et pour se prouver sans redémarrer une machine. Aucun test n'appelle Reboot() sous Windows ni sous Linux : l'appel réussirait, et la machine du test redémarrerait.
…viser Sous cage l'unité du kiosque dit qu'il n'y a « littéralement rien vers quoi s'échapper », et sous Shell Launcher le kiosque remplace l'explorateur : le seul recours d'un bénévole était de couper le courant. Le délai vit dans l'application, sur l'horloge injectée, et non dans shutdown.exe. C'est ce qui lui donne le même comportement sous Linux — où systemctl reboot est immédiat et n'a rien à annuler — et ce qui rend l'armement, l'annulation et l'échéance prouvables sans redémarrer une machine. La passe -race est verte sur ces six tests. Le garde refuse pendant une pesée, comme pour une mise à jour : ce qu'il protège est le client debout devant l'écran.
Les deux moitiés existaient. Ce qui est neuf est la règle entre les deux : l'erreur de l'arrêt est rendue sans tenter le démarrage, parce qu'un service qu'on n'a pas su arrêter ne se redémarre pas, et qu'enchaîner répondrait « le service est déjà démarré » à la place de « accès refusé ». Les deux moitiés voyagent comme des fonctions : platform.StopService parle au SCM, et un test qui l'appellerait vraiment arrêterait l'OpenScale installé sur la machine du développeur.
… fois sur deux Quand l'échéance et l'annulation tombent au même instant, les deux cas du select sont prêts et Go en choisit un AU HASARD : la moitié des annulations laissaient la machine partir quand même. C'est désormais le verrou, et non le select, qui tranche : la goroutine réclame le compte à rebours avant d'appeler platform.Reboot, sous le même mutex que Cancel prend. Un compte à rebours annulé — ou réarmé depuis — n'appartient plus à personne. Le test isolé passait ; c'est la suite complète qui l'a montré. Il tourne maintenant cent fois.
…eur, et le dit quand il ne l'a pas Le service tourne en « openscale », pas en root : sans règle polkit, systemctl reboot est refusé et le bouton ne sert à rien. La règle est la plus étroite possible — un compte, une action, ni power-off ni ignore-inhibit — et un test de deploy/ tient ces bornes, en lisant le code de la règle et non ses commentaires. Corrige au passage un silence que je venais d'introduire : un redémarrage REFUSÉ ne disait rien. Le 202 était parti trente secondes plus tôt, le poste tournait toujours, et le bénévole regardait un décompte expirer sur rien. C'est exactement ce que fait un poste Linux sans la règle. Il est maintenant écrit au journal technique, en ERR-SYS-12, distinct du ERR-SYS-11 de « ce poste ne sait pas faire » : les deux remèdes n'ont rien en commun.
…marrer l'ordinateur La réponse est invisible jusqu'au moment où elle coûte : sous Linux, un poste sans sa règle polkit marche parfaitement, jusqu'au soir où un bénévole touche le seul bouton qui l'aurait sauvé. Le contrôle cherche la RÈGLE et non le droit : demander le droit à polkit veut dire appeler systemctl reboot, et la réponse honnête à « ce poste peut-il redémarrer ? » ne vaut pas qu'on redémarre la machine. Au passage, « quinze » disparaît du code. Le nombre était écrit dans une douzaine de commentaires, trois fichiers de tests et cinq paragraphes de l'architecture ; en ajouter un seizième a fait rougir trois tests et laissé tout le reste faux en silence. ControlOrder était déjà documenté comme l'autorité sur le compte — c'est désormais ce que les tests lisent.
Trois boutons rangés par brutalité croissante. Le troisième est rouge, demande une confirmation, puis affiche un décompte annulable : c'est ce qui rend offrable un acte que rien ne défait une fois la machine partie. Le refus lui-même n'est pas recopié dans la rubrique — App.svelte en fait déjà une bannière, et deux exemplaires de la même phrase se lisent comme deux pannes. Ce qui est propre à la rubrique, les fautes d'un fichier refusé une par ligne, s'affiche sous la même forme que la page Poste : bande d'avertissement, jamais du texte rouge, que §14.2 inventorie. L'en-tête de la page disait « There is no restart button », en citant ADR-027 : elle dit maintenant ce que l'ADR refuse vraiment — un redémarrage exigé par un réglage — et ce que ces boutons sont, des réparations pour un poste où aucune console n'est atteignable.
L'architecture disait « pas de POST /admin/api/restart » sans distinguer un redémarrage exigé par un réglage — toujours refusé, le rechargement à chaud couvre tous les blocs — d'un redémarrage de dépannage, qu'aucun écran n'offrait sur un poste sans console ni bureau. ADR-055 pose la distinction ; §14.4, §14.5 et §15.3 la reflètent. TROUBLESHOOTING.md gagne une entrée par symptôme — « quelque chose est figé et on n'a accès à rien » — parce qu'un bénévole ne cherche pas un code, il cherche ce qu'il voit. Le glossaire tranche « poste » (l'application) contre « ordinateur » (la machine), et porte les quatre codes neufs. Compteurs de SUIVI.md MESURÉS : 2 950 tests Go sur 35 paquets, 796 tests front, -race verte sur les trois paquets touchés.
INVOCATION_ID EST HÉRITÉ. Tout enfant d'un service systemd le lit, si bien que « ce processus est-il supervisé ? » répondait oui à un shell, à un script lancé par systemd-run et — mesuré sur le coureur GitHub Actions, qui est lui-même un service — au binaire de test. Le bouton aurait alors arrêté un processus que rien ne relance, ce qu'il existe précisément pour éviter. S'y ajoute donc la condition qui distingue le processus PRINCIPAL d'une unité de sa descendance : systemd le fork directement, son parent est PID 1. C'est le raisonnement que notify.go applique déjà à WATCHDOG_PID. Le 16e contrôle lisait le VRAI système dans un test unitaire. Il était vert sur une machine Windows et rouge sur le coureur, qui n'a pas de règle polkit et n'en aura jamais. La question passe maintenant par l'interface Machine, comme les quatorze autres, et le banc décide de la réponse : les trois cas — autorisé, refusé, sans objet — sont couverts sans dépendre de la machine qui exécute les tests. J'avais écarté cette injection « pour ne pas toucher l'interface ». C'était le mauvais arbitrage, et c'est la CI qui l'a montré.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Un poste installé est enfermé : sous
cagel'unité du kiosque écrit qu'« il n'y alittéralement rien vers quoi s'échapper », et sous Shell Launcher le kiosque remplace
l'explorateur Windows. Trois besoins n'avaient donc aucune porte — relire un
config.jsonmodifié à la main (le
_readmedu fichier demandait « arrêtez le service, éditez,redémarrez »), relancer l'application, redémarrer la machine. La réponse était de couper le
courant.
Les quatre gestes
POST /admin/api/config/reload— relit le fichier tel qu'il est sur le disquePOST /admin/api/restart— le poste s'arrête, son superviseur le relancePOST·DELETE /admin/api/reboot— l'ordinateuropenscale service restartLes trois routes sont derrière la session d'administration et soumises au même garde
que la mise à jour —
station.DowntimeGuard, renommé depuisUpdateGuardparce que larègle n'a jamais dépendu de ce qui venait après l'arrêt.
ADR-027 n'est pas rouvert
L'ADR refuse un redémarrage exigé par un bloc de configuration : le rechargement à
chaud les couvre tous,
network.listencompris, et ça reste vrai. Ce qui est ajouté iciest du dépannage. Le redémarrage lui-même reste celui que l'ADR appelle légitime : la
station s'arrête par le chemin ordonné de §13.4 avec un code non nul, et c'est le SCM ou
systemd qui la relance. Aucun script neuf, aucun second chemin d'arrêt. ADR-055 pose la
distinction.
Trois choses trouvées en construisant
au même instant, les deux cas du
selectsont prêts et Go en choisit un au hasard :la machine repartait quand même. C'est le verrou, et non le
select, qui tranchedésormais. Le test isolé passait ; c'est la suite complète qui l'a montré, et il tourne
maintenant cent fois.
tôt, le poste tournait toujours, et le bénévole regardait un décompte expirer sur rien —
exactement ce que fait un poste Linux dont la règle polkit manque. D'où
ERR-SYS-12aujournal technique, et un 16ᵉ contrôle dans
doctorqui le dit avant que quelqu'unen ait besoin.
config.jsonreconstruit depuis un export fermerait l'administration.Config.Exportretire toujours les deux empreintes, et le contrôle 31 accepte leurabsence à juste titre — la faute n'est pas dans le fichier, elle est dans le fait de lire
ce fichier sur ce poste. Refusé en 422, avec le remède.
Deux décisions qui portent le reste
shutdown /r /t 30. Sous Linuxsystemctl rebootest immédiat et n'a rien à annuler :un délai porté par l'OS donnerait deux comportements pour un bouton — et il ne se
testerait pas sans redémarrer une machine.
writeConfigfaitl'inverse, à juste titre : là le document venait de l'écran, qui en garde une copie. Ici
il vient de la main de quelqu'un, et l'écraser détruirait le seul exemplaire.
Installation Linux
Le service tourne en
openscale, pas en root :install.shpose/etc/polkit-1/rules.d/49-openscale-reboot.rules, portée la plus étroite possible — uncompte, une action, ni
power-offniignore-inhibit. Un test dedeploy/tient cesbornes en lisant le code de la règle et non ses commentaires.
« Quinze contrôles » disparaît du dépôt
Le nombre était écrit dans une douzaine de commentaires, trois fichiers de tests et cinq
paragraphes de l'architecture ; en ajouter un seizième a fait rougir trois tests et laissé
tout le reste faux en silence.
diag.ControlOrderétait déjà documenté comme l'autorité surle compte — c'est désormais ce que les tests lisent.
Vérification
(
CGO_ENABLED=0 go test ./... -count=1 -v)npm test) ·svelte-check: 0 erreurgofmt,go vetsilencieux ·boundaryetdepsverts-raceverte surinternal/web,internal/stationetdeploy/windows,linux,linux/arm64Ce qui reste à valider sur le poste, et qui est dit
platform.Reboot()n'est exercé par aucun test sous Windows ni sous Linux : l'appelréussirait, et la machine du test redémarrerait. Restent donc à éprouver sur un poste réel
le redémarrage machine lui-même, et sous Linux la règle polkit posée par
install.sh.Hors périmètre, et dit : le kiosque n'est pas redémarrable depuis l'écran — il se
reconnecte seul en SSE, son superviseur le relance en moins de 2 s, et surtout un écran
tactile figé ne peut pas recevoir le clic qui le réparerait. Pas d'extinction de
l'ordinateur non plus : un poste éteint à distance ne se rallume pas à distance.