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 <ludovic@probo.com>
This commit is contained in:
@@ -25,11 +25,9 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -102,8 +100,6 @@ func newRootCmd() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newEnrollURLCmd() *cobra.Command {
|
func newEnrollURLCmd() *cobra.Command {
|
||||||
var preflight bool
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "enroll-url [url]",
|
Use: "enroll-url [url]",
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
@@ -116,32 +112,14 @@ func newEnrollURLCmd() *cobra.Command {
|
|||||||
|
|
||||||
dir := resolveDir(cmd)
|
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)
|
already, err := reportIfAlreadyEnrolled(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if already {
|
if already {
|
||||||
return nil
|
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()
|
exePath, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot resolve current executable path: %w", err)
|
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
|
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
|
// reportIfAlreadyEnrolled prints a success message and returns true when
|
||||||
// the local enrollment marker is already present. Deep-link retries must
|
// the local enrollment marker is already present. Deep-link retries must
|
||||||
// exit 0 so the macOS URL handler does not show "Enrollment failed".
|
// exit 0 so the macOS URL handler does not show "Enrollment failed".
|
||||||
@@ -286,7 +231,6 @@ func newInstallCmd() *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if already {
|
if already {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
package tray
|
package tray
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -73,6 +74,15 @@ func RegisterAutoStart(exePath string, runDir string) error {
|
|||||||
return fmt.Errorf("enrollment run directory is required")
|
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 {
|
if err := writeTrayLaunchAgentPlist(exePath, runDir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -82,7 +92,45 @@ func RegisterAutoStart(exePath string, runDir string) error {
|
|||||||
return nil
|
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 {
|
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)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot write plist (need root?): %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = f.Close() }()
|
if err := os.WriteFile(trayPlistPath, desired, 0o644); err != nil {
|
||||||
|
return fmt.Errorf("cannot write plist (need root?): %w", err)
|
||||||
if err := launchAgentPlist.Execute(
|
|
||||||
f,
|
|
||||||
launchAgentData{
|
|
||||||
Label: trayLabel,
|
|
||||||
ExePath: exePath,
|
|
||||||
RunDir: runDir,
|
|
||||||
},
|
|
||||||
); err != nil {
|
|
||||||
return fmt.Errorf("cannot render LaunchAgent plist: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -123,7 +162,10 @@ func UnregisterAutoStart() error {
|
|||||||
return nil
|
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 {
|
for _, uid := range uids {
|
||||||
target := fmt.Sprintf("gui/%d/%s", uid, trayLabel)
|
target := fmt.Sprintf("gui/%d/%s", uid, trayLabel)
|
||||||
|
|
||||||
@@ -135,16 +177,15 @@ func bootstrapTrayForUIDs(uids []int) error {
|
|||||||
fmt.Sprintf("gui/%d", uid),
|
fmt.Sprintf("gui/%d", uid),
|
||||||
trayPlistPath,
|
trayPlistPath,
|
||||||
).CombinedOutput(); err != nil {
|
).CombinedOutput(); err != nil {
|
||||||
return fmt.Errorf(
|
fmt.Fprintf(
|
||||||
"cannot run launchctl bootstrap for uid %d: %w: %s",
|
os.Stderr,
|
||||||
|
"warning: could not start tray helper for uid %d; it will start at next GUI login: %v: %s\n",
|
||||||
uid,
|
uid,
|
||||||
err,
|
err,
|
||||||
strings.TrimSpace(string(out)),
|
strings.TrimSpace(string(out)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func bootoutTrayForUIDs(uids []int) {
|
func bootoutTrayForUIDs(uids []int) {
|
||||||
|
|||||||
@@ -48,14 +48,23 @@ func RegisterAutoStart(exePath string, runDir string) error {
|
|||||||
|
|
||||||
keyPath := sid + `\` + runKeyPath
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot open or create Run registry key for interactive user: %w", err)
|
return fmt.Errorf("cannot open or create Run registry key for interactive user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = key.Close() }()
|
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 {
|
if err := key.SetStringValue(runValueName, command); err != nil {
|
||||||
return fmt.Errorf("cannot set Run registry value: %w", err)
|
return fmt.Errorf("cannot set Run registry value: %w", err)
|
||||||
}
|
}
|
||||||
@@ -63,6 +72,10 @@ func RegisterAutoStart(exePath string, runDir string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func trayRunCommand(exePath string, runDir string) string {
|
||||||
|
return fmt.Sprintf(`"%s" tray --run-dir "%s"`, exePath, runDir)
|
||||||
|
}
|
||||||
|
|
||||||
func UnregisterAutoStart() error {
|
func UnregisterAutoStart() error {
|
||||||
sid, err := currentInteractiveUserSID()
|
sid, err := currentInteractiveUserSID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user