From 3d1aa7a8538b2063e74e15e9b11fc0ce51f80adb Mon Sep 17 00:00:00 2001
From: Brad Fitzpatrick <bradfitz@tailscale.com>
Date: Sun, 23 Mar 2025 15:39:54 -0700
Subject: [PATCH] clientupdate: cache CanAutoUpdate, avoid log spam when false

I noticed logs on one of my machines where it can't auto-update with
scary log spam about "failed to apply tailnet-wide default for
auto-updates".

This avoids trying to do the EditPrefs if we know it's just going to
fail anyway.

Updates #282

Change-Id: Ib7db3b122185faa70efe08b60ebd05a6094eed8c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
---
 clientupdate/clientupdate.go | 19 ++++++++++++-------
 ipn/ipnlocal/local.go        | 26 ++++++++++++++------------
 2 files changed, 26 insertions(+), 19 deletions(-)

diff --git a/clientupdate/clientupdate.go b/clientupdate/clientupdate.go
index c5baeb8e9..cd5c299fc 100644
--- a/clientupdate/clientupdate.go
+++ b/clientupdate/clientupdate.go
@@ -28,6 +28,7 @@ import (
 	"strings"
 
 	"tailscale.com/hostinfo"
+	"tailscale.com/types/lazy"
 	"tailscale.com/types/logger"
 	"tailscale.com/util/cmpver"
 	"tailscale.com/version"
@@ -249,16 +250,20 @@ func (up *Updater) getUpdateFunction() (fn updateFunction, canAutoUpdate bool) {
 	return nil, false
 }
 
+var canAutoUpdate lazy.SyncValue[bool]
+
 // CanAutoUpdate reports whether auto-updating via the clientupdate package
 // is supported for the current os/distro.
 func CanAutoUpdate() bool {
-	if version.IsMacSysExt() {
-		// Macsys uses Sparkle for auto-updates, which doesn't have an update
-		// function in this package.
-		return true
-	}
-	_, canAutoUpdate := (&Updater{}).getUpdateFunction()
-	return canAutoUpdate
+	return canAutoUpdate.Get(func() bool {
+		if version.IsMacSysExt() {
+			// Macsys uses Sparkle for auto-updates, which doesn't have an update
+			// function in this package.
+			return true
+		}
+		_, canAutoUpdate := (&Updater{}).getUpdateFunction()
+		return canAutoUpdate
+	})
 }
 
 // Update runs a single update attempt using the platform-specific mechanism.
diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go
index 622283acb..0a0b2280d 100644
--- a/ipn/ipnlocal/local.go
+++ b/ipn/ipnlocal/local.go
@@ -3479,18 +3479,20 @@ func (b *LocalBackend) onTailnetDefaultAutoUpdate(au bool) {
 		// can still manually enable auto-updates on this node.
 		return
 	}
-	b.logf("using tailnet default auto-update setting: %v", au)
-	prefsClone := prefs.AsStruct()
-	prefsClone.AutoUpdate.Apply = opt.NewBool(au)
-	_, err := b.editPrefsLockedOnEntry(&ipn.MaskedPrefs{
-		Prefs: *prefsClone,
-		AutoUpdateSet: ipn.AutoUpdatePrefsMask{
-			ApplySet: true,
-		},
-	}, unlock)
-	if err != nil {
-		b.logf("failed to apply tailnet-wide default for auto-updates (%v): %v", au, err)
-		return
+	if clientupdate.CanAutoUpdate() {
+		b.logf("using tailnet default auto-update setting: %v", au)
+		prefsClone := prefs.AsStruct()
+		prefsClone.AutoUpdate.Apply = opt.NewBool(au)
+		_, err := b.editPrefsLockedOnEntry(&ipn.MaskedPrefs{
+			Prefs: *prefsClone,
+			AutoUpdateSet: ipn.AutoUpdatePrefsMask{
+				ApplySet: true,
+			},
+		}, unlock)
+		if err != nil {
+			b.logf("failed to apply tailnet-wide default for auto-updates (%v): %v", au, err)
+			return
+		}
 	}
 }