github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/install/install.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package install
     5  
     6  import (
     7  	"bufio"
     8  	"fmt"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"runtime"
    13  	"sort"
    14  	"strings"
    15  
    16  	"github.com/blang/semver"
    17  	"github.com/keybase/client/go/libkb"
    18  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    19  	"github.com/keybase/client/go/utils"
    20  )
    21  
    22  // Log is the logging interface for this package
    23  type Log interface {
    24  	Debug(s string, args ...interface{})
    25  	Info(s string, args ...interface{})
    26  	Warning(s string, args ...interface{})
    27  	Errorf(s string, args ...interface{})
    28  }
    29  
    30  // Context is the environment for this package
    31  type Context interface {
    32  	GetConfigDir() string
    33  	GetCacheDir() string
    34  	GetRuntimeDir() string
    35  	GetMountDir() (string, error)
    36  	GetLogDir() string
    37  	GetRunMode() libkb.RunMode
    38  	GetServiceInfoPath() string
    39  	GetKBFSInfoPath() string
    40  }
    41  
    42  // ComponentName defines a component name
    43  type ComponentName string
    44  
    45  const (
    46  	// ComponentNameCLI is the command line component
    47  	ComponentNameCLI ComponentName = "cli"
    48  	// ComponentNameService is the service component
    49  	ComponentNameService ComponentName = "service"
    50  	// ComponentNameKBFS is the KBFS component
    51  	ComponentNameKBFS ComponentName = "kbfs"
    52  	// ComponentNameKBNM is the Keybase NativeMessaging client component
    53  	ComponentNameKBNM ComponentName = "kbnm"
    54  	// ComponentNameUpdater is the updater component
    55  	ComponentNameUpdater ComponentName = "updater"
    56  	// ComponentNameApp is the UI app
    57  	ComponentNameApp ComponentName = "app"
    58  	// ComponentNameFuse is the Fuse component
    59  	ComponentNameFuse ComponentName = "fuse"
    60  	// ComponentNameHelper is the privileged helper tool
    61  	ComponentNameHelper ComponentName = "helper"
    62  	// ComponentNameMountDir is the mount directory
    63  	ComponentNameMountDir ComponentName = "mountdir"
    64  	// ComponentNameCLIPaths is for /etc/paths.d/Keybase
    65  	ComponentNameCLIPaths ComponentName = "clipaths"
    66  	// ComponentNameRedirector is the KBFS redirector
    67  	ComponentNameRedirector ComponentName = "redirector"
    68  	// ComponentNameUnknown is placeholder for unknown components
    69  	ComponentNameUnknown ComponentName = "unknown"
    70  )
    71  
    72  // ComponentNames are all the valid component names
    73  var ComponentNames = []ComponentName{ComponentNameCLI, ComponentNameService, ComponentNameKBFS, ComponentNameUpdater, ComponentNameFuse, ComponentNameHelper, ComponentNameApp, ComponentNameKBNM, ComponentNameRedirector, ComponentNameCLIPaths}
    74  
    75  // String returns string for ComponentName
    76  func (c ComponentName) String() string {
    77  	return string(c)
    78  }
    79  
    80  // Description returns description for component name
    81  func (c ComponentName) Description() string {
    82  	switch c {
    83  	case ComponentNameService:
    84  		return "Service"
    85  	case ComponentNameKBFS:
    86  		return "KBFS"
    87  	case ComponentNameApp:
    88  		return "App"
    89  	case ComponentNameCLI:
    90  		return "Command Line"
    91  	case ComponentNameUpdater:
    92  		return "Updater"
    93  	case ComponentNameFuse:
    94  		return "Fuse"
    95  	case ComponentNameHelper:
    96  		return "Privileged Helper Tool"
    97  	case ComponentNameKBNM:
    98  		return "Browser Native Messaging"
    99  	case ComponentNameCLIPaths:
   100  		return "Command Line (privileged)"
   101  	case ComponentNameRedirector:
   102  		return "Redirector (privileged)"
   103  	}
   104  	return "Unknown"
   105  }
   106  
   107  // ComponentNameFromString returns ComponentName from a string
   108  func ComponentNameFromString(s string) ComponentName {
   109  	switch s {
   110  	case string(ComponentNameCLI):
   111  		return ComponentNameCLI
   112  	case string(ComponentNameService):
   113  		return ComponentNameService
   114  	case string(ComponentNameKBFS):
   115  		return ComponentNameKBFS
   116  	case string(ComponentNameKBNM):
   117  		return ComponentNameKBNM
   118  	case string(ComponentNameUpdater):
   119  		return ComponentNameUpdater
   120  	case string(ComponentNameApp):
   121  		return ComponentNameApp
   122  	case string(ComponentNameFuse):
   123  		return ComponentNameFuse
   124  	case string(ComponentNameHelper):
   125  		return ComponentNameHelper
   126  	case string(ComponentNameCLIPaths):
   127  		return ComponentNameCLIPaths
   128  	case string(ComponentNameRedirector):
   129  		return ComponentNameRedirector
   130  	}
   131  	return ComponentNameUnknown
   132  }
   133  
   134  // ResolveInstallStatus will determine necessary install actions for the current environment
   135  func ResolveInstallStatus(version string, bundleVersion string, lastExitStatus string, log Log) (installStatus keybase1.InstallStatus, installAction keybase1.InstallAction, status keybase1.Status) {
   136  	installStatus = keybase1.InstallStatus_UNKNOWN
   137  	installAction = keybase1.InstallAction_UNKNOWN
   138  	switch {
   139  	case version != "" && bundleVersion != "":
   140  		sv, err := semver.Make(version)
   141  		if err != nil {
   142  			installStatus = keybase1.InstallStatus_ERROR
   143  			installAction = keybase1.InstallAction_REINSTALL
   144  			status = keybase1.StatusFromCode(keybase1.StatusCode_SCInvalidVersionError, err.Error())
   145  			return
   146  		}
   147  		bsv, err := semver.Make(bundleVersion)
   148  		// Invalid bundle version
   149  		if err != nil {
   150  			installStatus = keybase1.InstallStatus_ERROR
   151  			installAction = keybase1.InstallAction_NONE
   152  			status = keybase1.StatusFromCode(keybase1.StatusCode_SCInvalidVersionError, err.Error())
   153  			return
   154  		}
   155  		switch {
   156  		case bsv.GT(sv):
   157  			installStatus = keybase1.InstallStatus_INSTALLED
   158  			installAction = keybase1.InstallAction_UPGRADE
   159  		case bsv.EQ(sv):
   160  			installStatus = keybase1.InstallStatus_INSTALLED
   161  			installAction = keybase1.InstallAction_NONE
   162  		case bsv.LT(sv):
   163  			// It's ok if we have a bundled version less than what was installed
   164  			log.Warning("Bundle version (%s) is less than installed version (%s)", bundleVersion, version)
   165  			installStatus = keybase1.InstallStatus_INSTALLED
   166  			installAction = keybase1.InstallAction_NONE
   167  		}
   168  	case version != "" && bundleVersion == "":
   169  		installStatus = keybase1.InstallStatus_INSTALLED
   170  	case version == "" && bundleVersion != "":
   171  		installStatus = keybase1.InstallStatus_NOT_INSTALLED
   172  		installAction = keybase1.InstallAction_INSTALL
   173  	}
   174  
   175  	// If we have an unknown install status, then let's try to re-install.
   176  	if bundleVersion != "" && installStatus == keybase1.InstallStatus_UNKNOWN && (version != "" || lastExitStatus != "") {
   177  		installAction = keybase1.InstallAction_REINSTALL
   178  		installStatus = keybase1.InstallStatus_INSTALLED
   179  	}
   180  
   181  	status = keybase1.StatusOK("")
   182  	return
   183  }
   184  
   185  // KBFSBundleVersion returns the bundle (not installed) version for KBFS
   186  func KBFSBundleVersion(context Context, binPath string) (string, error) {
   187  	runMode := context.GetRunMode()
   188  	kbfsBinPath, err := KBFSBinPath(runMode, binPath)
   189  	if err != nil {
   190  		return "", err
   191  	}
   192  
   193  	kbfsVersionOutput, err := exec.Command(kbfsBinPath, "--version").Output()
   194  	if err != nil {
   195  		return "", err
   196  	}
   197  	kbfsVersion := strings.TrimSpace(string(kbfsVersionOutput))
   198  	return kbfsVersion, nil
   199  }
   200  
   201  func defaultLinkPath() (string, error) { //nolint
   202  	if runtime.GOOS == "windows" {
   203  		return "", fmt.Errorf("Unsupported on Windows")
   204  	}
   205  	keybaseName, err := binName()
   206  	if err != nil {
   207  		return "", err
   208  	}
   209  	linkPath := filepath.Join("/usr/local/bin", keybaseName)
   210  	return linkPath, nil
   211  }
   212  
   213  func uninstallLink(linkPath string, log Log) error { //nolint
   214  	log.Debug("Link path: %s", linkPath)
   215  	fi, err := os.Lstat(linkPath)
   216  	if os.IsNotExist(err) {
   217  		log.Debug("Path doesn't exist: %s", linkPath)
   218  		return nil
   219  	}
   220  	isLink := (fi.Mode()&os.ModeSymlink != 0)
   221  	if !isLink {
   222  		return fmt.Errorf("Path is not a symlink: %s", linkPath)
   223  	}
   224  	log.Info("Removing %s", linkPath)
   225  	return os.Remove(linkPath)
   226  }
   227  
   228  func chooseBinPath(bp string) (string, error) {
   229  	if bp != "" {
   230  		return bp, nil
   231  	}
   232  	return BinPath()
   233  }
   234  
   235  // BinPath returns path to the keybase executable. If the executable path is a
   236  // symlink, the target path is returned.
   237  func BinPath() (string, error) {
   238  	return utils.BinPath()
   239  }
   240  
   241  func binName() (string, error) { //nolint
   242  	path, err := BinPath()
   243  	if err != nil {
   244  		return "", err
   245  	}
   246  	return filepath.Base(path), nil
   247  }
   248  
   249  // UpdaterBinPath returns the path to the updater executable, by default is in
   250  // the same directory as the keybase executable.
   251  func UpdaterBinPath() (string, error) {
   252  	path, err := BinPath()
   253  	if err != nil {
   254  		return "", err
   255  	}
   256  	name, err := updaterBinName()
   257  	if err != nil {
   258  		return "", err
   259  	}
   260  	return filepath.Join(filepath.Dir(path), name), nil
   261  }
   262  
   263  // kbfsBinPathDefault returns the default path to the KBFS executable.
   264  // If binPath (directory) is specified, it will override the default (which is in
   265  // the same directory where the keybase executable is).
   266  func kbfsBinPathDefault(runMode libkb.RunMode, binPath string) (string, error) {
   267  	path, err := chooseBinPath(binPath)
   268  	if err != nil {
   269  		return "", err
   270  	}
   271  	return filepath.Join(filepath.Dir(path), kbfsBinName()), nil
   272  }
   273  
   274  type CommonLsofResult struct {
   275  	PID     string
   276  	Command string
   277  }
   278  
   279  func fileContainsWord(filePath, searchWord string) bool {
   280  	file, err := os.Open(filePath)
   281  	if err != nil {
   282  		return false
   283  	}
   284  	defer file.Close()
   285  	scanner := bufio.NewScanner(file)
   286  	var lineCount int
   287  	for scanner.Scan() {
   288  		if strings.Contains(scanner.Text(), searchWord) {
   289  			return true
   290  		}
   291  		if lineCount >= 400 {
   292  			// if we haven't seen it yet, we won't
   293  			return false
   294  		}
   295  		lineCount++
   296  	}
   297  	return false
   298  }
   299  
   300  type fileWithPath struct {
   301  	os.FileInfo
   302  	Path string
   303  }
   304  
   305  func LastModifiedMatchingFile(filePattern string, fileContentMatch string) (filePath *string, err error) {
   306  	// find all paths that match the pattern
   307  	allFiles, err := filepath.Glob(filePattern)
   308  	if err != nil {
   309  		return nil, err
   310  	}
   311  	// loop through those file paths and get file info on each one
   312  	var fileObjects []fileWithPath
   313  	for _, path := range allFiles {
   314  		fileInfo, err := os.Stat(path)
   315  		if err != nil {
   316  			continue
   317  		}
   318  		fileObjects = append(fileObjects, fileWithPath{
   319  			FileInfo: fileInfo,
   320  			Path:     path,
   321  		})
   322  	}
   323  	// sort them by most recently modified
   324  	sort.Slice(fileObjects, func(i, j int) bool {
   325  		return fileObjects[i].ModTime().Unix() > fileObjects[j].ModTime().Unix()
   326  	})
   327  	// loop through and return the first one that matches for content
   328  	for idx, f := range fileObjects {
   329  		if idx >= 200 {
   330  			// we've looked at a lot of files and couldn't find one that's relevant, just bail.
   331  			return nil, nil
   332  		}
   333  		if fileContainsWord(f.Path, fileContentMatch) {
   334  			return &f.Path, nil
   335  		}
   336  	}
   337  	return nil, nil
   338  
   339  }