mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-18 02:48:40 +00:00
clientupdate: distinguish when auto-updates are possible (#9896)
clientupdate.Updater will have a non-nil Update func in a few cases where it doesn't actually perform an update: * on Arch-like distros, where it prints instructions on how to update * on macOS app store version, where it opens the app store page Add a new clientupdate.Arguments field to cause NewUpdater to fail when we hit one of these cases. This results in c2n updates being "not supported" and `tailscale set --auto-update` returning an error. Updates #755 Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
This commit is contained in:
parent
7df6f8736a
commit
593c086866
@ -86,6 +86,10 @@ type Arguments struct {
|
|||||||
// PkgsAddr is the address of the pkgs server to fetch updates from.
|
// PkgsAddr is the address of the pkgs server to fetch updates from.
|
||||||
// Defaults to "https://pkgs.tailscale.com".
|
// Defaults to "https://pkgs.tailscale.com".
|
||||||
PkgsAddr string
|
PkgsAddr string
|
||||||
|
// ForAutoUpdate should be true when Updater is created in auto-update
|
||||||
|
// context. When true, NewUpdater returns an error if it cannot be used for
|
||||||
|
// auto-updates (even if Updater.Update field is non-nil).
|
||||||
|
ForAutoUpdate bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (args Arguments) validate() error {
|
func (args Arguments) validate() error {
|
||||||
@ -116,10 +120,14 @@ func NewUpdater(args Arguments) (*Updater, error) {
|
|||||||
if up.Stderr == nil {
|
if up.Stderr == nil {
|
||||||
up.Stderr = os.Stderr
|
up.Stderr = os.Stderr
|
||||||
}
|
}
|
||||||
up.Update = up.getUpdateFunction()
|
var canAutoUpdate bool
|
||||||
|
up.Update, canAutoUpdate = up.getUpdateFunction()
|
||||||
if up.Update == nil {
|
if up.Update == nil {
|
||||||
return nil, errors.ErrUnsupported
|
return nil, errors.ErrUnsupported
|
||||||
}
|
}
|
||||||
|
if args.ForAutoUpdate && !canAutoUpdate {
|
||||||
|
return nil, errors.ErrUnsupported
|
||||||
|
}
|
||||||
switch up.Version {
|
switch up.Version {
|
||||||
case StableTrack, UnstableTrack:
|
case StableTrack, UnstableTrack:
|
||||||
up.track = up.Version
|
up.track = up.Version
|
||||||
@ -144,52 +152,64 @@ func NewUpdater(args Arguments) (*Updater, error) {
|
|||||||
|
|
||||||
type updateFunction func() error
|
type updateFunction func() error
|
||||||
|
|
||||||
func (up *Updater) getUpdateFunction() updateFunction {
|
func (up *Updater) getUpdateFunction() (fn updateFunction, canAutoUpdate bool) {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "windows":
|
case "windows":
|
||||||
return up.updateWindows
|
return up.updateWindows, true
|
||||||
case "linux":
|
case "linux":
|
||||||
switch distro.Get() {
|
switch distro.Get() {
|
||||||
case distro.Synology:
|
case distro.Synology:
|
||||||
return up.updateSynology
|
return up.updateSynology, true
|
||||||
case distro.Debian: // includes Ubuntu
|
case distro.Debian: // includes Ubuntu
|
||||||
return up.updateDebLike
|
return up.updateDebLike, true
|
||||||
case distro.Arch:
|
case distro.Arch:
|
||||||
return up.updateArchLike
|
if up.archPackageInstalled() {
|
||||||
|
// Arch update func just prints a message about how to update,
|
||||||
|
// it doesn't support auto-updates.
|
||||||
|
return up.updateArchLike, false
|
||||||
|
}
|
||||||
|
return up.updateLinuxBinary, true
|
||||||
case distro.Alpine:
|
case distro.Alpine:
|
||||||
return up.updateAlpineLike
|
return up.updateAlpineLike, true
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case haveExecutable("pacman"):
|
case haveExecutable("pacman"):
|
||||||
return up.updateArchLike
|
if up.archPackageInstalled() {
|
||||||
|
// Arch update func just prints a message about how to update,
|
||||||
|
// it doesn't support auto-updates.
|
||||||
|
return up.updateArchLike, false
|
||||||
|
}
|
||||||
|
return up.updateLinuxBinary, true
|
||||||
case haveExecutable("apt-get"): // TODO(awly): add support for "apt"
|
case haveExecutable("apt-get"): // TODO(awly): add support for "apt"
|
||||||
// The distro.Debian switch case above should catch most apt-based
|
// The distro.Debian switch case above should catch most apt-based
|
||||||
// systems, but add this fallback just in case.
|
// systems, but add this fallback just in case.
|
||||||
return up.updateDebLike
|
return up.updateDebLike, true
|
||||||
case haveExecutable("dnf"):
|
case haveExecutable("dnf"):
|
||||||
return up.updateFedoraLike("dnf")
|
return up.updateFedoraLike("dnf"), true
|
||||||
case haveExecutable("yum"):
|
case haveExecutable("yum"):
|
||||||
return up.updateFedoraLike("yum")
|
return up.updateFedoraLike("yum"), true
|
||||||
case haveExecutable("apk"):
|
case haveExecutable("apk"):
|
||||||
return up.updateAlpineLike
|
return up.updateAlpineLike, true
|
||||||
}
|
}
|
||||||
// If nothing matched, fall back to tarball updates.
|
// If nothing matched, fall back to tarball updates.
|
||||||
if up.Update == nil {
|
if up.Update == nil {
|
||||||
return up.updateLinuxBinary
|
return up.updateLinuxBinary, true
|
||||||
}
|
}
|
||||||
case "darwin":
|
case "darwin":
|
||||||
switch {
|
switch {
|
||||||
case version.IsMacAppStore():
|
case version.IsMacAppStore():
|
||||||
return up.updateMacAppStore
|
// App store update func just opens the store page, it doesn't
|
||||||
|
// support auto-updates.
|
||||||
|
return up.updateMacAppStore, false
|
||||||
case version.IsMacSysExt():
|
case version.IsMacSysExt():
|
||||||
return up.updateMacSys
|
return up.updateMacSys, true
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil, false
|
||||||
}
|
}
|
||||||
case "freebsd":
|
case "freebsd":
|
||||||
return up.updateFreeBSD
|
return up.updateFreeBSD, true
|
||||||
}
|
}
|
||||||
return nil
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update runs a single update attempt using the platform-specific mechanism.
|
// Update runs a single update attempt using the platform-specific mechanism.
|
||||||
@ -454,12 +474,12 @@ func updateDebianAptSourcesListBytes(was []byte, dstTrack string) (newContent []
|
|||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (up *Updater) archPackageInstalled() bool {
|
||||||
|
err := exec.Command("pacman", "--query", "tailscale").Run()
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
func (up *Updater) updateArchLike() error {
|
func (up *Updater) updateArchLike() error {
|
||||||
if err := exec.Command("pacman", "--query", "tailscale").Run(); err != nil && isExitError(err) {
|
|
||||||
// Tailscale was not installed via pacman, update via tarball download
|
|
||||||
// instead.
|
|
||||||
return up.updateLinuxBinary()
|
|
||||||
}
|
|
||||||
// Arch maintainer asked us not to implement "tailscale update" or
|
// Arch maintainer asked us not to implement "tailscale update" or
|
||||||
// auto-updates on Arch-based distros:
|
// auto-updates on Arch-based distros:
|
||||||
// https://github.com/tailscale/tailscale/issues/6995#issuecomment-1687080106
|
// https://github.com/tailscale/tailscale/issues/6995#issuecomment-1687080106
|
||||||
|
@ -157,7 +157,7 @@ func runSet(ctx context.Context, args []string) (retErr error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if maskedPrefs.AutoUpdateSet {
|
if maskedPrefs.AutoUpdateSet {
|
||||||
_, err := clientupdate.NewUpdater(clientupdate.Arguments{})
|
_, err := clientupdate.NewUpdater(clientupdate.Arguments{ForAutoUpdate: true})
|
||||||
if errors.Is(err, errors.ErrUnsupported) {
|
if errors.Is(err, errors.ErrUnsupported) {
|
||||||
return errors.New("automatic updates are not supported on this platform")
|
return errors.New("automatic updates are not supported on this platform")
|
||||||
}
|
}
|
||||||
|
@ -265,7 +265,7 @@ func (b *LocalBackend) newC2NUpdateResponse() tailcfg.C2NUpdateResponse {
|
|||||||
// Note that we create the Updater solely to check for errors; we do not
|
// Note that we create the Updater solely to check for errors; we do not
|
||||||
// invoke it here. For this purpose, it is ok to pass it a zero Arguments.
|
// invoke it here. For this purpose, it is ok to pass it a zero Arguments.
|
||||||
prefs := b.Prefs().AutoUpdate()
|
prefs := b.Prefs().AutoUpdate()
|
||||||
_, err := clientupdate.NewUpdater(clientupdate.Arguments{})
|
_, err := clientupdate.NewUpdater(clientupdate.Arguments{ForAutoUpdate: true})
|
||||||
return tailcfg.C2NUpdateResponse{
|
return tailcfg.C2NUpdateResponse{
|
||||||
Enabled: envknob.AllowsRemoteUpdate() || prefs.Apply,
|
Enabled: envknob.AllowsRemoteUpdate() || prefs.Apply,
|
||||||
Supported: err == nil,
|
Supported: err == nil,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user