From b00f95b7fb07ce13f9cf6188f5ff632601c48ec6 Mon Sep 17 00:00:00 2001 From: UnbalancedCat Date: Tue, 24 Mar 2026 23:02:52 +0800 Subject: [PATCH] fix: uninstall now properly cleans files and PATH, stops on permission error, separate config/log cleanup prompts --- cmd/smart-shutdown/main.go | 42 +++++++++++++++++++++++--------------- pkg/config/interactive.go | 36 ++++++++++++++++++++++---------- 2 files changed, 51 insertions(+), 27 deletions(-) diff --git a/cmd/smart-shutdown/main.go b/cmd/smart-shutdown/main.go index 2ed3e6b..b90872a 100644 --- a/cmd/smart-shutdown/main.go +++ b/cmd/smart-shutdown/main.go @@ -112,30 +112,39 @@ func main() { Short: "废除并移除在册的后台服务、扫除环境关联残留", Run: func(cmd *cobra.Command, args []string) { service.Control(svc, "stop") - handleServiceControl(svc, "uninstall") + if !handleServiceControl(svc, "uninstall") { + return + } targetDir, targetExe := getTargetSystemPath() currentExe, _ := os.Executable() + isSelfExe := strings.EqualFold(filepath.Clean(currentExe), filepath.Clean(targetExe)) - if !strings.EqualFold(filepath.Clean(currentExe), filepath.Clean(targetExe)) { - if _, err := os.Stat(targetExe); err == nil { - logger.Debug("查明环境曾记录过全局部署逻辑,现已启动强力移除可执行源地址流: %s", targetExe) + // Clean up PATH (Windows) + if runtime.GOOS == "windows" { + envClearCmd := fmt.Sprintf(`$p=[Environment]::GetEnvironmentVariable("Path","Machine");$np=($p -split ';' | Where-Object {$_ -ne "%s" -and $_ -ne ""}) -join ';';[Environment]::SetEnvironmentVariable("Path",$np,"Machine")`, targetDir) + exec.Command("powershell", "-Command", envClearCmd).Run() + logger.Debug("环境变量系统清理:Windows Global Path 中的指向挂载项业已剥除卸载完成。") + } + + // Delete executable + if _, err := os.Stat(targetExe); err == nil { + if isSelfExe && runtime.GOOS == "windows" { + // Windows: cannot delete a running exe, use delayed self-deletion + delCmd := fmt.Sprintf(`ping 127.0.0.1 -n 2 > nul & del "%s" & rmdir "%s"`, targetExe, targetDir) + exec.Command("cmd", "/C", "start", "/min", "cmd", "/C", delCmd).Start() + logger.Debug("已调度延迟自删除任务: %s", targetExe) + } else { os.Remove(targetExe) - if runtime.GOOS == "windows" { os.Remove(targetDir) - envClearCmd := fmt.Sprintf(`$p=[Environment]::GetEnvironmentVariable("Path","Machine");$np=($p -split ';' | Where-Object {$_ -ne "%s" -and $_ -ne ""}) -join ';';[Environment]::SetEnvironmentVariable("Path",$np,"Machine")`, targetDir) - exec.Command("powershell", "-Command", envClearCmd).Run() - logger.Debug("环境变量系统清理:Windows Global Path 中的指向挂载项业已剥除卸载完成。") } + logger.Debug("已移除可执行文件: %s", targetExe) } } - if config.ConfirmConfigCleanup() { - configDir := config.GetConfigDir() - os.RemoveAll(configDir) - fmt.Println("配置文件与日志已清除。") - } + // Prompt for config and log cleanup + config.ConfirmAndCleanup() }, }, { @@ -287,18 +296,19 @@ func main() { } } -func handleServiceControl(s service.Service, action string) { +func handleServiceControl(s service.Service, action string) bool { err := service.Control(s, action) if err != nil { errStr := err.Error() if strings.Contains(errStr, "Access is denied") || strings.Contains(errStr, "permission denied") || strings.Contains(errStr, "拒绝访问") { logger.Fail("管控流程 [%s] 触发越级防卫!无核准身份特设记录。请开具含有全 Root 及高配权限窗格承接口径。", action) - return + return false } logger.Crit("后台执行流程组块阻断报错 [%s] : %v", action, err) - return + return false } logger.Succ("指令动作 [%s] 解析下发执行完毕,无阻断警告。", action) + return true } func printLastLogLines(n int) { diff --git a/pkg/config/interactive.go b/pkg/config/interactive.go index 1c5e713..075f9f3 100644 --- a/pkg/config/interactive.go +++ b/pkg/config/interactive.go @@ -63,25 +63,39 @@ func InteractiveSetup() (*Config, error) { return cfg, nil } -// ConfirmConfigCleanup prompts the user to confirm deletion of config and log files. -// Returns true if the user confirms. Non-terminal stdin defaults to no. -func ConfirmConfigCleanup() bool { +// ConfirmAndCleanup prompts the user to confirm deletion of config and log files separately. +// Non-terminal stdin skips all prompts. +func ConfirmAndCleanup() { if !isTerminal() { - return false + return } + scanner := bufio.NewScanner(os.Stdin) configDir := GetConfigDir() configPath := filepath.Join(configDir, "config.json") - if _, err := os.Stat(configPath); err != nil { - return false + logDir := GetLogDir() + + if _, err := os.Stat(configPath); err == nil { + fmt.Printf("检测到配置文件: %s\n是否清除配置文件?[y/N]: ", configPath) + if scanner.Scan() && strings.EqualFold(strings.TrimSpace(scanner.Text()), "y") { + os.Remove(configPath) + fmt.Println("配置文件已清除。") + } } - fmt.Printf("检测到配置文件: %s\n是否一并清除配置文件与日志?[y/N]: ", configDir) - scanner := bufio.NewScanner(os.Stdin) - if scanner.Scan() { - return strings.EqualFold(strings.TrimSpace(scanner.Text()), "y") + if _, err := os.Stat(logDir); err == nil { + fmt.Printf("检测到日志目录: %s\n是否清除日志文件?[y/N]: ", logDir) + if scanner.Scan() && strings.EqualFold(strings.TrimSpace(scanner.Text()), "y") { + os.RemoveAll(logDir) + fmt.Println("日志文件已清除。") + } + } + + // If configDir is now empty, remove it too + entries, err := os.ReadDir(configDir) + if err == nil && len(entries) == 0 { + os.Remove(configDir) } - return false } func promptString(scanner *bufio.Scanner, label, defaultVal string, validate func(string) error) string {