diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e51aada..ed84f34 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -114,18 +114,6 @@ jobs: GOOS=$os GOARCH=$arch CGO_ENABLED=0 \ go build -trimpath -ldflags="$ldflags" -o "dist/$name" ./cmd/ajean done - # Client léger « poste distant » ajean-remote : pas de CGO (ni UI ni - # systray), donc les 6 cibles se cross-compilent ici, macOS compris. - # Noms d'asset ajean-remote-[-arm][.exe] — la page /download les - # présente à part (« piloter un autre PC »). - for t in linux/amd64:ajean-remote-linux linux/arm64:ajean-remote-linux-arm \ - windows/amd64:ajean-remote-windows.exe windows/arm64:ajean-remote-windows-arm.exe \ - darwin/amd64:ajean-remote-macos darwin/arm64:ajean-remote-macos-arm; do - target=${t%%:*}; name=${t#*:} - os=${target%/*}; arch=${target#*/} - GOOS=$os GOARCH=$arch CGO_ENABLED=0 \ - go build -trimpath -ldflags="-s -w" -o "dist/$name" ./cmd/ajean-remote - done - uses: actions/download-artifact@v4 with: name: macos diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index b022c73..c52fbb0 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,15 +1,19 @@ -Piloter un autre PC fonctionne maintenant à distance, à travers ajean.link, sans que le relais ne voie jamais rien. +Le poste distant est maintenant intégré à AJEAN : plus de binaire séparé, plus de téléchargement à part. -## Le poste distant passe par ajean.link, chiffré de bout en bout +## Une seule application -La 0.8.9 a introduit le poste distant, mais uniquement sur le réseau local. Il fonctionne désormais **depuis n'importe où**, via ajean.link — et avec la même exigence que le reste d'ajean.link : le **relais reste aveugle**. +Jusqu'ici, piloter un autre PC demandait d'installer un second binaire, « ajean-remote ». C'était une application de plus à télécharger, à suivre et à mettre à jour. On l'a supprimée : tout vit désormais dans **AJEAN**. -Concrètement, l'identité d'un poste n'est plus une clé partagée mais une **paire de clés** qui lui est propre. À l'appairage, le poste scelle sa demande vers la clé publique de ton serveur : le relais ne voit ni le code, ni rien d'autre. Ensuite, tout ce qui circule entre le poste et ton serveur — les commandes de l'IA comme leurs résultats — est **chiffré de bout en bout** avec une clé que seuls ton serveur et ton poste peuvent calculer. Le relais ne transporte que de l'opaque. +Sur le PC à piloter, tu installes simplement AJEAN, puis tu lances : -## Rien à changer pour toi +``` +ajean remote install https://ajean.link --machine --key --code --allow shell,read,write,list +``` -Dans **Réglages → Postes distants**, générer un code affiche maintenant deux commandes : une pour l'**accès à distance** (via ajean.link) et une pour le **réseau local** (connexion directe). Tu choisis, tu copies, tu colles sur l'autre PC. +La commande s'installe en service — invisible, au démarrage, reconnexion automatique — exactement comme avant. Rien ne change au chiffrement : l'accès reste **chiffré de bout en bout**, le relais ajean.link ne voit toujours rien. -## Un client, toujours aussi léger +## Rien à changer dans ton usage -Le petit client s'appelle **ajean-remote** (quelques mégaoctets, aucune interface). Il s'installe en service — invisible, au démarrage, reconnexion automatique — et se télécharge depuis ajean.link, section « Piloter un autre PC ». +Dans **Réglages → Postes distants**, générer un code affiche la commande prête à copier — à distance (via ajean.link) ou en réseau local. Seul le nom de la commande change : `ajean remote install …` au lieu de `ajean-remote install …`. + +Les postes déjà appairés continuent de fonctionner sans réappairage. diff --git a/cmd/ajean/resource_windows_amd64.syso b/cmd/ajean/resource_windows_amd64.syso index f149d1b..8cc6175 100644 Binary files a/cmd/ajean/resource_windows_amd64.syso and b/cmd/ajean/resource_windows_amd64.syso differ diff --git a/cmd/ajean/resource_windows_arm64.syso b/cmd/ajean/resource_windows_arm64.syso index 39a6089..c8b30d3 100644 Binary files a/cmd/ajean/resource_windows_arm64.syso and b/cmd/ajean/resource_windows_arm64.syso differ diff --git a/cmd/ajean/versioninfo.json b/cmd/ajean/versioninfo.json index c0f4fc5..2864961 100644 --- a/cmd/ajean/versioninfo.json +++ b/cmd/ajean/versioninfo.json @@ -3,13 +3,13 @@ "FileVersion": { "Major": 0, "Minor": 9, - "Patch": 0, + "Patch": 1, "Build": 0 }, "ProductVersion": { "Major": 0, "Minor": 9, - "Patch": 0, + "Patch": 1, "Build": 0 }, "FileFlagsMask": "3f", @@ -25,7 +25,7 @@ "LegalCopyright": "Copyright (c) 2026 AJEAN contributors. MIT License.", "OriginalFilename": "ajean.exe", "ProductName": "AJEAN", - "ProductVersion": "0.9.0", + "ProductVersion": "0.9.1", "Comments": "https://github.com/nathaninline/ajean — projet open source (MIT)" }, "VarFileInfo": { diff --git a/cmd/ajean-remote/main.go b/internal/ajean/remote.go similarity index 53% rename from cmd/ajean-remote/main.go rename to internal/ajean/remote.go index 62fa972..ff2fe85 100644 --- a/cmd/ajean-remote/main.go +++ b/internal/ajean/remote.go @@ -1,13 +1,17 @@ -// ajean-remote — client LÉGER « poste distant ». Binaire séparé et minuscule -// (protocole partagé + WebSocket, sans UI ni moteur) : on l'installe sur un PC -// pour que l'IA d'un serveur AJEAN puisse agir dessus. +// Sous-commande « ajean remote » — client « poste distant » intégré au binaire +// principal. On l'installe sur un PC pour que l'IA d'un serveur AJEAN puisse agir +// dessus (shell/fichiers), chiffré de bout en bout via ajean.link (relais aveugle) +// ou en direct sur le réseau local. // -// ajean-remote install --code CODE [--allow shell,read,write,list] [--root DIR] -// appaire ce PC et l'installe en service (démarrage auto) -// ajean-remote connect --code CODE [...] même chose mais en avant-plan (test) -// ajean-remote run boucle client (utilisé par le service) -// ajean-remote status | uninstall | logout -package main +// ajean remote install --machine ID --key CLÉ --code CODE [--allow shell,read,write,list] [--root DIR] +// appaire ce PC et l'installe en service (démarrage auto) +// ajean remote connect ... idem mais en avant-plan (test) +// ajean remote run boucle client (utilisé par le service) +// ajean remote status | uninstall | logout +// +// Historiquement un binaire séparé « ajean-remote » ; fusionné dans ce binaire +// pour n'avoir qu'une seule application. +package ajean import ( "context" @@ -19,65 +23,64 @@ import ( "github.com/nathaninline/ajean/internal/nodeclient" ) -func main() { - args := os.Args[1:] - cmd := "" +func cmdRemote(args []string) error { + sub := "" if len(args) > 0 { - cmd = args[0] + sub = args[0] args = args[1:] } - var err error - switch cmd { + switch sub { case "install": - err = doConnect(args, true) + return remoteConnect(args, true) case "connect": - err = doConnect(args, false) + return remoteConnect(args, false) case "run": - err = doRun() + return remoteRun() case "uninstall": - err = nodeclient.Uninstall() - if err == nil { - fmt.Println("[ok] service retiré") + if err := nodeclient.Uninstall(); err != nil { + return err } + fmt.Println("[ok] service retiré") + return nil case "logout": _ = nodeclient.Uninstall() - err = os.Remove(nodeclient.ConfigPath()) - if err == nil || os.IsNotExist(err) { - err = nil - fmt.Println("[ok] configuration oubliée") + err := os.Remove(nodeclient.ConfigPath()) + if err != nil && !os.IsNotExist(err) { + return err } + fmt.Println("[ok] configuration oubliée") + return nil case "status": - err = doStatus() + return remoteStatus() default: - usage() - } - if err != nil { - fmt.Fprintln(os.Stderr, "[err]", err) - os.Exit(1) + remoteUsage() + return nil } } -func usage() { - fmt.Print(`ajean-remote — client léger « poste distant » pour AJEAN +func remoteUsage() { + fmt.Print(`ajean remote — piloter CE PC depuis l'IA d'un serveur AJEAN - ajean-remote install --code CODE [options] appaire + installe en service (démarrage auto, invisible) - ajean-remote connect --code CODE [options] idem en avant-plan (pour tester) - ajean-remote status état de la configuration - ajean-remote uninstall retire le service (garde la config) - ajean-remote logout retire le service ET oublie la clé + ajean remote install --code CODE [options] appaire + installe en service (démarrage auto, invisible) + ajean remote connect --code CODE [options] idem en avant-plan (pour tester) + ajean remote status état de la configuration + ajean remote uninstall retire le service (garde la config) + ajean remote logout retire le service ET oublie la clé Options : - --code CODE code d'appairage (généré dans l'UI AJEAN → Postes distants) - --allow LISTE capacités acceptées : shell,read,write,list (défaut : ce que le serveur autorise) - --root DIR dossier autorisé (confine lecture/écriture) - --yes auto-approuve les actions (obligatoire pour le service, sans terminal) + --machine ID machine à joindre via ajean.link (fournie par l'UI → Postes distants) + --key CLÉ clé publique de l'agent (le poste scelle vers elle) + --code CODE code d'appairage (10 min, généré dans l'UI AJEAN → Postes distants) + --allow LISTE capacités acceptées : shell,read,write,list (défaut : ce que le serveur autorise) + --root DIR dossier autorisé (confine lecture/écriture) + --yes auto-approuve les actions (obligatoire pour le service, sans terminal) `) } -// doConnect appaire au besoin, sauvegarde, puis installe (service) ou lance +// remoteConnect appaire au besoin, sauvegarde, puis installe (service) ou lance // (avant-plan) selon install. -func doConnect(args []string, install bool) error { - f := parseFlags(args) +func remoteConnect(args []string, install bool) error { + f := parseRemoteFlags(args) cfg, lerr := nodeclient.LoadConfig() needEnroll := lerr != nil || cfg.Priv == "" if f.url != "" { @@ -91,7 +94,7 @@ func doConnect(args []string, install bool) error { } if needEnroll { if cfg.ServerURL == "" { - return fmt.Errorf("url du serveur requise (ex: http://192.168.1.127:8090)") + return fmt.Errorf("url du serveur requise (ex: https://ajean.link)") } if f.code == "" { return fmt.Errorf("code d'appairage requis : --code CODE") @@ -104,17 +107,16 @@ func doConnect(args []string, install bool) error { } fmt.Printf("[ok] appairé (id %s)\n", cfg.ID) } - allow, root, yes := f.allow, f.root, f.yes - if len(allow) > 0 { - cfg.Caps = nodeclient.SanitizeCaps(allow) + if len(f.allow) > 0 { + cfg.Caps = nodeclient.SanitizeCaps(f.allow) } - if root != "" { - cfg.Root = root + if f.root != "" { + cfg.Root = f.root } if cfg.Root == "" { cfg.Root = nodeclient.DataDir() + string(os.PathSeparator) + "workspace" } - if yes || install { + if f.yes || install { cfg.AutoYes = true // un service n'a pas de terminal pour confirmer } if cfg.Name == "" { @@ -137,15 +139,15 @@ func doConnect(args []string, install bool) error { return nil } -func doRun() error { +func remoteRun() error { cfg, err := nodeclient.LoadConfig() if err != nil { - return fmt.Errorf("aucune configuration — lance d'abord « ajean-remote install … »") + return fmt.Errorf("aucune configuration — lance d'abord « ajean remote install … »") } return nodeclient.RunService(cfg) } -func doStatus() error { +func remoteStatus() error { cfg, err := nodeclient.LoadConfig() if err != nil { fmt.Println("aucun poste configuré") @@ -160,14 +162,14 @@ func doStatus() error { return nil } -type flags struct { +type remoteFlags struct { url, code, root, key, machine string allow []string yes bool } -func parseFlags(args []string) flags { - var f flags +func parseRemoteFlags(args []string) remoteFlags { + var f remoteFlags for i := 0; i < len(args); i++ { a := args[i] next := func() string { diff --git a/internal/ajean/run.go b/internal/ajean/run.go index cd295cc..0638d9a 100644 --- a/internal/ajean/run.go +++ b/internal/ajean/run.go @@ -10,7 +10,7 @@ import ( "strings" ) -const Version = "0.9.0" +const Version = "0.9.1" // Main est le vrai main() du binaire (cmd/ajean ne fait que l'appeler). func Main() { @@ -74,6 +74,8 @@ func Main() { mustExit(cmdWeb(args)) case "link": mustExit(cmdLink(args)) + case "remote": + mustExit(cmdRemote(args)) case "ui": mustExit(cmdUI(args)) case "oai": @@ -158,9 +160,12 @@ Accès distant (ajean.link) : link code code d'appairage (10 min, usage unique) pour le portail link status | logout état du jeton / l'oublier -Poste distant (piloter un autre PC depuis l'IA) : - le poste s'installe avec le binaire LÉGER séparé « ajean-remote » (cmd/ajean-remote), - pas avec ce binaire : ajean-remote install --code CODE +Poste distant (piloter CE PC depuis l'IA d'un serveur AJEAN) : + remote install --machine ID --key CLÉ --code CODE [--allow shell,read,write,list] + appaire ce PC + installe le service (démarrage auto) + remote connect … idem en avant-plan (pour tester) + remote status | uninstall | logout + (commande d'appairage prête à copier : UI web → Postes distants) Backend llama.cpp : llamacpp install clone + compile llama.cpp (CUDA/ROCm/Metal/CPU), pointe BIN dessus diff --git a/internal/ajean/ui/index.html b/internal/ajean/ui/index.html index 737b5c1..0353741 100644 --- a/internal/ajean/ui/index.html +++ b/internal/ajean/ui/index.html @@ -1303,7 +1303,7 @@ button:hover{border-color:var(--dim);color:var(--text)}
-
Postes distants?Pilote un autre PC depuis l'IA. installe le petit client ajean-remote sur le PC secondaire et lance ajean-remote install <url> --code CODE : il se connecte en sortant (aucun port à ouvrir) et expose à l'IA un jeu d'outils restreint (shell, fichiers). ⚠ tu décides des capacités et du dossier autorisé pour chaque poste ; le poste peut aussi demander confirmation localement.
+
Postes distants?Pilote un autre PC depuis l'IA. installe ajean sur le PC secondaire et lance ajean remote install <url> --code CODE : il se connecte en sortant (aucun port à ouvrir) et expose à l'IA un jeu d'outils restreint (shell, fichiers). ⚠ tu décides des capacités et du dossier autorisé pour chaque poste ; le poste peut aussi demander confirmation localement.
(aucun poste — appaire un PC pour que l'IA puisse agir dessus)
@@ -1723,7 +1723,7 @@ button:hover{border-color:var(--dim);color:var(--text)}
Code d'appairage
——
- Sur le PC à piloter, installe ajean-remote (ajean.link/download) puis lance — accès à distance, chiffré de bout en bout : + Sur le PC à piloter, installe ajean (ajean.link/download) puis lance — accès à distance, chiffré de bout en bout :

           Ou en réseau local (connexion directe) :
           

@@ -5509,10 +5509,10 @@ async function genNodeCode(){
   // Commande d'accès À DISTANCE (partout) via ajean.link : chiffrée de bout en
   // bout, le relais reste aveugle. --key = clé publique de l'agent (le poste
   // scelle vers elle), --machine = quelle machine joindre.
-  const remote = 'ajean-remote install https://ajean.link --machine '+r.machine+
+  const remote = 'ajean remote install https://ajean.link --machine '+r.machine+
     ' --key '+r.agent_pub+' --code '+r.code+' --allow '+allow+rootArg;
   // Variante RÉSEAU LOCAL (connexion directe, sans passer par le relais).
-  const lan = 'ajean-remote install '+location.origin+
+  const lan = 'ajean remote install '+location.origin+
     ' --key '+r.agent_pub+' --code '+r.code+' --allow '+allow+rootArg;
   document.getElementById('node-pair-cmd').textContent = remote;
   const lanEl = document.getElementById('node-pair-cmd-lan');
diff --git a/internal/ajean/ui/src/index.tmpl.html b/internal/ajean/ui/src/index.tmpl.html
index 1ce1bb6..95501a6 100644
--- a/internal/ajean/ui/src/index.tmpl.html
+++ b/internal/ajean/ui/src/index.tmpl.html
@@ -147,7 +147,7 @@
       
-
Postes distants?Pilote un autre PC depuis l'IA. installe le petit client ajean-remote sur le PC secondaire et lance ajean-remote install <url> --code CODE : il se connecte en sortant (aucun port à ouvrir) et expose à l'IA un jeu d'outils restreint (shell, fichiers). ⚠ tu décides des capacités et du dossier autorisé pour chaque poste ; le poste peut aussi demander confirmation localement.
+
Postes distants?Pilote un autre PC depuis l'IA. installe ajean sur le PC secondaire et lance ajean remote install <url> --code CODE : il se connecte en sortant (aucun port à ouvrir) et expose à l'IA un jeu d'outils restreint (shell, fichiers). ⚠ tu décides des capacités et du dossier autorisé pour chaque poste ; le poste peut aussi demander confirmation localement.
(aucun poste — appaire un PC pour que l'IA puisse agir dessus)
@@ -567,7 +567,7 @@
Code d'appairage
——
- Sur le PC à piloter, installe ajean-remote (ajean.link/download) puis lance — accès à distance, chiffré de bout en bout : + Sur le PC à piloter, installe ajean (ajean.link/download) puis lance — accès à distance, chiffré de bout en bout :

           Ou en réseau local (connexion directe) :
           

diff --git a/internal/ajean/ui/src/js/15-node.js b/internal/ajean/ui/src/js/15-node.js
index 40b4ecb..490df99 100644
--- a/internal/ajean/ui/src/js/15-node.js
+++ b/internal/ajean/ui/src/js/15-node.js
@@ -115,10 +115,10 @@ async function genNodeCode(){
   // Commande d'accès À DISTANCE (partout) via ajean.link : chiffrée de bout en
   // bout, le relais reste aveugle. --key = clé publique de l'agent (le poste
   // scelle vers elle), --machine = quelle machine joindre.
-  const remote = 'ajean-remote install https://ajean.link --machine '+r.machine+
+  const remote = 'ajean remote install https://ajean.link --machine '+r.machine+
     ' --key '+r.agent_pub+' --code '+r.code+' --allow '+allow+rootArg;
   // Variante RÉSEAU LOCAL (connexion directe, sans passer par le relais).
-  const lan = 'ajean-remote install '+location.origin+
+  const lan = 'ajean remote install '+location.origin+
     ' --key '+r.agent_pub+' --code '+r.code+' --allow '+allow+rootArg;
   document.getElementById('node-pair-cmd').textContent = remote;
   const lanEl = document.getElementById('node-pair-cmd-lan');
diff --git a/internal/nodeclient/install_other.go b/internal/nodeclient/install_other.go
index bea994b..92f64af 100644
--- a/internal/nodeclient/install_other.go
+++ b/internal/nodeclient/install_other.go
@@ -40,7 +40,7 @@ func Install() error {
 	case "darwin":
 		return installLaunchd(dst)
 	}
-	return fmt.Errorf("installation auto non gérée sur %s — lance « ajean-remote connect » via ton propre gestionnaire de démarrage", runtime.GOOS)
+	return fmt.Errorf("installation auto non gérée sur %s — lance « ajean remote connect » via ton propre gestionnaire de démarrage", runtime.GOOS)
 }
 
 func Uninstall() error {
@@ -58,7 +58,7 @@ func Uninstall() error {
 
 func installSystemd(exe string) error {
 	unit := "[Unit]\nDescription=AJEAN poste distant\nAfter=network-online.target\n\n" +
-		"[Service]\nExecStart=" + exe + " run\nRestart=always\nRestartSec=5\n\n" +
+		"[Service]\nExecStart=" + exe + " remote run\nRestart=always\nRestartSec=5\n\n" +
 		"[Install]\nWantedBy=default.target\n"
 	dir := filepath.Join(os.Getenv("HOME"), ".config/systemd/user")
 	if err := os.MkdirAll(dir, 0o755); err != nil {
@@ -79,7 +79,7 @@ func installLaunchd(exe string) error {
 
 
   Labellink.ajean.remote
-  ProgramArguments` + exe + `run
+  ProgramArguments` + exe + `remoterun
   RunAtLoad
   KeepAlive
 
diff --git a/internal/nodeclient/install_windows.go b/internal/nodeclient/install_windows.go
index f054761..8cffe41 100644
--- a/internal/nodeclient/install_windows.go
+++ b/internal/nodeclient/install_windows.go
@@ -73,13 +73,13 @@ func Install() error {
 	defer m.Disconnect()
 	if s, err := m.OpenService(serviceName); err == nil {
 		s.Close()
-		return fmt.Errorf("service déjà installé — « ajean-remote uninstall » d'abord")
+		return fmt.Errorf("service déjà installé — « ajean remote uninstall » d'abord")
 	}
 	s, err := m.CreateService(serviceName, dst, mgr.Config{
 		StartType:   mgr.StartAutomatic,
 		DisplayName: "AJEAN — poste distant",
 		Description: "Pont léger permettant à votre serveur IA AJEAN d'agir sur ce PC.",
-	}, "run")
+	}, "remote", "run")
 	if err != nil {
 		return fmt.Errorf("création du service: %w", err)
 	}