//go:build darwin package update import ( "context" "fmt" "os" "os/exec" "path/filepath" "strconv" "strings" ) // Apply downloads the new binary and schedules replacement + relaunch after exit. func Apply(ctx context.Context, manifestURL string) (string, error) { latest, _, _, exe, tmp, err := prepareDownload(ctx, manifestURL) if err != nil { return "", err } _ = os.Chmod(tmp, 0o755) dir := filepath.Dir(exe) scriptPath := filepath.Join(dir, "navis-update.sh") pid := os.Getpid() script := "#!/bin/bash\n" + "set -e\n" + "EXE=" + shellQuote(exe) + "\n" + "NEW=" + shellQuote(tmp) + "\n" + "PID=" + strconv.Itoa(pid) + "\n" + "while kill -0 \"$PID\" 2>/dev/null; do sleep 0.4; done\n" + "sleep 0.5\n" + "mv -f \"$NEW\" \"$EXE\"\n" + "chmod +x \"$EXE\"\n" + "rm -f " + shellQuote(scriptPath) + "\n" + "exec \"$EXE\"\n" if err := os.WriteFile(scriptPath, []byte(script), 0o755); err != nil { return "", err } cmd := exec.Command("/bin/bash", scriptPath) cmd.Dir = dir if err := cmd.Start(); err != nil { return "", fmt.Errorf("start updater: %w", err) } return latest, nil } func shellQuote(s string) string { return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'" } // CleanupStaleDownloads removes leftover update temps from failed/old runs. func CleanupStaleDownloads() { exe, err := os.Executable() if err != nil { return } exe, _ = filepath.Abs(exe) _ = os.Remove(exe + ".new") _ = os.Remove(filepath.Join(filepath.Dir(exe), ".navis-update-download")) }