AG-05 · Embedded Runtime
nova-agent — Binaire embarqué
Le pont logiciel entre la plateforme cloud NOVA et le mini-PC qui pilote le télescope. Conçu pour être sûr par défaut, minimal, auditable et installable en 5 minutes par un non-développeur.
En une phrase
nova-agent est un binaire Rust statique de ~5 Mo qui tourne sur le mini-PC MeLE,
établit une connexion HTTPS sortante toutes les 30 secondes vers nova-obsrouen.pages.dev,
et exécute des commandes ASCOM (slew, capture, focus) uniquement si l'opérateur de l'observatoire a explicitement activé le mode pilotage.
Pourquoi un agent embarqué
Le mini-PC de l'observatoire est derrière une box ADSL/Fibre résidentielle ou la box de l'association — pas d'IP publique, pas de port forwarding, souvent sans VPN. Trois architectures envisageables :
- Pas d'agent — l'utilisateur prépare uniquement, l'opérateur exécute manuellement. Plus simple mais plus lent.
- VPN entrant (WireGuard, ZeroTier) — permet le SSH/RDP direct, mais demande à l'observatoire d'ouvrir un tunnel permanent. Risque sécuritaire.
- Agent sortant pull ← choix NOVA — l'observatoire n'ouvre aucun port, l'agent poll la queue cloud. Zéro surface d'attaque entrante.
L'option 3 est devenue le standard de facto pour les agents distants (Tailscale Funnel, Cloudflare Tunnel, GitHub Actions self-hosted runners) : l'observatoire reste maître de sa connexion sortante, qu'il peut couper d'un clic.
Installation — Windows 10/11
Pré-requis
- Windows 10 (1909+) ou Windows 11
- ASCOM Platform 6.6+ installée (depuis ascom-standards.org)
- Drivers ASCOM : Atik, ZWO, Robofocus, Temma déjà installés et testés via ASCOM Diagnostics
- Connexion Internet sortante autorisée vers
nova-obsrouen.pages.dev:443 - ~50 Mo libres dans
C:\nova-agent\
Étape 1 — Télécharger
Décompresser nova-agent-windows-x64.zip dans C:\nova-agent\ (créer le dossier si nécessaire).
Contenu après extraction :
C:\nova-agent\
├── nova-agent.exe ← binaire principal (5 Mo)
├── nova-agent-config.toml ← configuration éditable
├── README.txt ← démarrage rapide
├── LICENSE.md ← MIT
├── SECURITY.md ← inventaire fail-safes
└── logs\ ← dossier logs (créé à la 1ère exécution)
Étape 2 — Configurer
Ouvrir nova-agent-config.toml dans le bloc-notes et adapter :
[server]
endpoint = "https://nova-obsrouen.pages.dev"
heartbeat_interval_s = 30
token = "nva_xxxxxxxxxxxxxxxxxxxxxxxx" # à coller depuis le mail d'activation
[observatory]
name = "Observatoire de Rouen"
lat = 49.44649
lon = 1.09760
altitude_m = 65
timezone = "Europe/Paris"
[hardware]
mount_driver = "ASCOM.Temma.Telescope"
camera_driver = "ASCOM.Atik460EX.Camera"
guider_driver = "ASCOM.ASIGuide.Camera"
focuser_driver = "ASCOM.Robofocus.Focuser"
[control]
enabled = false # ← MODE LECTURE SEULE PAR DÉFAUT
max_slew_per_hour = 20
max_session_duration_min = 480
require_acknowledge = true
abort_on_clouds = true
[safety]
min_altitude_deg = 5
max_altitude_deg = 89
min_exposure_s = 0
max_exposure_s = 1800
kill_switch_path = "C:\\nova-agent\\STOP"
park_on_disconnect = true
park_on_dawn = true
[logging]
log_dir = "logs"
log_level = "info"
log_rotate_days = 30
Étape 3 — Premier lancement (mode lecture seule)
Double-cliquer nova-agent.exe. Une console s'ouvre :
nova-agent v0.1.0
Mode: READ-ONLY (control.enabled = false)
Heartbeat target: https://nova-obsrouen.pages.dev/api/control/heartbeat
Observatory: Observatoire de Rouen (49.44649, 1.09760)
ASCOM Telescope: ASCOM.Temma.Telescope ............ OK
ASCOM Camera: ASCOM.Atik460EX.Camera ........... OK
ASCOM Guider: ASCOM.ASIGuide.Camera ............ OK
ASCOM Focuser: ASCOM.Robofocus.Focuser .......... OK
Heartbeat #1 sent (200 OK, 142 ms)
Kill switch: not present (OK)
Listening for commands... (READ-ONLY: commands logged but not executed)
L'agent envoie un heartbeat toutes les 30 s avec le statut hardware (température capteur, position monture, météo,
connexions ASCOM). Si une commande arrive (depuis /control ou l'API), elle est loguée mais non exécutée.
Étape 4 — Activer le mode pilotage (optionnel)
⚠ Étape critique — autorisation explicite
L'activation du mode pilotage doit faire l'objet d'une autorisation écrite du responsable de l'observatoire (Eric Mandon pour Rouen). NOVA ne suppose aucun pilotage sans accord.
Pour activer :
- Arrêter l'agent (Ctrl+C ou fermer la console).
- Éditer
nova-agent-config.toml→[control] enabled = true. - Relancer l'agent. Le banner affichera maintenant
Mode: CONTROL (commands will be executed). - Le kill switch reste actif : créer le fichier
C:\nova-agent\STOPcoupe l'agent en < 10 s.
Installation — Linux (avancé)
Pour les setups Linux (ce n'est pas le cas de Rouen, mais documenté pour les futures stations) :
# Téléchargement
$ wget https://nova-obsrouen.pages.dev/dl/nova-agent-linux-x64.tar.gz
$ tar xzf nova-agent-linux-x64.tar.gz -C /opt/
# Service systemd
$ sudo cp /opt/nova-agent/nova-agent.service /etc/systemd/system/
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now nova-agent
# Logs
$ journalctl -u nova-agent -f
Sur Linux, l'agent utilise INDI au lieu d'ASCOM (driver indi-temma, indi-atik, etc.).
Tous les fail-safes restent identiques.
Protocole heartbeat
Toutes les 30 s, l'agent envoie un POST :
POST /api/control/heartbeat HTTP/1.1
Host: nova-obsrouen.pages.dev
Authorization: Bearer nva_xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"agent_version": "0.1.0",
"observatory": "Observatoire de Rouen",
"timestamp": "2026-06-21T22:34:15.123Z",
"mode": "control",
"hardware": {
"mount": {
"connected": true,
"tracking": true,
"ra_hours": 5.587,
"dec_deg": 41.269,
"alt_deg": 67.2,
"az_deg": 102.5
},
"camera": {
"connected": true,
"temp_C": -15.2,
"cooling_pct": 64
},
"guider": { "connected": true, "rms_arcsec": 0.82 },
"focuser": { "connected": true, "position": 18432, "temperature_C": 11.8 }
},
"weather": {
"source": "open-meteo",
"cloud_pct": 12,
"humidity_pct": 78,
"wind_kmh": 8.5,
"temperature_C": 12.3
},
"current_session": {
"target": "M31",
"filter": "L",
"subs_completed": 14,
"subs_planned": 30,
"elapsed_min": 42
},
"alerts": []
} Le serveur répond avec :
200 OK
{
"ok": true,
"serverTime": "2026-06-21T22:34:15.456Z",
"next_heartbeat_s": 30
} Queue de commandes
Après chaque heartbeat, l'agent fait un GET sur la queue :
GET /api/control/queue HTTP/1.1
Authorization: Bearer nva_xxxxxxxxxxxxxxxxxxxxxxxx
200 OK
[
{
"id": "cmd-2026-06-21-001",
"issued_at": "2026-06-21T22:33:00Z",
"cmd": "goto",
"params": { "ra_hours": 0.7128, "dec_deg": 41.269 },
"deadline": "2026-06-21T22:40:00Z",
"ack_required": true
}
] L'agent valide chaque commande contre ses fail-safes hard-codés avant exécution :
- Altitude calculée pour les coordonnées demandées doit être dans
[min_altitude_deg, max_altitude_deg]. - Exposure dans
[min_exposure_s, max_exposure_s]. - Token bearer valide et préfixé
nva_. control.enabled = true.- Fichier
STOPabsent. - Quota slew/h non dépassé.
Si une validation échoue, l'agent POST un résultat "status": "rejected" avec la raison, sans exécuter.
Résultats de commande
Après exécution ou rejet, l'agent POST :
POST /api/control/result HTTP/1.1
Authorization: Bearer nva_xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"cmd_id": "cmd-2026-06-21-001",
"status": "ok",
"executed_at": "2026-06-21T22:35:12.123Z",
"duration_ms": 8421,
"result": {
"platesolve": {
"ra_hours": 0.7129,
"dec_deg": 41.270,
"rotation_deg": 178.3,
"scale_arcsec_per_px": 0.334
},
"final_position_error_arcsec": 3.2
},
"logs": [
"2026-06-21T22:35:03 SLEW start",
"2026-06-21T22:35:08 SLEW done (5.1s)",
"2026-06-21T22:35:09 CAPTURE 5s for solve",
"2026-06-21T22:35:14 SOLVE start (ASTAP)",
"2026-06-21T22:35:16 SOLVE done (RA 0.7129 Dec 41.270)",
"2026-06-21T22:35:17 SYNC mount",
"2026-06-21T22:35:18 GOTO offset 8.3'",
"2026-06-21T22:35:23 IDLE position locked"
]
} Inventaire des fail-safes
| # | Fail-safe | Description | Bypass possible ? |
|---|---|---|---|
| FS1 | Mode lecture seule par défaut | L'agent démarre toujours avec control.enabled = false | Édition fichier config + restart |
| FS2 | Kill switch fichier | Présence de STOP dans le dossier agent ⇒ arrêt en < 10 s | Non (hardcoded) |
| FS3 | Token bearer obligatoire | Refus si Authorization manquant ou ne commence pas par nva_ | Non (hardcoded) |
| FS4 | Altitude minimale | Refus slew si Alt < 5° (collisions trépied) | Non (hardcoded) |
| FS5 | Altitude maximale | Refus slew si Alt > 89° (zone interdite zénith) | Non (hardcoded) |
| FS6 | Plage exposure | Refus si exposure_s hors [0, 1800] | Non (hardcoded) |
| FS7 | Timeout commande | Toute commande abandon après 60 s sans réponse hardware | Non (hardcoded) |
| FS8 | Quota slew par heure | Max 20 slew/h pour limiter usure mécanique | Config (par défaut 20) |
| FS9 | Quota durée session | Max 480 min (8 h) par session continue | Config (par défaut 480) |
| FS10 | Park on disconnect | Si le serveur ne répond plus pendant 5 heartbeats consécutifs ⇒ park mount | Config |
| FS11 | Park on dawn | Park automatique 30 min avant lever du Soleil | Config |
| FS12 | Connexion sortante uniquement | Aucun port n'est ouvert côté observatoire | Non (architecture) |
| FS13 | Logs locaux indépendants | Tous les événements sont écrits dans logs\ en clair, auditables | Non (hardcoded) |
| FS14 | Mode dry-run | Flag CLI --dry-run exécute toute la logique sans envoyer les commandes ASCOM | Flag |
Modèle de menace et de confiance
Trois acteurs sont en jeu :
- L'opérateur d'observatoire (Eric Mandon, Rouen) — confiance ultime, maître du matériel, peut tout arrêter.
- L'opérateur NOVA (Fabrice Langlois) — confiance technique, peut envoyer des commandes mais soumises aux fail-safes.
- L'utilisateur final — confiance limitée, ses commandes passent par le scheduler qui les valide avant de les mettre en queue.
Menaces couvertes :
- Commande malveillante en queue ⇒ fail-safes refusent (altitude, exposure, quota).
- Compromission du token ⇒ rotation manuelle par l'opérateur observatoire (regénérer config + restart).
- Bug logiciel du serveur ⇒ kill-switch fichier reste accessible localement.
- Coupure réseau ⇒ park on disconnect.
- Conditions météo dégradées ⇒ abort_on_clouds vérifie chaque heartbeat.
Menaces non couvertes (à ce stade) :
- Compromission du PC mini-PC MeLE (malware Windows) — au-delà du périmètre de l'agent.
- Attaque physique sur le matériel — vandalisme (problème historique de Rouen).
- Chute de température extrême non détectée par capteur ambient — hardcoder un seuil futur.
Mise à jour de l'agent
La mise à jour est manuelle par design — pas d'auto-update silencieux :
- L'opérateur reçoit un mail de notification avec checksum SHA-256 de la nouvelle version.
- Télécharger le nouveau ZIP depuis
https://nova-obsrouen.pages.dev/dl/nova-agent-windows-x64-v0.2.zip. - Vérifier le SHA-256 (commande PowerShell :
Get-FileHash file.zip). - Arrêter l'agent (Ctrl+C).
- Renommer l'ancien binaire en
nova-agent-v0.1.exe.bak. - Décompresser le nouveau, conserver le
nova-agent-config.tomlexistant. - Relancer.
Les notes de version sont publiées sur /dl/CHANGELOG.txt.
Désinstaller
- Arrêter l'agent (Ctrl+C).
- Supprimer le dossier
C:\nova-agent\. - Optionnel : supprimer la tâche planifiée si configurée (Task Scheduler → nova-agent).
L'agent ne modifie aucune entrée registre Windows. La suppression est complète.