mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-20 11:58:39 +00:00
clientupdate: change Mac App Store support (#9891)
In the sandboxed app from the app store, we cannot check `/Library/Preferences/com.apple.commerce.plist` or run `softwareupdate`. We can at most print a helpful message and open the app store page. Also, reenable macsys update function to mark it as supporting c2n updates. macsys support in `tailscale update` was fixed. Updates #755 Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
This commit is contained in:
parent
eced054796
commit
70f9c8a6ed
@ -73,9 +73,6 @@ type Arguments struct {
|
|||||||
//
|
//
|
||||||
// Leaving this empty is the same as using CurrentTrack.
|
// Leaving this empty is the same as using CurrentTrack.
|
||||||
Version string
|
Version string
|
||||||
// AppStore forces a local app store check, even if the current binary was
|
|
||||||
// not installed via an app store. TODO(cpalmer): Remove this.
|
|
||||||
AppStore bool
|
|
||||||
// Logf is a logger for update progress messages.
|
// Logf is a logger for update progress messages.
|
||||||
Logf logger.Logf
|
Logf logger.Logf
|
||||||
// Stdout and Stderr should be used for output instead of os.Stdout and
|
// Stdout and Stderr should be used for output instead of os.Stdout and
|
||||||
@ -182,14 +179,12 @@ func (up *Updater) getUpdateFunction() updateFunction {
|
|||||||
}
|
}
|
||||||
case "darwin":
|
case "darwin":
|
||||||
switch {
|
switch {
|
||||||
case !up.Arguments.AppStore && !version.IsSandboxedMacOS():
|
case version.IsMacAppStore():
|
||||||
return nil
|
|
||||||
case !up.Arguments.AppStore && strings.HasSuffix(os.Getenv("HOME"), "/io.tailscale.ipn.macsys/Data"):
|
|
||||||
// TODO(noncombatant): return up.updateMacSys when we figure out why
|
|
||||||
// Sparkle update doesn't work when running "tailscale update".
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return up.updateMacAppStore
|
return up.updateMacAppStore
|
||||||
|
case version.IsMacSysExt():
|
||||||
|
return up.updateMacSys
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
case "freebsd":
|
case "freebsd":
|
||||||
return up.updateFreeBSD
|
return up.updateFreeBSD
|
||||||
@ -625,55 +620,17 @@ func (up *Updater) updateMacSys() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (up *Updater) updateMacAppStore() error {
|
func (up *Updater) updateMacAppStore() error {
|
||||||
out, err := exec.Command("defaults", "read", "/Library/Preferences/com.apple.commerce.plist", "AutoUpdate").CombinedOutput()
|
// We can't trigger the update via App Store from the sandboxed app. At
|
||||||
|
// most, we can open the App Store page for them.
|
||||||
|
up.Logf("Please use the App Store to update Tailscale.\nConsider enabling Automatic Updates in the App Store Settings, if you haven't already.\nOpening the Tailscale app page...")
|
||||||
|
|
||||||
|
out, err := exec.Command("open", "https://apps.apple.com/us/app/tailscale/id1475387142").CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't check App Store auto-update setting: %w, output: %q", err, string(out))
|
return fmt.Errorf("can't open the Tailscale page in App Store: %w, output: %q", err, string(out))
|
||||||
}
|
|
||||||
const on = "1\n"
|
|
||||||
if string(out) != on {
|
|
||||||
up.Logf("NOTE: Automatic updating for App Store apps is turned off. You can change this setting in System Settings (search for ‘update’).")
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err = exec.Command("softwareupdate", "--list").CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't check App Store for available updates: %w, output: %q", err, string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
newTailscale := parseSoftwareupdateList(out)
|
|
||||||
if newTailscale == "" {
|
|
||||||
up.Logf("no Tailscale update available")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newTailscaleVer := strings.TrimPrefix(newTailscale, "Tailscale-")
|
|
||||||
if !up.confirm(newTailscaleVer) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command("sudo", "softwareupdate", "--install", newTailscale)
|
|
||||||
cmd.Stdout = up.Stdout
|
|
||||||
cmd.Stderr = up.Stderr
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("can't install App Store update for Tailscale: %w", err)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var macOSAppStoreListPattern = regexp.MustCompile(`(?m)^\s+\*\s+Label:\s*(Tailscale-\d[\d\.]+)`)
|
|
||||||
|
|
||||||
// parseSoftwareupdateList searches the output of `softwareupdate --list` on
|
|
||||||
// Darwin and returns the matching Tailscale package label. If there is none,
|
|
||||||
// returns the empty string.
|
|
||||||
//
|
|
||||||
// See TestParseSoftwareupdateList for example inputs.
|
|
||||||
func parseSoftwareupdateList(stdout []byte) string {
|
|
||||||
matches := macOSAppStoreListPattern.FindSubmatch(stdout)
|
|
||||||
if len(matches) < 2 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return string(matches[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
// winMSIEnv is the environment variable that, if set, is the MSI file for the
|
// winMSIEnv is the environment variable that, if set, is the MSI file for the
|
||||||
// update command to install. It's passed like this so we can stop the
|
// update command to install. It's passed like this so we can stop the
|
||||||
// tailscale.exe process from running before the msiexec process runs and tries
|
// tailscale.exe process from running before the msiexec process runs and tries
|
||||||
|
@ -84,84 +84,6 @@ func TestUpdateDebianAptSourcesListBytes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSoftwareupdateList(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input []byte
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "update-at-end-of-list",
|
|
||||||
input: []byte(`
|
|
||||||
Software Update Tool
|
|
||||||
|
|
||||||
Finding available software
|
|
||||||
Software Update found the following new or updated software:
|
|
||||||
* Label: MacBookAirEFIUpdate2.4-2.4
|
|
||||||
Title: MacBook Air EFI Firmware Update, Version: 2.4, Size: 3817K, Recommended: YES, Action: restart,
|
|
||||||
* Label: ProAppsQTCodecs-1.0
|
|
||||||
Title: ProApps QuickTime codecs, Version: 1.0, Size: 968K, Recommended: YES,
|
|
||||||
* Label: Tailscale-1.23.4
|
|
||||||
Title: The Tailscale VPN, Version: 1.23.4, Size: 1023K, Recommended: YES,
|
|
||||||
`),
|
|
||||||
want: "Tailscale-1.23.4",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "update-in-middle-of-list",
|
|
||||||
input: []byte(`
|
|
||||||
Software Update Tool
|
|
||||||
|
|
||||||
Finding available software
|
|
||||||
Software Update found the following new or updated software:
|
|
||||||
* Label: MacBookAirEFIUpdate2.4-2.4
|
|
||||||
Title: MacBook Air EFI Firmware Update, Version: 2.4, Size: 3817K, Recommended: YES, Action: restart,
|
|
||||||
* Label: Tailscale-1.23.5000
|
|
||||||
Title: The Tailscale VPN, Version: 1.23.4, Size: 1023K, Recommended: YES,
|
|
||||||
* Label: ProAppsQTCodecs-1.0
|
|
||||||
Title: ProApps QuickTime codecs, Version: 1.0, Size: 968K, Recommended: YES,
|
|
||||||
`),
|
|
||||||
want: "Tailscale-1.23.5000",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "update-not-in-list",
|
|
||||||
input: []byte(`
|
|
||||||
Software Update Tool
|
|
||||||
|
|
||||||
Finding available software
|
|
||||||
Software Update found the following new or updated software:
|
|
||||||
* Label: MacBookAirEFIUpdate2.4-2.4
|
|
||||||
Title: MacBook Air EFI Firmware Update, Version: 2.4, Size: 3817K, Recommended: YES, Action: restart,
|
|
||||||
* Label: ProAppsQTCodecs-1.0
|
|
||||||
Title: ProApps QuickTime codecs, Version: 1.0, Size: 968K, Recommended: YES,
|
|
||||||
`),
|
|
||||||
want: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "decoy-in-list",
|
|
||||||
input: []byte(`
|
|
||||||
Software Update Tool
|
|
||||||
|
|
||||||
Finding available software
|
|
||||||
Software Update found the following new or updated software:
|
|
||||||
* Label: MacBookAirEFIUpdate2.4-2.4
|
|
||||||
Title: MacBook Air EFI Firmware Update, Version: 2.4, Size: 3817K, Recommended: YES, Action: restart,
|
|
||||||
* Label: Malware-1.0
|
|
||||||
Title: * Label: Tailscale-0.99.0, Version: 1.0, Size: 968K, Recommended: NOT REALLY TBH,
|
|
||||||
`),
|
|
||||||
want: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
got := parseSoftwareupdateList(test.input)
|
|
||||||
if test.want != got {
|
|
||||||
t.Fatalf("got %q, want %q", got, test.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateYUMRepoTrack(t *testing.T) {
|
func TestUpdateYUMRepoTrack(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
desc string
|
desc string
|
||||||
|
@ -26,7 +26,6 @@ var updateCmd = &ffcli.Command{
|
|||||||
fs := newFlagSet("update")
|
fs := newFlagSet("update")
|
||||||
fs.BoolVar(&updateArgs.yes, "yes", false, "update without interactive prompts")
|
fs.BoolVar(&updateArgs.yes, "yes", false, "update without interactive prompts")
|
||||||
fs.BoolVar(&updateArgs.dryRun, "dry-run", false, "print what update would do without doing it, or prompts")
|
fs.BoolVar(&updateArgs.dryRun, "dry-run", false, "print what update would do without doing it, or prompts")
|
||||||
fs.BoolVar(&updateArgs.appStore, "app-store", false, "HIDDEN: check the App Store for updates, even if this is not an App Store install (for testing only)")
|
|
||||||
// These flags are not supported on several systems that only provide
|
// These flags are not supported on several systems that only provide
|
||||||
// the latest version of Tailscale:
|
// the latest version of Tailscale:
|
||||||
//
|
//
|
||||||
@ -42,11 +41,10 @@ var updateCmd = &ffcli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var updateArgs struct {
|
var updateArgs struct {
|
||||||
yes bool
|
yes bool
|
||||||
dryRun bool
|
dryRun bool
|
||||||
appStore bool
|
track string // explicit track; empty means same as current
|
||||||
track string // explicit track; empty means same as current
|
version string // explicit version; empty means auto
|
||||||
version string // explicit version; empty means auto
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runUpdate(ctx context.Context, args []string) error {
|
func runUpdate(ctx context.Context, args []string) error {
|
||||||
@ -61,12 +59,11 @@ func runUpdate(ctx context.Context, args []string) error {
|
|||||||
ver = updateArgs.track
|
ver = updateArgs.track
|
||||||
}
|
}
|
||||||
err := clientupdate.Update(clientupdate.Arguments{
|
err := clientupdate.Update(clientupdate.Arguments{
|
||||||
Version: ver,
|
Version: ver,
|
||||||
AppStore: updateArgs.appStore,
|
Logf: func(f string, a ...any) { printf(f+"\n", a...) },
|
||||||
Logf: func(f string, a ...any) { printf(f+"\n", a...) },
|
Stdout: Stdout,
|
||||||
Stdout: Stdout,
|
Stderr: Stderr,
|
||||||
Stderr: Stderr,
|
Confirm: confirmUpdate,
|
||||||
Confirm: confirmUpdate,
|
|
||||||
})
|
})
|
||||||
if errors.Is(err, errors.ErrUnsupported) {
|
if errors.Is(err, errors.ErrUnsupported) {
|
||||||
return errors.New("The 'update' command is not supported on this platform; see https://tailscale.com/s/client-updates")
|
return errors.New("The 'update' command is not supported on this platform; see https://tailscale.com/s/client-updates")
|
||||||
|
@ -47,19 +47,7 @@ var isSandboxedMacOS lazy.SyncValue[bool]
|
|||||||
// and macsys (System Extension) version on macOS, and false for
|
// and macsys (System Extension) version on macOS, and false for
|
||||||
// tailscaled-on-macOS.
|
// tailscaled-on-macOS.
|
||||||
func IsSandboxedMacOS() bool {
|
func IsSandboxedMacOS() bool {
|
||||||
if runtime.GOOS != "darwin" {
|
return IsMacAppStore() || IsMacSysExt()
|
||||||
return false
|
|
||||||
}
|
|
||||||
return isSandboxedMacOS.Get(func() bool {
|
|
||||||
if IsMacSysExt() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
exe, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return filepath.Base(exe) == "io.tailscale.ipn.macsys.network-extension" || strings.HasSuffix(exe, "/Contents/MacOS/Tailscale") || strings.HasSuffix(exe, "/Contents/MacOS/IPNExtension")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var isMacSysExt lazy.SyncValue[bool]
|
var isMacSysExt lazy.SyncValue[bool]
|
||||||
@ -79,6 +67,23 @@ func IsMacSysExt() bool {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isMacAppStore lazy.SyncValue[bool]
|
||||||
|
|
||||||
|
// IsMacAppStore whether this binary is from the App Store version of Tailscale
|
||||||
|
// for macOS.
|
||||||
|
func IsMacAppStore() bool {
|
||||||
|
if runtime.GOOS != "darwin" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return isMacAppStore.Get(func() bool {
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.HasSuffix(exe, "/Contents/MacOS/Tailscale") || strings.HasSuffix(exe, "/Contents/MacOS/IPNExtension")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var isAppleTV lazy.SyncValue[bool]
|
var isAppleTV lazy.SyncValue[bool]
|
||||||
|
|
||||||
// IsAppleTV reports whether this binary is part of the Tailscale network extension for tvOS.
|
// IsAppleTV reports whether this binary is part of the Tailscale network extension for tvOS.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user