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 }