|
|
@@ -27,11 +27,9 @@ import (
|
|
|
"strconv"
|
|
|
"strings"
|
|
|
|
|
|
- "github.com/google/uuid"
|
|
|
"tailscale.com/clientupdate/distsign"
|
|
|
"tailscale.com/types/logger"
|
|
|
"tailscale.com/util/cmpver"
|
|
|
- "tailscale.com/util/winutil"
|
|
|
"tailscale.com/version"
|
|
|
"tailscale.com/version/distro"
|
|
|
)
|
|
|
@@ -756,164 +754,6 @@ func (up *Updater) updateMacAppStore() error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-const (
|
|
|
- // 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
|
|
|
- // tailscale.exe process from running before the msiexec process runs and
|
|
|
- // tries to overwrite ourselves.
|
|
|
- winMSIEnv = "TS_UPDATE_WIN_MSI"
|
|
|
- // winExePathEnv is the environment variable that is set along with
|
|
|
- // winMSIEnv and carries the full path of the calling tailscale.exe binary.
|
|
|
- // It is used to re-launch the GUI process (tailscale-ipn.exe) after
|
|
|
- // install is complete.
|
|
|
- winExePathEnv = "TS_UPDATE_WIN_EXE_PATH"
|
|
|
-)
|
|
|
-
|
|
|
-var (
|
|
|
- verifyAuthenticode func(string) error // set non-nil only on Windows
|
|
|
- markTempFileFunc func(string) error // set non-nil only on Windows
|
|
|
-)
|
|
|
-
|
|
|
-func (up *Updater) updateWindows() error {
|
|
|
- if msi := os.Getenv(winMSIEnv); msi != "" {
|
|
|
- // stdout/stderr from this part of the install could be lost since the
|
|
|
- // parent tailscaled is replaced. Create a temp log file to have some
|
|
|
- // output to debug with in case update fails.
|
|
|
- close, err := up.switchOutputToFile()
|
|
|
- if err != nil {
|
|
|
- up.Logf("failed to create log file for installation: %v; proceeding with existing outputs", err)
|
|
|
- } else {
|
|
|
- defer close.Close()
|
|
|
- }
|
|
|
-
|
|
|
- up.Logf("installing %v ...", msi)
|
|
|
- if err := up.installMSI(msi); err != nil {
|
|
|
- up.Logf("MSI install failed: %v", err)
|
|
|
- return err
|
|
|
- }
|
|
|
-
|
|
|
- up.Logf("success.")
|
|
|
- return nil
|
|
|
- }
|
|
|
-
|
|
|
- if !winutil.IsCurrentProcessElevated() {
|
|
|
- return errors.New(`update must be run as Administrator
|
|
|
-
|
|
|
-you can run the command prompt as Administrator one of these ways:
|
|
|
-* right-click cmd.exe, select 'Run as administrator'
|
|
|
-* press Windows+x, then press a
|
|
|
-* press Windows+r, type in "cmd", then press Ctrl+Shift+Enter`)
|
|
|
- }
|
|
|
- ver, err := requestedTailscaleVersion(up.Version, up.Track)
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- arch := runtime.GOARCH
|
|
|
- if arch == "386" {
|
|
|
- arch = "x86"
|
|
|
- }
|
|
|
- if !up.confirm(ver) {
|
|
|
- return nil
|
|
|
- }
|
|
|
-
|
|
|
- tsDir := filepath.Join(os.Getenv("ProgramData"), "Tailscale")
|
|
|
- msiDir := filepath.Join(tsDir, "MSICache")
|
|
|
- if fi, err := os.Stat(tsDir); err != nil {
|
|
|
- return fmt.Errorf("expected %s to exist, got stat error: %w", tsDir, err)
|
|
|
- } else if !fi.IsDir() {
|
|
|
- return fmt.Errorf("expected %s to be a directory; got %v", tsDir, fi.Mode())
|
|
|
- }
|
|
|
- if err := os.MkdirAll(msiDir, 0700); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- up.cleanupOldDownloads(filepath.Join(msiDir, "*.msi"))
|
|
|
- pkgsPath := fmt.Sprintf("%s/tailscale-setup-%s-%s.msi", up.Track, ver, arch)
|
|
|
- msiTarget := filepath.Join(msiDir, path.Base(pkgsPath))
|
|
|
- if err := up.downloadURLToFile(pkgsPath, msiTarget); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
-
|
|
|
- up.Logf("verifying MSI authenticode...")
|
|
|
- if err := verifyAuthenticode(msiTarget); err != nil {
|
|
|
- return fmt.Errorf("authenticode verification of %s failed: %w", msiTarget, err)
|
|
|
- }
|
|
|
- up.Logf("authenticode verification succeeded")
|
|
|
-
|
|
|
- up.Logf("making tailscale.exe copy to switch to...")
|
|
|
- up.cleanupOldDownloads(filepath.Join(os.TempDir(), "tailscale-updater-*.exe"))
|
|
|
- selfOrig, selfCopy, err := makeSelfCopy()
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- defer os.Remove(selfCopy)
|
|
|
- up.Logf("running tailscale.exe copy for final install...")
|
|
|
-
|
|
|
- cmd := exec.Command(selfCopy, "update")
|
|
|
- cmd.Env = append(os.Environ(), winMSIEnv+"="+msiTarget, winExePathEnv+"="+selfOrig)
|
|
|
- cmd.Stdout = up.Stderr
|
|
|
- cmd.Stderr = up.Stderr
|
|
|
- cmd.Stdin = os.Stdin
|
|
|
- if err := cmd.Start(); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- // Once it's started, exit ourselves, so the binary is free
|
|
|
- // to be replaced.
|
|
|
- os.Exit(0)
|
|
|
- panic("unreachable")
|
|
|
-}
|
|
|
-
|
|
|
-func (up *Updater) switchOutputToFile() (io.Closer, error) {
|
|
|
- var logFilePath string
|
|
|
- exePath, err := os.Executable()
|
|
|
- if err != nil {
|
|
|
- logFilePath = filepath.Join(os.TempDir(), "tailscale-updater.log")
|
|
|
- } else {
|
|
|
- logFilePath = strings.TrimSuffix(exePath, ".exe") + ".log"
|
|
|
- }
|
|
|
-
|
|
|
- up.Logf("writing update output to %q", logFilePath)
|
|
|
- logFile, err := os.Create(logFilePath)
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
-
|
|
|
- up.Logf = func(m string, args ...any) {
|
|
|
- fmt.Fprintf(logFile, m+"\n", args...)
|
|
|
- }
|
|
|
- up.Stdout = logFile
|
|
|
- up.Stderr = logFile
|
|
|
- return logFile, nil
|
|
|
-}
|
|
|
-
|
|
|
-func (up *Updater) installMSI(msi string) error {
|
|
|
- var err error
|
|
|
- for tries := 0; tries < 2; tries++ {
|
|
|
- cmd := exec.Command("msiexec.exe", "/i", filepath.Base(msi), "/quiet", "/norestart", "/qn")
|
|
|
- cmd.Dir = filepath.Dir(msi)
|
|
|
- cmd.Stdout = up.Stdout
|
|
|
- cmd.Stderr = up.Stderr
|
|
|
- cmd.Stdin = os.Stdin
|
|
|
- err = cmd.Run()
|
|
|
- if err == nil {
|
|
|
- break
|
|
|
- }
|
|
|
- up.Logf("Install attempt failed: %v", err)
|
|
|
- uninstallVersion := up.currentVersion
|
|
|
- if v := os.Getenv("TS_DEBUG_UNINSTALL_VERSION"); v != "" {
|
|
|
- uninstallVersion = v
|
|
|
- }
|
|
|
- // Assume it's a downgrade, which msiexec won't permit. Uninstall our current version first.
|
|
|
- up.Logf("Uninstalling current version %q for downgrade...", uninstallVersion)
|
|
|
- cmd = exec.Command("msiexec.exe", "/x", msiUUIDForVersion(uninstallVersion), "/norestart", "/qn")
|
|
|
- cmd.Stdout = up.Stdout
|
|
|
- cmd.Stderr = up.Stderr
|
|
|
- cmd.Stdin = os.Stdin
|
|
|
- err = cmd.Run()
|
|
|
- up.Logf("msiexec uninstall: %v", err)
|
|
|
- }
|
|
|
- return err
|
|
|
-}
|
|
|
-
|
|
|
// cleanupOldDownloads removes all files matching glob (see filepath.Glob).
|
|
|
// Only regular files are removed, so the glob must match specific files and
|
|
|
// not directories.
|
|
|
@@ -938,45 +778,6 @@ func (up *Updater) cleanupOldDownloads(glob string) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func msiUUIDForVersion(ver string) string {
|
|
|
- arch := runtime.GOARCH
|
|
|
- if arch == "386" {
|
|
|
- arch = "x86"
|
|
|
- }
|
|
|
- track, err := versionToTrack(ver)
|
|
|
- if err != nil {
|
|
|
- track = UnstableTrack
|
|
|
- }
|
|
|
- msiURL := fmt.Sprintf("https://pkgs.tailscale.com/%s/tailscale-setup-%s-%s.msi", track, ver, arch)
|
|
|
- return "{" + strings.ToUpper(uuid.NewSHA1(uuid.NameSpaceURL, []byte(msiURL)).String()) + "}"
|
|
|
-}
|
|
|
-
|
|
|
-func makeSelfCopy() (origPathExe, tmpPathExe string, err error) {
|
|
|
- selfExe, err := os.Executable()
|
|
|
- if err != nil {
|
|
|
- return "", "", err
|
|
|
- }
|
|
|
- f, err := os.Open(selfExe)
|
|
|
- if err != nil {
|
|
|
- return "", "", err
|
|
|
- }
|
|
|
- defer f.Close()
|
|
|
- f2, err := os.CreateTemp("", "tailscale-updater-*.exe")
|
|
|
- if err != nil {
|
|
|
- return "", "", err
|
|
|
- }
|
|
|
- if f := markTempFileFunc; f != nil {
|
|
|
- if err := f(f2.Name()); err != nil {
|
|
|
- return "", "", err
|
|
|
- }
|
|
|
- }
|
|
|
- if _, err := io.Copy(f2, f); err != nil {
|
|
|
- f2.Close()
|
|
|
- return "", "", err
|
|
|
- }
|
|
|
- return selfExe, f2.Name(), f2.Close()
|
|
|
-}
|
|
|
-
|
|
|
func (up *Updater) downloadURLToFile(pathSrc, fileDst string) (ret error) {
|
|
|
c, err := distsign.NewClient(up.Logf, up.PkgsAddr)
|
|
|
if err != nil {
|