From 754d12d58385e023f08a8d1dd78a83387c869b99 Mon Sep 17 00:00:00 2001 From: Ludovic Vielle Date: Sun, 19 Jul 2026 12:55:57 +0200 Subject: [PATCH] Skip tray re-register on deep-link enroll Browser enrollment succeeds once the device is ACTIVE, but the macOS URL handler failed whenever install re-bootstrapped a tray LaunchAgent the PKG had already installed. Skip registration when the plist is current, treat live bootstrap as best-effort, and exit successfully if the device is already enrolled so retries do not show "Enrollment failed". Signed-off-by: Ludovic Vielle --- cmd/probo-agent/main.go | 56 ---------------- pkg/deviceagent/tray/autostart_darwin.go | 79 +++++++++++++++++------ pkg/deviceagent/tray/autostart_windows.go | 17 ++++- 3 files changed, 75 insertions(+), 77 deletions(-) diff --git a/cmd/probo-agent/main.go b/cmd/probo-agent/main.go index 93af350fc..9a75be5b9 100644 --- a/cmd/probo-agent/main.go +++ b/cmd/probo-agent/main.go @@ -25,11 +25,9 @@ import ( "encoding/json" "errors" "fmt" - "io" "os" "os/signal" "path/filepath" - "runtime" "syscall" "time" @@ -102,8 +100,6 @@ func newRootCmd() *cobra.Command { } func newEnrollURLCmd() *cobra.Command { - var preflight bool - cmd := &cobra.Command{ Use: "enroll-url [url]", Hidden: true, @@ -116,32 +112,14 @@ func newEnrollURLCmd() *cobra.Command { dir := resolveDir(cmd) - if preflight { - enrolled, err := deviceagent.IsEnrolled(deviceagent.EnrollmentRunDir(dir)) - if err != nil { - return fmt.Errorf("cannot check enrollment state: %w", err) - } - - return writeEnrollPreflight(cmd.OutOrStdout(), serverURL, enrollmentToken, dir, enrolled) - } - already, err := reportIfAlreadyEnrolled(dir) if err != nil { return err } - if already { return nil } - if runtime.GOOS == "darwin" { - return fmt.Errorf( - "macOS browser enrollment must use the signed Probo Agent.app " + - "(probo:// deeplink); for CLI use: sudo probo-agent install " + - "--server … --enrollment-token …", - ) - } - exePath, err := os.Executable() if err != nil { return fmt.Errorf("cannot resolve current executable path: %w", err) @@ -157,42 +135,9 @@ func newEnrollURLCmd() *cobra.Command { }, } - cmd.Flags().BoolVar(&preflight, "preflight", false, "validate enrollment URL and print JSON for the macOS URL handler") - return cmd } -type enrollPreflightResponse struct { - Server string `json:"server"` - Token string `json:"token"` - AlreadyEnrolled bool `json:"alreadyEnrolled"` - ConfigDir string `json:"configDir"` -} - -func writeEnrollPreflight( - w io.Writer, - serverURL, enrollmentToken, dir string, - alreadyEnrolled bool, -) error { - payload := enrollPreflightResponse{ - Server: serverURL, - Token: enrollmentToken, - AlreadyEnrolled: alreadyEnrolled, - ConfigDir: dir, - } - - out, err := json.Marshal(payload) - if err != nil { - return fmt.Errorf("cannot encode enrollment preflight response: %w", err) - } - - if _, err := fmt.Fprintln(w, string(out)); err != nil { - return fmt.Errorf("cannot write enrollment preflight response: %w", err) - } - - return nil -} - // reportIfAlreadyEnrolled prints a success message and returns true when // the local enrollment marker is already present. Deep-link retries must // exit 0 so the macOS URL handler does not show "Enrollment failed". @@ -286,7 +231,6 @@ func newInstallCmd() *cobra.Command { if err != nil { return err } - if already { return nil } diff --git a/pkg/deviceagent/tray/autostart_darwin.go b/pkg/deviceagent/tray/autostart_darwin.go index 13e67d38f..1e87fbc44 100644 --- a/pkg/deviceagent/tray/autostart_darwin.go +++ b/pkg/deviceagent/tray/autostart_darwin.go @@ -23,6 +23,7 @@ package tray import ( + "bytes" _ "embed" "encoding/xml" "errors" @@ -73,6 +74,15 @@ func RegisterAutoStart(exePath string, runDir string) error { return fmt.Errorf("enrollment run directory is required") } + current, err := launchAgentIsCurrent(trayPlistPath, exePath, runDir) + if err != nil { + return err + } + + if current { + return nil + } + if err := writeTrayLaunchAgentPlist(exePath, runDir); err != nil { return err } @@ -82,7 +92,45 @@ func RegisterAutoStart(exePath string, runDir string) error { return nil } - return bootstrapTrayForUIDs(uids) + bootstrapTrayForUIDs(uids) + + return nil +} + +func renderLaunchAgentPlist(exePath string, runDir string) ([]byte, error) { + var buf bytes.Buffer + if err := launchAgentPlist.Execute( + &buf, + launchAgentData{ + Label: trayLabel, + ExePath: exePath, + RunDir: runDir, + }, + ); err != nil { + return nil, fmt.Errorf("cannot render LaunchAgent plist: %w", err) + } + + return buf.Bytes(), nil +} + +// launchAgentIsCurrent reports whether plistPath already contains the +// LaunchAgent definition for exePath and runDir. +func launchAgentIsCurrent(plistPath string, exePath string, runDir string) (bool, error) { + existing, err := os.ReadFile(plistPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return false, nil + } + + return false, fmt.Errorf("cannot read LaunchAgent plist: %w", err) + } + + desired, err := renderLaunchAgentPlist(exePath, runDir) + if err != nil { + return false, err + } + + return bytes.Equal(existing, desired), nil } func writeTrayLaunchAgentPlist(exePath string, runDir string) error { @@ -91,22 +139,13 @@ func writeTrayLaunchAgentPlist(exePath string, runDir string) error { return fmt.Errorf("cannot ensure launch agents directory: %w", err) } - f, err := os.OpenFile(trayPlistPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644) + desired, err := renderLaunchAgentPlist(exePath, runDir) if err != nil { - return fmt.Errorf("cannot write plist (need root?): %w", err) + return err } - defer func() { _ = f.Close() }() - - if err := launchAgentPlist.Execute( - f, - launchAgentData{ - Label: trayLabel, - ExePath: exePath, - RunDir: runDir, - }, - ); err != nil { - return fmt.Errorf("cannot render LaunchAgent plist: %w", err) + if err := os.WriteFile(trayPlistPath, desired, 0o644); err != nil { + return fmt.Errorf("cannot write plist (need root?): %w", err) } return nil @@ -123,7 +162,10 @@ func UnregisterAutoStart() error { return nil } -func bootstrapTrayForUIDs(uids []int) error { +// bootstrapTrayForUIDs best-effort loads the tray LaunchAgent into each +// GUI session. Failures are warnings only: the plist is enough for the +// next login (same policy as the macOS PKG postinstall script). +func bootstrapTrayForUIDs(uids []int) { for _, uid := range uids { target := fmt.Sprintf("gui/%d/%s", uid, trayLabel) @@ -135,16 +177,15 @@ func bootstrapTrayForUIDs(uids []int) error { fmt.Sprintf("gui/%d", uid), trayPlistPath, ).CombinedOutput(); err != nil { - return fmt.Errorf( - "cannot run launchctl bootstrap for uid %d: %w: %s", + fmt.Fprintf( + os.Stderr, + "warning: could not start tray helper for uid %d; it will start at next GUI login: %v: %s\n", uid, err, strings.TrimSpace(string(out)), ) } } - - return nil } func bootoutTrayForUIDs(uids []int) { diff --git a/pkg/deviceagent/tray/autostart_windows.go b/pkg/deviceagent/tray/autostart_windows.go index e62fa4582..06b5e46b8 100644 --- a/pkg/deviceagent/tray/autostart_windows.go +++ b/pkg/deviceagent/tray/autostart_windows.go @@ -48,14 +48,23 @@ func RegisterAutoStart(exePath string, runDir string) error { keyPath := sid + `\` + runKeyPath - key, _, err := registry.CreateKey(registry.USERS, keyPath, registry.SET_VALUE) + key, _, err := registry.CreateKey(registry.USERS, keyPath, registry.QUERY_VALUE|registry.SET_VALUE) if err != nil { return fmt.Errorf("cannot open or create Run registry key for interactive user: %w", err) } defer func() { _ = key.Close() }() - command := fmt.Sprintf(`"%s" tray --run-dir "%s"`, exePath, runDir) + command := trayRunCommand(exePath, runDir) + + existing, _, err := key.GetStringValue(runValueName) + if err == nil && existing == command { + return nil + } + if err != nil && !errors.Is(err, registry.ErrNotExist) { + return fmt.Errorf("cannot read Run registry value: %w", err) + } + if err := key.SetStringValue(runValueName, command); err != nil { return fmt.Errorf("cannot set Run registry value: %w", err) } @@ -63,6 +72,10 @@ func RegisterAutoStart(exePath string, runDir string) error { return nil } +func trayRunCommand(exePath string, runDir string) string { + return fmt.Sprintf(`"%s" tray --run-dir "%s"`, exePath, runDir) +} + func UnregisterAutoStart() error { sid, err := currentInteractiveUserSID() if err != nil {