diff --git a/agent/cmd/behavision-setup/main.go b/agent/cmd/behavision-setup/main.go index 50fd96b..a8119f1 100644 --- a/agent/cmd/behavision-setup/main.go +++ b/agent/cmd/behavision-setup/main.go @@ -185,8 +185,19 @@ func run() error { } fmt.Println() - fmt.Println(" Done. Start Behavision from the Start menu or the desktop icon.") - fmt.Println(" It appears in the system tray; right-click there to stop it.") + // The last thing setup says is the first thing the operator does, so it + // has to describe THEIR machine. On macOS there is no Start menu and, + // deliberately, no tray at all - telling somebody to right-click a tray + // icon that does not exist is how software loses their trust on the step + // where it was otherwise finished. + if runtime.GOOS == "windows" { + fmt.Println(" Done. Start Behavision from the Start menu or the desktop icon.") + fmt.Println(" It appears in the system tray; right-click there to stop it.") + } else { + fmt.Println(" Done. Open Behavision.app - right-click it and choose Open the") + fmt.Println(" first time, because this build is not notarised.") + fmt.Println(" There is no tray on macOS: closing the window stops recognition.") + } fmt.Println() return nil } @@ -247,13 +258,53 @@ func findPython() (string, string, error) { if runtime.GOOS == "windows" { cands = append(cands, cand{"py", []string{"-3"}}) } + + // Versioned names FIRST, newest first, and this is not belt-and-braces on + // macOS - it is the only thing that works. `/usr/bin/python3` there is + // always the Command Line Tools build, 3.9 on current macOS, which is + // below the 3.10 floor. Anything newer installs as `python3.12` or into a + // directory that is not on a GUI application's PATH. Searching only + // `python3` therefore told a Mac with Python 3.12 sitting on it to go and + // install Python - measured on this machine, which has 3.12 under + // ~/.local/opt and reported "Found, but too old: python3 3.9". + versions := []string{"3.14", "3.13", "3.12", "3.11", "3.10"} + for _, v := range versions { + cands = append(cands, cand{"python" + v, nil}) + } cands = append(cands, cand{"python3", nil}, cand{"python", nil}) + // And the places a Mac puts an interpreter that LookPath will not find, + // because a double-clicked app inherits a minimal PATH rather than the + // one a shell profile builds. + if runtime.GOOS != "windows" { + home, _ := os.UserHomeDir() + for _, v := range versions { + for _, dir := range []string{ + "/opt/homebrew/bin", + "/usr/local/bin", + "/Library/Frameworks/Python.framework/Versions/" + v + "/bin", + filepath.Join(home, ".local", "opt", "python"+v, "bin"), + } { + cands = append(cands, cand{filepath.Join(dir, "python"+v), nil}) + } + } + } + var tried []string for _, c := range cands { - exe, err := exec.LookPath(c.exe) - if err != nil { - continue + exe := c.exe + if filepath.IsAbs(exe) { + // An absolute candidate is a guess about where an interpreter + // might be; most will not exist, and that is not an error. + if fi, err := os.Stat(exe); err != nil || fi.IsDir() { + continue + } + } else { + found, err := exec.LookPath(exe) + if err != nil { + continue + } + exe = found } args := append(append([]string{}, c.args...), "-c", "import sys;print('%d.%d'%sys.version_info[:2])")