mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-12 01:37:06 +02:00
v0.7.3 : demander l'elevation Windows quand elle debloque la migration
Un compte non administrateur ne peut pas renommer C:\ProgramData\jean : le droit manquant porte sur C:\ProgramData. Ces postes gardaient donc l'ancien nom indefiniment. Les 0.7.1 et 0.7.2 avaient traite le bruit, pas le fond. L'installation relance desormais AJEAN en administrateur le temps de la SEULE migration (sous-commande interne, absente de l'aide), puis rend la main. Un refus de la fenetre UAC est une reponse valable : on continue sur l'ancien chemin, sans rien casser. Deux tentatives de discrimination ont echoue avant celle-ci, et le detail compte pour qui relira ce code : - Le code d'erreur ne distingue rien. Windows renvoie ERROR_ACCESS_DENIED aussi bien pour un manque de droits que pour un dossier verrouille par un programme en cours. Un test le prouve, il avait ete ecrit en croyant l'inverse. - Sonder l'ecriture dans le dossier parent ne distingue rien non plus : ProgramData autorise la creation de fichiers aux utilisateurs standards, c'est la suppression/renommage d'une entree existante qui est refusee. C'est donc le LIEU D'APPEL qui selectionne, et lui est fiable : on n'eleve que depuis `install` et depuis l'installeur de mise a jour, tous deux apres l'arret des instances. Le cas « verrouille » y est deja ecarte. Note pour les tests : ne JAMAIS appeler elevateForHomeMigration() sous `go test`. os.Executable() y designe le binaire de test, que l'elevation relance — donc toute la suite, en boucle. Constate.
This commit is contained in:
1 parent
7a141d19b5
commit
8365447de0
10 files changed
+129
-13
No files matched your search
+4
-8
@@ -1,14 +1,10 @@
|
||||
## Correction
|
||||
## Le renommage du dossier se termine tout seul sous Windows
|
||||
|
||||
**Sous Windows, « Une version plus récente d'AJEAN est déjà installée » pouvait s'afficher à chaque lancement.** Le message était exact, mais rien ne permettait d'en sortir : il revenait indéfiniment.
|
||||
Jusqu'ici, sur un ordinateur où vous n'êtes pas administrateur, `C:\ProgramData\jean` ne pouvait pas devenir `C:\ProgramData\ajean` : renommer ce dossier demande un droit qu'un compte standard n'a pas. Le renommage restait donc à moitié fait, indéfiniment.
|
||||
|
||||
Deux causes, qui se renforçaient.
|
||||
L'installation demande désormais l'autorisation Windows nécessaire, le temps du renommage, puis rend la main. La fenêtre apparaît au moment où vous installez ou mettez à jour AJEAN — jamais au démarrage ordinaire, et jamais si elle ne sert à rien : ni quand le dossier porte déjà le bon nom, ni quand vous avez choisi vous-même son emplacement.
|
||||
|
||||
La commande `jean`, conservée en alias depuis la 0.7.0, est une copie du programme posée à côté de `ajean.exe`. Windows refusant d'écraser un exécutable en cours d'exécution, cette copie n'était pas remplacée si elle tournait au moment de la mise à jour — elle restait figée sur une version périmée. Elle est désormais mise de côté puis réécrite, la même technique que celle utilisée pour le programme principal.
|
||||
|
||||
Et les raccourcis du menu Démarrer et du Bureau, créés avant la 0.7.0, visaient toujours cet alias. Ils n'étaient jamais repointés. Chaque lancement exécutait donc l'ancienne version, qui constatait à juste titre qu'une plus récente était installée — et le disait. Un raccourci existant est maintenant repointé vers le programme courant.
|
||||
|
||||
Après cette mise à jour, le message disparaît de lui-même au premier lancement.
|
||||
Refuser cette autorisation est une réponse valable. AJEAN continue alors d'utiliser son dossier actuel, exactement comme avant. Rien n'est perdu, rien n'est bloqué, et la question reviendra à la prochaine installation.
|
||||
|
||||
## Mise à jour
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -3,13 +3,13 @@
|
||||
"FileVersion": {
|
||||
"Major": 0,
|
||||
"Minor": 7,
|
||||
"Patch": 2,
|
||||
"Patch": 3,
|
||||
"Build": 0
|
||||
},
|
||||
"ProductVersion": {
|
||||
"Major": 0,
|
||||
"Minor": 7,
|
||||
"Patch": 2,
|
||||
"Patch": 3,
|
||||
"Build": 0
|
||||
},
|
||||
"FileFlagsMask": "3f",
|
||||
@@ -25,7 +25,7 @@
|
||||
"LegalCopyright": "Copyright (c) 2026 AJEAN contributors. MIT License.",
|
||||
"OriginalFilename": "ajean.exe",
|
||||
"ProductName": "AJEAN",
|
||||
"ProductVersion": "0.7.2",
|
||||
"ProductVersion": "0.7.3",
|
||||
"Comments": "https://github.com/nathaninline/ajean — projet open source (MIT)"
|
||||
},
|
||||
"VarFileInfo": {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const Version = "0.7.2"
|
||||
const Version = "0.7.3"
|
||||
|
||||
// Main est le vrai main() du binaire (cmd/ajean ne fait que l'appeler).
|
||||
func Main() {
|
||||
@@ -91,6 +91,10 @@ func Main() {
|
||||
mustExit(cmdUpdate(args))
|
||||
case "where", "paths":
|
||||
mustExit(cmdWhere(args))
|
||||
case migrateHomeArg:
|
||||
// Sous-commande interne, volontairement absente de l'aide : c'est le
|
||||
// travail delegue au process elevé par elevateForHomeMigration().
|
||||
mustExit(cmdMigrateHomeElevated())
|
||||
case "install":
|
||||
mustExit(cmdInstall(args))
|
||||
case "uninstall":
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
//go:build !windows
|
||||
|
||||
package ajean
|
||||
|
||||
// Pendant non-Windows de sys_elevate_windows.go.
|
||||
//
|
||||
// L'élévation ponctuelle n'a de sens que sous Windows : ailleurs, l'installation
|
||||
// se fait déjà sous `sudo ajean install`, qui a les droits nécessaires et migre
|
||||
// l'agencement complet (voir sys_layout.go). Il n'y a donc rien à relancer.
|
||||
|
||||
const migrateHomeArg = "migrate-home-elevated"
|
||||
|
||||
func cmdMigrateHomeElevated() error { return nil }
|
||||
|
||||
func elevateForHomeMigration() bool { return false }
|
||||
@@ -0,0 +1,79 @@
|
||||
//go:build windows
|
||||
|
||||
package ajean
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Élévation ponctuelle pour terminer la migration du dossier de données.
|
||||
//
|
||||
// Renommer %ProgramData%\jean exige d'écrire dans %ProgramData%, ce qu'un compte
|
||||
// standard ne peut pas faire. Sans élévation, ces postes gardent l'ancien nom
|
||||
// pour toujours — ce qui est sans conséquence, mais laisse le renommage à
|
||||
// moitié fait.
|
||||
//
|
||||
// La fenêtre UAC n'apparaît QUE si elle sert à quelque chose :
|
||||
//
|
||||
// - migration déjà faite, inutile, ou chemin imposé par l'utilisateur → rien ;
|
||||
// - migration en attente, dans un parcours d'installation → on demande.
|
||||
//
|
||||
// On ne cherche PAS à deviner la cause depuis le code d'erreur : Windows renvoie
|
||||
// ERROR_ACCESS_DENIED aussi bien pour un manque de droits que pour un dossier
|
||||
// verrouillé par un programme en cours. Impossible de les séparer — vérifié.
|
||||
//
|
||||
// C'est donc le LIEU D'APPEL qui fait la sélection, et lui est fiable : on
|
||||
// n'élève que depuis `install` et depuis l'installeur de mise à jour, tous deux
|
||||
// après l'arrêt des instances en cours. Le cas « verrouillé » y est déjà écarté ;
|
||||
// ce qui reste est un problème de droits, que l'élévation résout.
|
||||
|
||||
// migrateHomeArg est la sous-commande interne exécutée par le process élevé.
|
||||
// Absente de l'aide : ce n'est pas une commande destinée aux utilisateurs, mais
|
||||
// le travail précis qu'on délègue à un process ayant les droits.
|
||||
const migrateHomeArg = "migrate-home-elevated"
|
||||
|
||||
// cmdMigrateHomeElevated est le point d'entrée du process élevé : il migre, dit
|
||||
// ce qu'il a fait, et rend la main. Rien d'autre — moins il en fait sous des
|
||||
// droits administrateur, mieux c'est.
|
||||
func cmdMigrateHomeElevated() error {
|
||||
if retryHomeMigration() {
|
||||
fmt.Printf("%s dossier de données migré vers %s\n", green("✓"), AjeanHome())
|
||||
return nil
|
||||
}
|
||||
if migrationDeferred != "" {
|
||||
return fmt.Errorf("migration toujours impossible : %s", migrationDeferred)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// elevateForHomeMigration relance AJEAN en administrateur le temps de la seule
|
||||
// migration, puis revérifie. Renvoie true si le dossier a effectivement changé.
|
||||
//
|
||||
// Silencieux et sans effet quand l'élévation ne servirait à rien, y compris si
|
||||
// l'utilisateur refuse la fenêtre UAC : son refus est une réponse valable, la
|
||||
// machine continue de fonctionner sur l'ancien chemin.
|
||||
func elevateForHomeMigration() bool {
|
||||
if migrationDeferred == "" {
|
||||
return false
|
||||
}
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// Start-Process -Verb RunAs déclenche l'invite UAC ; -Wait pour que la suite
|
||||
// de l'installation reparte sur le bon dossier, pas sur l'ancien.
|
||||
ps := fmt.Sprintf(
|
||||
`try { Start-Process -FilePath %s -ArgumentList '%s' -Verb RunAs -Wait -WindowStyle Hidden; 'ok' } catch { 'refus' }`,
|
||||
psQuote(exe), migrateHomeArg)
|
||||
out, err := hideCmd(exec.Command("powershell", "-NoProfile", "-Command", ps)).CombinedOutput()
|
||||
if err != nil || !strings.Contains(string(out), "ok") {
|
||||
return false // refus de l'utilisateur, ou élévation indisponible
|
||||
}
|
||||
// Le process élevé a fait le travail dans SON espace mémoire : on relit le
|
||||
// disque pour savoir ce qu'il en est réellement ici.
|
||||
migrationDeferred = ""
|
||||
return retryHomeMigration()
|
||||
}
|
||||
@@ -195,6 +195,11 @@ func runAsInstaller(target string) bool {
|
||||
return launch(target) // l'instance en cours reprend la main (port occupé)
|
||||
}
|
||||
stopProcesses(running)
|
||||
// Les instances sont arrêtées : c'est le moment où le renommage peut
|
||||
// aboutir. S'il bute encore, et seulement sur un refus de droits, on
|
||||
// demande l'élévation — l'utilisateur est déjà dans un parcours
|
||||
// d'installation, une fenêtre UAC n'y est pas une surprise.
|
||||
elevateForHomeMigration()
|
||||
target = migrateThenResolveTarget(target)
|
||||
if err := replaceInstalled(target); err != nil {
|
||||
messageBox("La mise à jour a échoué :\n\n"+err.Error()+"\n\nAJEAN va redémarrer dans sa version actuelle.",
|
||||
@@ -206,6 +211,7 @@ func runAsInstaller(target string) bool {
|
||||
case cmp > 0:
|
||||
// Mise à jour, application arrêtée : rien à décider, on remplace et on
|
||||
// démarre. C'est le cas courant et il doit rester muet.
|
||||
elevateForHomeMigration()
|
||||
target = migrateThenResolveTarget(target)
|
||||
_ = replaceInstalled(target)
|
||||
ensureShortcuts(target)
|
||||
|
||||
@@ -42,7 +42,10 @@ func cmdInstall(args []string) error {
|
||||
// enfin aboutir sur un poste où l'utilisateur courant n'a pas les droits sur
|
||||
// %ProgramData%. On la retente donc AVANT de résoudre le dossier : l'inverse
|
||||
// laisserait la variable pointer sur l'ancien chemin et installerait à côté.
|
||||
if retryHomeMigration() {
|
||||
// elevateForHomeMigration ne demande l'élévation que si elle peut débloquer
|
||||
// la situation : jamais quand il n'y a rien à migrer, ni quand l'obstacle
|
||||
// est un fichier verrouillé que des droits administrateur ne libéreraient pas.
|
||||
if retryHomeMigration() || elevateForHomeMigration() {
|
||||
fmt.Printf("%s dossier de données migré vers %s\n", green("✓"), AjeanHome())
|
||||
} else if notice := homeMigrationNotice(); notice != "" {
|
||||
fmt.Printf("%s %s\n", yellow("[info]"), notice)
|
||||
|
||||
@@ -271,3 +271,16 @@ func TestNoNoticeWhenMigrationSucceeds(t *testing.T) {
|
||||
t.Fatalf("message inattendu apres une migration reussie:\n%s", n)
|
||||
}
|
||||
}
|
||||
|
||||
// Migration reussie : aucun residu, donc aucune demande d'elevation nulle part.
|
||||
func TestAucunResiduApresMigrationReussie(t *testing.T) {
|
||||
migrationDeferred = ""
|
||||
t.Cleanup(func() { migrationDeferred = "" })
|
||||
root := t.TempDir()
|
||||
legacy := filepath.Join(root, "jean")
|
||||
mkHome(t, legacy)
|
||||
migrateHome(filepath.Join(root, "ajean"), legacy)
|
||||
if migrationDeferred != "" {
|
||||
t.Errorf("etat inattendu apres une migration reussie: %q", migrationDeferred)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user