Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions SUIVI.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,30 @@
> Tableau de bord. À mettre à jour au fil de l'eau — c'est le premier fichier à lire
> pour savoir où on en est.

**Le démarrage à froid de la v0.6 montrait une panne qui n'existait pas (29/07/2026).**
Poste redémarré après l'installation : session `openscale` ouverte seule, puis une page
blanche pendant deux minutes, puis un redémarrage du navigateur que personne n'avait
demandé. Rien n'était cassé — c'est l'addition de deux mécanismes voulus. Le service est en
démarrage automatique **différé** et Windows fixe ce différé à **120 s** par défaut
(`AutoStartDelay` absent du registre) ; la tâche du kiosque, elle, part 5 s après
l'ouverture de session. Mesuré : démarrage `17:47:54`, kiosque `17:48:15`, service
`17:50:11`, navigateur relancé sur l'écran client `17:50:12`. Le « redémarrage » observé
**était** le mécanisme de retour du superviseur, pas la panne.

Ce qui a été fait, dans l'ordre où ça se voit à l'écran :

| # | Correction | État |
|---|---|---|
| 1 | `AutoStartDelay = 20` posé par `install.ps1`, sauvegardé et restauré comme les autres réglages écrasés | ✅ |
| 2 | Délai de grâce de 20 s : rien n'est affiché tant que le poste n'a jamais répondu | ✅ |
| 3 | Deux formulations d'attente — « Application en cours de démarrage… » puis « Le poste redémarre… » — et trois points animés en CSS | ✅ |
| 4 | `C:\ProgramData\OpenScale\kiosk.log` : la sortie du superviseur n'allait nulle part | ✅ |

Le diagnostic a dû se faire à la pince — heures de création des processus, journal système —
faute justement de ce journal. **Reste ouvert** : `kiosk.log` n'est pas dans
`diagnostic.zip`, ce qui oblige `TROUBLESHOOTING.md` à demander un second fichier alors que
la promesse était « le fichier de diagnostic, et lui seul ».

**La mise à jour se déclenche depuis l'écran, et elle est livrée (29/07/2026).** ADR-040 :
le poste sonde une fois par jour l'API des publications du dépôt suivi, porte une pastille
au tableau de bord, télécharge l'archive au clic, **vérifie son empreinte SHA-256**,
Expand Down Expand Up @@ -42,6 +66,7 @@ est dépassé est **effacée** au lieu d'opposer `ErrAlreadyRunning` pour toujou
conséquence directe de ce que le banc a mesuré, un `Start()` qui rend `nil` sans rien
lancer. Et un lancement qui **échoue** efface son `pending.json` : le processus est encore
vivant pour le faire.

**Installer la v0.5 comme un bénévole a buté six fois (29/07/2026).** L'archive publiée a
été posée sur `PC-RECEPTION` par `install.ps1` sans option, puis conduite étape par étape
comme `INSTALLATION.md` la décrit. Le poste tourne — service automatique, balance GRAM sur
Expand Down
26 changes: 21 additions & 5 deletions TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,22 @@ Il fonctionne même quand le poste ne démarre pas — c'est là qu'il sert le p
4. **Voyez-vous l'écran de connexion de Windows ?** Alors ce n'est pas le poste qui est
en panne : voir la section suivante, c'est la panne la plus fréquente et la plus
coûteuse.
5. **Voyez-vous le bureau de Windows, avec la barre des tâches ?** L'écran client ne
5. **Le poste vient-il de démarrer ?** Les vingt premières secondes après l'ouverture de
session, l'écran reste noir **exprès** : le service finit de démarrer, et le poste
préfère ne rien montrer plutôt que d'afficher une page qu'il remplacerait aussitôt.
Comptez jusqu'à vingt avant de conclure.
6. **Voyez-vous le bureau de Windows, avec la barre des tâches ?** L'écran client ne
s'est pas lancé. Ouvrez le Planificateur de tâches, trouvez **OpenScale-Kiosk**, clic
droit → **Exécuter**. Si ça remet l'écran, la tâche existe mais ne s'est pas
déclenchée : relancez `install.ps1`.
6. **Voyez-vous une page blanche avec « Le poste redémarre… » ?** Le service n'a pas
encore répondu. Attendez cinq secondes. Si la phrase reste, allez à « Le poste ne
répond pas du tout ».
7. **Voyez-vous « Le poste rencontre un problème — ERR-KSK-02 » ?** L'affichage
7. **Voyez-vous une page blanche avec « Application en cours de démarrage… » ?** Le
service met plus de vingt secondes à répondre. Attendez encore une minute — un poste
qui a beaucoup de photos produit à relire au premier démarrage est plus lent. Si la
phrase reste, allez à « Le poste ne répond pas du tout ».
8. **Voyez-vous une page blanche avec « Le poste redémarre… » ?** Même chose, mais le
poste avait déjà fonctionné depuis son démarrage : c'est le service qui s'est arrêté
en cours de journée. Attendez cinq secondes, puis même section.
9. **Voyez-vous « Le poste rencontre un problème — ERR-KSK-02 » ?** L'affichage
n'arrive pas à rester ouvert : le navigateur se ferme dès qu'il s'ouvre. Le poste a
cessé de réessayer exprès, pour ne pas clignoter devant les clients. Lancez `doctor`,
et prévenez un responsable avec le fichier de diagnostic.
Expand Down Expand Up @@ -345,6 +353,14 @@ Si l'écran ne s'ouvre pas :
& "C:\Program Files\OpenScale\openscale.exe" doctor --zip
```

**Et s'il ne s'est rien passé à l'écran au démarrage**, joignez le journal de l'écran
client, qui dit ce qu'il a affiché et pourquoi — c'est le seul document qui répond à
« qu'est-ce qu'il y avait sur l'écran avant que j'arrive ? » :

```
C:\ProgramData\OpenScale\kiosk.log
```

---

## Les codes, pour en parler au téléphone
Expand Down
30 changes: 27 additions & 3 deletions cmd/openscale/kiosk.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"bytes"
"context"
"flag"
"fmt"
Expand All @@ -23,11 +24,28 @@ import (
// label: everything it knows about the station it learns from one URL. That is what
// makes it restartable at will, and what makes « the browser died » a non-event.
func runKiosk(ctx context.Context, args []string, out io.Writer) error {
options, err := parseKioskOptions(args, out)
// What the option parsing has to say is kept aside until the journal exists. It
// cannot be opened any earlier — its own path is one of the options — and « la
// configuration est illisible » is exactly the line somebody looks for afterwards.
var parsed bytes.Buffer
options, err := parseKioskOptions(args, io.MultiWriter(out, &parsed))
if err != nil {
return err
}

if options.logPath != "" {
journal, err := kiosk.OpenLog(options.logPath, kiosk.DefaultLogSize)
if err != nil {
// A journal is a diagnostic aid, never a reason to leave a customer in front
// of a black screen. The sentence goes to the stream that is left.
fmt.Fprintf(out, "openscale kiosk : %v\n", err)
} else {
defer func() { _ = journal.Close() }()
_, _ = journal.Write(parsed.Bytes())
out = io.MultiWriter(out, journal)
}
}

browser, found := kiosk.Find(browserCandidates(), kiosk.LookBrowser(programDirectories()))
if !found {
// The one failure no relaunch fixes, and the only one this subcommand refuses
Expand Down Expand Up @@ -59,6 +77,9 @@ func runKiosk(ctx context.Context, args []string, out io.Writer) error {
type kioskOptions struct {
url string
profileDir string
// logPath is where the supervisor's lines are kept. Empty means « nowhere but the
// standard output », which is what a station under systemd already has.
logPath string
}

// parseKioskOptions resolves the address of the client screen.
Expand All @@ -74,9 +95,10 @@ func parseKioskOptions(args []string, out io.Writer) (kioskOptions, error) {
configPath = fs.String("config", os.Getenv("OPENSCALE_CONFIG"), "fichier de configuration")
address = fs.String("url", "", "adresse de l'écran client, sinon celle de la configuration")
profile = fs.String("profile", "", "répertoire de profil du navigateur")
logPath = fs.String("log", platform.DefaultKioskLogPath(), "journal du superviseur")
)
fs.Usage = func() {
fmt.Fprint(out, `Usage : openscale kiosk [--config fichier] [--url http://hôte:port] [--profile répertoire]
fmt.Fprint(out, `Usage : openscale kiosk [--config fichier] [--url http://hôte:port] [--profile répertoire] [--log fichier]

Le superviseur de navigateur : il ouvre l'écran client en plein écran et le relance
s'il se ferme. C'est ce que lance la tâche planifiée « `+taskName+` » à l'ouverture de
Expand All @@ -88,6 +110,8 @@ Options :
--url <adresse> adresse de l'écran client ; prioritaire sur la configuration
--profile <répertoire> profil dédié du navigateur, effacé à chaque démarrage ;
sinon un répertoire sous le dossier temporaire du compte
--log <fichier> journal du superviseur, en plus de la sortie standard ;
--log "" ne journalise que sur la sortie standard
`)
}
positional, err := parseMixed(fs, args)
Expand All @@ -99,7 +123,7 @@ Options :
return kioskOptions{}, fmt.Errorf("argument inattendu %q : kiosk ne prend que des options", positional[0])
}

o := kioskOptions{url: *address, profileDir: *profile}
o := kioskOptions{url: *address, profileDir: *profile, logPath: *logPath}
if o.profileDir == "" {
o.profileDir = kiosk.DefaultProfileDir()
}
Expand Down
13 changes: 13 additions & 0 deletions deploy/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,19 @@ if ($text -notmatch 'K7M4Q2XR') { throw 'la fiche ne porte pas le code de secour
if ($text -match 'RECOPIER ICI') { throw 'la fiche demande de recopier un code qu''elle porte déjà' }
if ($text -notmatch 'seule copie') { throw 'la fiche ne dit pas qu''elle est la seule copie du code' }

# --- 9. Un instantané écrit par une version ANTÉRIEURE se relit sans exploser ------
# restore.json n'est jamais réécrit : celui d'un poste installé il y a six mois ne
# connaît pas les sections que l'installeur d'aujourd'hui y met. Sous
# « Set-StrictMode -Version Latest », lire une propriété absente ÉCHOUE — et ce serait
# la désinstallation, le geste qui doit toujours marcher, qui casserait.
$old = Read-Snapshot -Path $restore
if ($null -ne (Get-SnapshotValue (Get-SnapshotValue $old 'service_control') 'AutoStartDelay')) {
throw 'une section absente de l''instantane a rendu une valeur'
}
if ((Get-SnapshotValue $old.winlogon 'DefaultUserName') -ne 'ancien') {
throw 'Get-SnapshotValue perd une valeur presente'
}

Write-Output 'TOUT-EST-VERIFIE'
`
writeScript(t, harness, body)
Expand Down
46 changes: 46 additions & 0 deletions deploy/windows/common.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ $script:BinaryName = 'openscale.exe'
# Les clés que l'installeur écrase, donc celles qu'il doit savoir remettre (important-15).
$script:WinlogonKey = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
$script:WindowsUpdateKey = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate'
$script:ServiceControlKey = 'HKLM:\SYSTEM\CurrentControlSet\Control'

# Le différé appliqué aux services à démarrage automatique différé, en secondes. Il vaut
# 120 s par défaut chez Windows, et le service du poste en fait partie : c'est ce qui
# faisait attendre le kiosque deux minutes après une coupure de courant.
$script:AutoStartDelaySeconds = 20

# La suspension USB sélective : le GUID du sous-groupe « Paramètres USB » et celui du
# réglage lui-même. Ils sont RECOPIÉS de docs/02-architecture.md §15.2 — on ne devine pas
Expand Down Expand Up @@ -405,6 +411,25 @@ function Get-RegistryValue {
$property.$Name
}

function Get-SnapshotValue {
<#
.SYNOPSIS
Lit une valeur d'instantané, ou $null quand la section ou la valeur manque.
.DESCRIPTION
« Set-StrictMode -Version Latest » fait ÉCHOUER l'accès à une propriété absente d'un
PSCustomObject. Un restore.json écrit par une version antérieure de l'installeur n'a
pas les sections que la version d'aujourd'hui y met : sans cette fonction, désinstaller
un poste installé il y a six mois s'arrête sur « The property cannot be found », et
c'est la désinstallation — le geste qui doit toujours marcher — qui casse.
#>
[CmdletBinding()]
param($Section, [Parameter(Mandatory)][string]$Name)

if ($null -eq $Section) { return $null }
if (-not ($Section.PSObject.Properties.Name -contains $Name)) { return $null }
$Section.$Name
}

function Get-SystemSettings {
<#
.SYNOPSIS
Expand Down Expand Up @@ -433,6 +458,11 @@ function Get-SystemSettings {
ActiveHoursStart = Get-RegistryValue $script:WindowsUpdateKey 'ActiveHoursStart'
ActiveHoursEnd = Get-RegistryValue $script:WindowsUpdateKey 'ActiveHoursEnd'
}
service_control = @{
# Le différé des services à démarrage automatique différé. Il vaut pour TOUTE la
# machine, pas seulement pour le nôtre : c'est ce qui en fait un réglage à remettre.
AutoStartDelay = Get-RegistryValue $script:ServiceControlKey 'AutoStartDelay'
}
power = @{
# Le plan actif est identifié par son GUID : « SCHEME_CURRENT » n'a de sens que
# pendant la session qui l'a lu, et une désinstallation a lieu des mois plus tard.
Expand Down Expand Up @@ -557,6 +587,22 @@ function Restore-SystemSettings {
}
}

# Le différé des services, remis AVANT le reste parce qu'il peut manquer de
# l'instantané : les postes installés avant que l'installeur ne touche à ce réglage ont
# un restore.json qui n'en parle pas, et restore.json n'est jamais réécrit. « Absent »
# veut alors dire « c'est nous qui l'avons posé », donc on le supprime — au risque
# assumé de rendre son défaut à une machine qui en avait choisi un autre avant nous.
$serviceControl = Get-SnapshotValue $Snapshot 'service_control'
$autoStartDelay = Get-SnapshotValue $serviceControl 'AutoStartDelay'
if ($null -eq $autoStartDelay) {
Remove-ItemProperty -Path $script:ServiceControlKey -Name 'AutoStartDelay' -ErrorAction Ignore
Write-Step 'démarrage différé des services : réglage retiré (il n''existait pas avant l''installation)' $LogFile
}
else {
Set-ItemProperty -Path $script:ServiceControlKey -Name 'AutoStartDelay' -Value $autoStartDelay -Type DWord
Write-Step 'démarrage différé des services remis à sa valeur d''origine' $LogFile
}

foreach ($name in 'SetActiveHours', 'ActiveHoursStart', 'ActiveHoursEnd') {
$value = $Snapshot.windows_update.$name
if ($null -eq $value) {
Expand Down
9 changes: 9 additions & 0 deletions deploy/windows/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,15 @@ else {
}

# --- 4. Service et tâche du kiosque ------------------------------------------------
# Le service est en démarrage automatique DIFFÉRÉ (internal/platform/service_windows.go) :
# les disques, la pile réseau et le spouleur d'impression passent devant. Windows fixe ce
# différé à 120 s par défaut, et personne ne l'a choisi — mesuré sur PC-RECEPTION le
# 29/07/2026 : démarrage à 17:47:54, service à 17:50:11, soit deux minutes pendant
# lesquelles le kiosque n'avait rien d'autre à afficher que sa page d'attente. 20 s
# laissent passer ce qui doit passer sans faire attendre le premier client du samedi.
Set-ItemProperty $script:ServiceControlKey 'AutoStartDelay' $script:AutoStartDelaySeconds -Type DWord
Write-Step "démarrage différé des services ramené à $($script:AutoStartDelaySeconds) s (défaut Windows : 120 s)" $paths.LogFile

$startMode = if ($Pilot) { 'demand' } else { 'auto' }
& $paths.Binary service install --start $startMode --config $paths.Config --data $paths.DataDir
Assert-Success 'openscale service install'
Expand Down
Loading