From 21797414be2efd041c018185308e9b920e150165 Mon Sep 17 00:00:00 2001 From: Killian - Aidalinfo Date: Thu, 7 Aug 2025 14:56:23 +0000 Subject: [PATCH] feat: Automated task completion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Implémentation terminée ✅ J'ai implémenté la solution pour demander le mot de passe sudo dans le frontend au lieu du terminal. Voici ce qui a été fait : ### 1. **Backend (`backend/update.go`)** : - Ajout d'une erreur spécifique `ErrSudoRequired` pour détecter quand sudo est nécessaire - Modification de `PerformUpdate()` pour retourner cette erreur au lieu d'exécuter sudo directement - Création de `PerformUpdateWithPassword()` qui utilise `sudo -S` pour passer le mot de passe de manière sécurisée ### 2. **App Bridge (`app.go`)** : - Modification de `PerformUpdate()` pour émettre un événement `update:sudo-required` quand sudo est requis - Ajout de `PerformUpdateWithSudo()` pour gérer la mise à jour avec le mot de passe ### 3. **Frontend - Nouveau composant (`SudoPasswordDialog.vue`)** : - Dialog modal avec champ de saisie sécurisé pour le mot de passe - Gestion des erreurs (mot de passe incorrect) - Effacement automatique du mot de passe après utilisation ### 4. **Frontend - Mise à jour (`UpdateDialog.vue`)** : - Intégration du nouveau dialog de mot de passe - Écoute de l'événement `update:sudo-required` - Gestion du flow complet avec réessai après saisie du mot de passe ### Fonctionnement : 1. L'utilisateur clique sur "Mettre à jour" 2. Le téléchargement se fait normalement 3. Si les permissions sont insuffisantes, un dialog s'ouvre pour demander le mot de passe 4. Le mot de passe est envoyé au backend de manière sécurisée 5. La mise à jour s'effectue avec sudo 6. L'application redémarre automatiquement La solution est sécurisée car : - Le mot de passe n'est jamais stocké - Il est transmis via stdin à sudo (méthode standard) - Le mot de passe est effacé de la mémoire après utilisation - L'utilisateur peut annuler à tout moment 🤖 Generated with open-rcode automation --- app.go | 16 +++ backend/update.go | 64 +++++++++-- .../src/components/SudoPasswordDialog.vue | 106 ++++++++++++++++++ frontend/src/components/UpdateDialog.vue | 78 ++++++++++--- 4 files changed, 243 insertions(+), 21 deletions(-) create mode 100644 frontend/src/components/SudoPasswordDialog.vue diff --git a/app.go b/app.go index 8c5f98d..72d69eb 100644 --- a/app.go +++ b/app.go @@ -114,6 +114,22 @@ func (a *App) PerformUpdate(downloadURL string) error { } err = backend.PerformUpdate(tmpFile) + if err != nil { + // Si sudo est requis, émettre un événement spécifique + if err == backend.ErrSudoRequired { + runtime.EventsEmit(a.ctx, "update:sudo-required", tmpFile) + return err + } + return err + } + + runtime.EventsEmit(a.ctx, "update:complete") + return nil +} + +// PerformUpdateWithSudo performs the update with sudo password +func (a *App) PerformUpdateWithSudo(tmpFilePath string, password string) error { + err := backend.PerformUpdateWithPassword(tmpFilePath, password) if err != nil { return err } diff --git a/backend/update.go b/backend/update.go index 27de18f..44f9c96 100644 --- a/backend/update.go +++ b/backend/update.go @@ -2,6 +2,7 @@ package backend import ( "encoding/json" + "errors" "fmt" "io" "net/http" @@ -25,6 +26,9 @@ type UpdateInfo struct { DownloadURL string `json:"downloadUrl"` } +// ErrSudoRequired is returned when sudo privileges are required +var ErrSudoRequired = errors.New("sudo_required") + func GetCurrentVersion() string { return CURRENT_VERSION } @@ -167,19 +171,22 @@ func PerformUpdate(tmpFilePath string) error { backupPath := currentExe + ".backup" err = os.Rename(currentExe, backupPath) if err != nil { - cmd := exec.Command("sudo", "mv", currentExe, backupPath) - if err := cmd.Run(); err != nil { - return fmt.Errorf("erreur lors de la sauvegarde de l'ancien binaire: %v", err) + // Si on ne peut pas renommer, on a besoin de sudo + if os.IsPermission(err) { + return ErrSudoRequired } + return fmt.Errorf("erreur lors de la sauvegarde de l'ancien binaire: %v", err) } err = os.Rename(tmpFilePath, currentExe) if err != nil { - cmd := exec.Command("sudo", "mv", tmpFilePath, currentExe) - if err := cmd.Run(); err != nil { - os.Rename(backupPath, currentExe) - return fmt.Errorf("erreur lors du remplacement du binaire: %v", err) + // Restaurer l'ancien binaire + os.Rename(backupPath, currentExe) + // Si on ne peut pas renommer, on a besoin de sudo + if os.IsPermission(err) { + return ErrSudoRequired } + return fmt.Errorf("erreur lors du remplacement du binaire: %v", err) } os.Remove(backupPath) @@ -187,6 +194,49 @@ func PerformUpdate(tmpFilePath string) error { return nil } +// PerformUpdateWithPassword performs the update using sudo with the provided password +func PerformUpdateWithPassword(tmpFilePath string, password string) error { + currentExe, err := os.Executable() + if err != nil { + return fmt.Errorf("erreur lors de la récupération du chemin de l'exécutable: %v", err) + } + + currentExe, err = filepath.EvalSymlinks(currentExe) + if err != nil { + return fmt.Errorf("erreur lors de la résolution du symlink: %v", err) + } + + backupPath := currentExe + ".backup" + + // Créer un script pour effectuer la mise à jour + script := fmt.Sprintf(`#!/bin/bash +mv "%s" "%s" || exit 1 +mv "%s" "%s" || { mv "%s" "%s"; exit 1; } +rm -f "%s" +`, + currentExe, backupPath, + tmpFilePath, currentExe, + backupPath, currentExe, + backupPath) + + // Utiliser sudo avec le mot de passe fourni + cmd := exec.Command("sudo", "-S", "bash", "-c", script) + cmd.Stdin = strings.NewReader(password + "\n") + + output, err := cmd.CombinedOutput() + if err != nil { + // Vérifier si c'est une erreur d'authentification + if strings.Contains(string(output), "incorrect password") || + strings.Contains(string(output), "Désolé, essayez de nouveau") || + strings.Contains(string(output), "Sorry, try again") { + return fmt.Errorf("mot de passe incorrect") + } + return fmt.Errorf("erreur lors de la mise à jour: %v - %s", err, string(output)) + } + + return nil +} + func GetLatestReleaseInfo() (map[string]interface{}, error) { url := fmt.Sprintf("https://api.github.com/repos/aidalinfo/aidalinfo-devcli/releases/latest") diff --git a/frontend/src/components/SudoPasswordDialog.vue b/frontend/src/components/SudoPasswordDialog.vue new file mode 100644 index 0000000..86561fd --- /dev/null +++ b/frontend/src/components/SudoPasswordDialog.vue @@ -0,0 +1,106 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/UpdateDialog.vue b/frontend/src/components/UpdateDialog.vue index a431826..11c7d8c 100644 --- a/frontend/src/components/UpdateDialog.vue +++ b/frontend/src/components/UpdateDialog.vue @@ -50,10 +50,18 @@ + + + \ No newline at end of file