|
|
@ -57,14 +57,8 @@ func versionToTrack(v string) (string, error) {
|
|
|
|
return "unstable", nil
|
|
|
|
return "unstable", nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type updater struct {
|
|
|
|
// Arguments contains arguments needed to run an update.
|
|
|
|
UpdateArgs
|
|
|
|
type Arguments struct {
|
|
|
|
track string
|
|
|
|
|
|
|
|
update func() error
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// UpdateArgs contains arguments needed to run an update.
|
|
|
|
|
|
|
|
type UpdateArgs struct {
|
|
|
|
|
|
|
|
// Version can be a specific version number or one of the predefined track
|
|
|
|
// Version can be a specific version number or one of the predefined track
|
|
|
|
// constants:
|
|
|
|
// constants:
|
|
|
|
//
|
|
|
|
//
|
|
|
@ -76,7 +70,7 @@ type UpdateArgs 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
|
|
|
|
// AppStore forces a local app store check, even if the current binary was
|
|
|
|
// not installed via an app store.
|
|
|
|
// not installed via an app store. TODO(cpalmer): Remove this.
|
|
|
|
AppStore bool
|
|
|
|
AppStore bool
|
|
|
|
// Logf is a logger for update progress messages.
|
|
|
|
// Logf is a logger for update progress messages.
|
|
|
|
Logf logger.Logf
|
|
|
|
Logf logger.Logf
|
|
|
@ -89,30 +83,31 @@ type UpdateArgs struct {
|
|
|
|
PkgsAddr string
|
|
|
|
PkgsAddr string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (args UpdateArgs) validate() error {
|
|
|
|
func (args Arguments) validate() error {
|
|
|
|
if args.Confirm == nil {
|
|
|
|
if args.Confirm == nil {
|
|
|
|
return errors.New("missing Confirm callback in UpdateArgs")
|
|
|
|
return errors.New("missing Confirm callback in Arguments")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if args.Logf == nil {
|
|
|
|
if args.Logf == nil {
|
|
|
|
return errors.New("missing Logf callback in UpdateArgs")
|
|
|
|
return errors.New("missing Logf callback in Arguments")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Update runs a single update attempt using the platform-specific mechanism.
|
|
|
|
type Updater struct {
|
|
|
|
//
|
|
|
|
Arguments
|
|
|
|
// On Windows, this copies the calling binary and re-executes it to apply the
|
|
|
|
track string
|
|
|
|
// update. The calling binary should handle an "update" subcommand and call
|
|
|
|
// Update is a platform-specific method that updates the installation. May be
|
|
|
|
// this function again for the re-executed binary to proceed.
|
|
|
|
// nil (not all platforms support updates from within Tailscale).
|
|
|
|
func Update(args UpdateArgs) error {
|
|
|
|
Update func() error
|
|
|
|
if err := args.validate(); err != nil {
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
|
|
|
|
}
|
|
|
|
func NewUpdater(args Arguments) (*Updater, error) {
|
|
|
|
if args.PkgsAddr == "" {
|
|
|
|
up := Updater{
|
|
|
|
args.PkgsAddr = "https://pkgs.tailscale.com"
|
|
|
|
Arguments: args,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
up := &updater{
|
|
|
|
up.Update = up.getUpdateFunction()
|
|
|
|
UpdateArgs: args,
|
|
|
|
if up.Update == nil {
|
|
|
|
|
|
|
|
return nil, errors.ErrUnsupported
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch up.Version {
|
|
|
|
switch up.Version {
|
|
|
|
case StableTrack, UnstableTrack:
|
|
|
|
case StableTrack, UnstableTrack:
|
|
|
@ -127,60 +122,82 @@ func Update(args UpdateArgs) error {
|
|
|
|
var err error
|
|
|
|
var err error
|
|
|
|
up.track, err = versionToTrack(args.Version)
|
|
|
|
up.track, err = versionToTrack(args.Version)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if args.PkgsAddr == "" {
|
|
|
|
|
|
|
|
args.PkgsAddr = "https://pkgs.tailscale.com"
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return &up, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type updateFunction func() error
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (up *Updater) getUpdateFunction() updateFunction {
|
|
|
|
switch runtime.GOOS {
|
|
|
|
switch runtime.GOOS {
|
|
|
|
case "windows":
|
|
|
|
case "windows":
|
|
|
|
up.update = up.updateWindows
|
|
|
|
return up.updateWindows
|
|
|
|
case "linux":
|
|
|
|
case "linux":
|
|
|
|
switch distro.Get() {
|
|
|
|
switch distro.Get() {
|
|
|
|
case distro.Synology:
|
|
|
|
case distro.Synology:
|
|
|
|
up.update = up.updateSynology
|
|
|
|
return up.updateSynology
|
|
|
|
case distro.Debian: // includes Ubuntu
|
|
|
|
case distro.Debian: // includes Ubuntu
|
|
|
|
up.update = up.updateDebLike
|
|
|
|
return up.updateDebLike
|
|
|
|
case distro.Arch:
|
|
|
|
case distro.Arch:
|
|
|
|
up.update = up.updateArchLike
|
|
|
|
return up.updateArchLike
|
|
|
|
case distro.Alpine:
|
|
|
|
case distro.Alpine:
|
|
|
|
up.update = up.updateAlpineLike
|
|
|
|
return up.updateAlpineLike
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch {
|
|
|
|
switch {
|
|
|
|
case haveExecutable("pacman"):
|
|
|
|
case haveExecutable("pacman"):
|
|
|
|
up.update = up.updateArchLike
|
|
|
|
return up.updateArchLike
|
|
|
|
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.
|
|
|
|
up.update = up.updateDebLike
|
|
|
|
return up.updateDebLike
|
|
|
|
case haveExecutable("dnf"):
|
|
|
|
case haveExecutable("dnf"):
|
|
|
|
up.update = up.updateFedoraLike("dnf")
|
|
|
|
return up.updateFedoraLike("dnf")
|
|
|
|
case haveExecutable("yum"):
|
|
|
|
case haveExecutable("yum"):
|
|
|
|
up.update = up.updateFedoraLike("yum")
|
|
|
|
return up.updateFedoraLike("yum")
|
|
|
|
case haveExecutable("apk"):
|
|
|
|
case haveExecutable("apk"):
|
|
|
|
up.update = up.updateAlpineLike
|
|
|
|
return up.updateAlpineLike
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If nothing matched, fall back to tarball updates.
|
|
|
|
// If nothing matched, fall back to tarball updates.
|
|
|
|
if up.update == nil {
|
|
|
|
if up.Update == nil {
|
|
|
|
up.update = up.updateLinuxBinary
|
|
|
|
return up.updateLinuxBinary
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case "darwin":
|
|
|
|
case "darwin":
|
|
|
|
switch {
|
|
|
|
switch {
|
|
|
|
case !args.AppStore && !version.IsSandboxedMacOS():
|
|
|
|
case !up.Arguments.AppStore && !version.IsSandboxedMacOS():
|
|
|
|
return errors.ErrUnsupported
|
|
|
|
return nil
|
|
|
|
case !args.AppStore && strings.HasSuffix(os.Getenv("HOME"), "/io.tailscale.ipn.macsys/Data"):
|
|
|
|
case !up.Arguments.AppStore && strings.HasSuffix(os.Getenv("HOME"), "/io.tailscale.ipn.macsys/Data"):
|
|
|
|
up.update = up.updateMacSys
|
|
|
|
return up.updateMacSys
|
|
|
|
default:
|
|
|
|
default:
|
|
|
|
up.update = up.updateMacAppStore
|
|
|
|
return up.updateMacAppStore
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case "freebsd":
|
|
|
|
case "freebsd":
|
|
|
|
up.update = up.updateFreeBSD
|
|
|
|
return up.updateFreeBSD
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Update runs a single update attempt using the platform-specific mechanism.
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// On Windows, this copies the calling binary and re-executes it to apply the
|
|
|
|
|
|
|
|
// update. The calling binary should handle an "update" subcommand and call
|
|
|
|
|
|
|
|
// this function again for the re-executed binary to proceed.
|
|
|
|
|
|
|
|
func Update(args Arguments) error {
|
|
|
|
|
|
|
|
if err := args.validate(); err != nil {
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if up.update == nil {
|
|
|
|
up, err := NewUpdater(args)
|
|
|
|
return errors.ErrUnsupported
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return up.update()
|
|
|
|
return up.Update()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (up *updater) confirm(ver string) bool {
|
|
|
|
func (up *Updater) confirm(ver string) bool {
|
|
|
|
if version.Short() == ver {
|
|
|
|
if version.Short() == ver {
|
|
|
|
up.Logf("already running %v; no update needed", ver)
|
|
|
|
up.Logf("already running %v; no update needed", ver)
|
|
|
|
return false
|
|
|
|
return false
|
|
|
@ -193,7 +210,7 @@ func (up *updater) confirm(ver string) bool {
|
|
|
|
|
|
|
|
|
|
|
|
const synoinfoConfPath = "/etc/synoinfo.conf"
|
|
|
|
const synoinfoConfPath = "/etc/synoinfo.conf"
|
|
|
|
|
|
|
|
|
|
|
|
func (up *updater) updateSynology() error {
|
|
|
|
func (up *Updater) updateSynology() error {
|
|
|
|
if up.Version != "" {
|
|
|
|
if up.Version != "" {
|
|
|
|
return errors.New("installing a specific version on Synology is not supported")
|
|
|
|
return errors.New("installing a specific version on Synology is not supported")
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -303,7 +320,7 @@ func parseSynoinfo(path string) (string, error) {
|
|
|
|
return "", fmt.Errorf(`missing "unique=" field in %q`, path)
|
|
|
|
return "", fmt.Errorf(`missing "unique=" field in %q`, path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (up *updater) updateDebLike() error {
|
|
|
|
func (up *Updater) updateDebLike() error {
|
|
|
|
if err := requireRoot(); err != nil {
|
|
|
|
if err := requireRoot(); err != nil {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -409,7 +426,7 @@ func updateDebianAptSourcesListBytes(was []byte, dstTrack string) (newContent []
|
|
|
|
return buf.Bytes(), nil
|
|
|
|
return buf.Bytes(), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (up *updater) updateArchLike() error {
|
|
|
|
func (up *Updater) updateArchLike() error {
|
|
|
|
if err := exec.Command("pacman", "--query", "tailscale").Run(); err != nil && isExitError(err) {
|
|
|
|
if err := exec.Command("pacman", "--query", "tailscale").Run(); err != nil && isExitError(err) {
|
|
|
|
// Tailscale was not installed via pacman, update via tarball download
|
|
|
|
// Tailscale was not installed via pacman, update via tarball download
|
|
|
|
// instead.
|
|
|
|
// instead.
|
|
|
@ -427,7 +444,7 @@ const yumRepoConfigFile = "/etc/yum.repos.d/tailscale.repo"
|
|
|
|
// updateFedoraLike updates tailscale on any distros in the Fedora family,
|
|
|
|
// updateFedoraLike updates tailscale on any distros in the Fedora family,
|
|
|
|
// specifically anything that uses "dnf" or "yum" package managers. The actual
|
|
|
|
// specifically anything that uses "dnf" or "yum" package managers. The actual
|
|
|
|
// package manager is passed via packageManager.
|
|
|
|
// package manager is passed via packageManager.
|
|
|
|
func (up *updater) updateFedoraLike(packageManager string) func() error {
|
|
|
|
func (up *Updater) updateFedoraLike(packageManager string) func() error {
|
|
|
|
return func() (err error) {
|
|
|
|
return func() (err error) {
|
|
|
|
if err := requireRoot(); err != nil {
|
|
|
|
if err := requireRoot(); err != nil {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
@ -508,7 +525,7 @@ func updateYUMRepoTrack(repoFile, dstTrack string) (rewrote bool, err error) {
|
|
|
|
return true, os.WriteFile(repoFile, newContent.Bytes(), 0644)
|
|
|
|
return true, os.WriteFile(repoFile, newContent.Bytes(), 0644)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (up *updater) updateAlpineLike() (err error) {
|
|
|
|
func (up *Updater) updateAlpineLike() (err error) {
|
|
|
|
if up.Version != "" {
|
|
|
|
if up.Version != "" {
|
|
|
|
return errors.New("installing a specific version on Alpine-based distros is not supported")
|
|
|
|
return errors.New("installing a specific version on Alpine-based distros is not supported")
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -570,11 +587,11 @@ func parseAlpinePackageVersion(out []byte) (string, error) {
|
|
|
|
return "", errors.New("tailscale version not found in output")
|
|
|
|
return "", errors.New("tailscale version not found in output")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (up *updater) updateMacSys() error {
|
|
|
|
func (up *Updater) updateMacSys() error {
|
|
|
|
return errors.New("NOTREACHED: On MacSys builds, `tailscale update` is handled in Swift to launch the GUI updater")
|
|
|
|
return errors.New("NOTREACHED: On MacSys builds, `tailscale update` is handled in Swift to launch the GUI updater")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (up *updater) updateMacAppStore() error {
|
|
|
|
func (up *Updater) updateMacAppStore() error {
|
|
|
|
out, err := exec.Command("defaults", "read", "/Library/Preferences/com.apple.commerce.plist", "AutoUpdate").CombinedOutput()
|
|
|
|
out, err := exec.Command("defaults", "read", "/Library/Preferences/com.apple.commerce.plist", "AutoUpdate").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 check App Store auto-update setting: %w, output: %q", err, string(out))
|
|
|
@ -635,7 +652,7 @@ var (
|
|
|
|
markTempFileFunc func(string) error // or nil on non-Windows
|
|
|
|
markTempFileFunc func(string) error // or nil on non-Windows
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func (up *updater) updateWindows() error {
|
|
|
|
func (up *Updater) updateWindows() error {
|
|
|
|
if msi := os.Getenv(winMSIEnv); msi != "" {
|
|
|
|
if msi := os.Getenv(winMSIEnv); msi != "" {
|
|
|
|
up.Logf("installing %v ...", msi)
|
|
|
|
up.Logf("installing %v ...", msi)
|
|
|
|
if err := up.installMSI(msi); err != nil {
|
|
|
|
if err := up.installMSI(msi); err != nil {
|
|
|
@ -705,7 +722,7 @@ func (up *updater) updateWindows() error {
|
|
|
|
panic("unreachable")
|
|
|
|
panic("unreachable")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (up *updater) installMSI(msi string) error {
|
|
|
|
func (up *Updater) installMSI(msi string) error {
|
|
|
|
var err error
|
|
|
|
var err error
|
|
|
|
for tries := 0; tries < 2; tries++ {
|
|
|
|
for tries := 0; tries < 2; tries++ {
|
|
|
|
cmd := exec.Command("msiexec.exe", "/i", filepath.Base(msi), "/quiet", "/promptrestart", "/qn")
|
|
|
|
cmd := exec.Command("msiexec.exe", "/i", filepath.Base(msi), "/quiet", "/promptrestart", "/qn")
|
|
|
@ -772,7 +789,7 @@ func makeSelfCopy() (tmpPathExe string, err error) {
|
|
|
|
return f2.Name(), f2.Close()
|
|
|
|
return f2.Name(), f2.Close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (up *updater) downloadURLToFile(pathSrc, fileDst string) (ret error) {
|
|
|
|
func (up *Updater) downloadURLToFile(pathSrc, fileDst string) (ret error) {
|
|
|
|
c, err := distsign.NewClient(up.Logf, up.PkgsAddr)
|
|
|
|
c, err := distsign.NewClient(up.Logf, up.PkgsAddr)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
@ -780,7 +797,7 @@ func (up *updater) downloadURLToFile(pathSrc, fileDst string) (ret error) {
|
|
|
|
return c.Download(context.Background(), pathSrc, fileDst)
|
|
|
|
return c.Download(context.Background(), pathSrc, fileDst)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (up *updater) updateFreeBSD() (err error) {
|
|
|
|
func (up *Updater) updateFreeBSD() (err error) {
|
|
|
|
if up.Version != "" {
|
|
|
|
if up.Version != "" {
|
|
|
|
return errors.New("installing a specific version on FreeBSD is not supported")
|
|
|
|
return errors.New("installing a specific version on FreeBSD is not supported")
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -821,7 +838,7 @@ func (up *updater) updateFreeBSD() (err error) {
|
|
|
|
return nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (up *updater) updateLinuxBinary() error {
|
|
|
|
func (up *Updater) updateLinuxBinary() error {
|
|
|
|
return errors.New("Linux binary updates without a package manager are not supported yet")
|
|
|
|
return errors.New("Linux binary updates without a package manager are not supported yet")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|