Protocole de l'agent
Quatre routes derrière un jeton porteur. Assez petit pour se lire d'une traite, et implémenté deux fois pour ne pas pouvoir diverger.
L'agent est un petit démon installé sur la machine qui fait tourner les VM. Il n'existe que pour une chose : permettre au téléphone d'allumer une VM avant de s'y connecter, puis d'apprendre sur quel port sa console écoute. Tout le reste de wisq fonctionne sans lui.
Transport
HTTP/1.1, port 7442 par défaut, toutes les routes sous /v1. L'authentification est un jeton porteur, comparé en temps constant — c'est une crédence sur un réseau que le démon ne contrôle pas, et une comparaison octet par octet en fuit le préfixe à qui sait mesurer.
Authorization: Bearer <jeton>Le jeton est tiré de la source aléatoire du système au premier lancement et conservé dans ~/.wisq-agent/token, lisible par son seul propriétaire. Pas de compte, pas de mot de passe : un jeton, révocable en supprimant le fichier.
Routes
GET /v1/vms
[
{
"id": "debian-13",
"name": "Debian 13",
"state": "running",
"consoleProtocol": "vnc",
"consolePort": 5901,
"guestOS": "linux"
}
]state vaut running, paused, stopped, starting ou unknown. consolePort et consoleProtocol sont absents tant que la console n'existe pas — c'est précisément ce que le client attend en interrogeant.
GET /v1/vms/{id}
Le même objet pour une seule machine. C'est la route que le client interroge en boucle pendant un démarrage, et un 404 au message lisible quand l'identifiant est inconnu.
POST /v1/vms/{id}/start
Démarre la machine et répond immédiatement avec l'état starting. Le démarrage d'un invité prend des dizaines de secondes, et une requête HTTP maintenue ouverte aussi longtemps ne survit pas à un téléphone qui change de cellule.
POST /v1/vms/{id}/stop
{ "force": false }false envoie un arrêt ACPI, true coupe l'alimentation. Un corps vide vaut false.
Erreurs
Tout code hors 2xx porte un corps JSON. Le message est affiché à la personne qui utilise l'application : il doit se lire comme une phrase.
{ "error": "VM introuvable : debian-13" }Appairage
Au lancement, le démon imprime un lien par adresse joignable. Ouvert sur l'iPhone, le lien arrive sur l'écran d'import, adresse et jeton remplis, interrogation déjà lancée.
wisq://agent?host=nas&port=7442&token=…&name=nasLes adresses de boucle locale ne sont jamais proposées : un lien vers 127.0.0.1 ne sert à rien depuis un téléphone. Le démon s'annonce aussi en Bonjour sous _wisq-agent._tcp, au mieux des outils présents — avahi-publish-service sur Linux, dns-sd sur macOS, et silencieusement rien sinon. Une commodité absente ne doit jamais empêcher le démon de servir.
Implémentation
Le démon est en Rust, le client en Swift. Ils n'ont pas les mêmes contraintes : un programme sans interface ni framework de plateforme n'a aucune raison d'embarquer un runtime de langage. Statiquement lié à celui de Swift, il pesait 58 Mo pour servir quatre routes ; il en fait 582 Ko, un seul fichier statique qui tourne sur n'importe quel Linux, Alpine compris.
Zéro dépendance, délibérément. C'est un programme qu'on installe par un script shell dans un tube : ses dépendances deviennent celles de l'utilisateur — et le protocole ci-dessus est assez petit pour qu'un serveur HTTP/1.1 et un écrivain JSON écrits à la main pèsent moins que la glu qu'un framework demanderait.
Le format de fil est gardé par un test qui traverse les deux langages : la suite Swift lance le vrai binaire Rust sur un port éphémère et l'interroge avec le client que l'application embarque, puis analyse les liens d'appairage du démon avec le parseur de l'app. C'est le seul endroit où une divergence entre les deux moitiés peut se voir.