github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/install/install_darwin.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 "bytes" 8 "context" 9 "errors" 10 "fmt" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "strconv" 15 "strings" 16 "syscall" 17 "time" 18 19 "github.com/blang/semver" 20 "github.com/keybase/client/go/install/libnativeinstaller" 21 kbnminstaller "github.com/keybase/client/go/kbnm/installer" 22 "github.com/keybase/client/go/launchd" 23 "github.com/keybase/client/go/libkb" 24 "github.com/keybase/client/go/logger" 25 "github.com/keybase/client/go/mounter" 26 "github.com/keybase/client/go/protocol/keybase1" 27 ) 28 29 // defaultLaunchdWait is how long we should wait after install & start. 30 // We should make this shorter if the app is started by the user (so 31 // they get more immediate feedback), and longer if the app is started 32 // after boot (when it takes longer for things to start). 33 const defaultLaunchdWait = 20 * time.Second 34 35 // ServiceLabel is an identifier string for a service 36 type ServiceLabel string 37 38 const ( 39 // AppServiceLabel is the service label for the keybase launchd service in Keybase.app 40 AppServiceLabel ServiceLabel = "keybase.service" 41 // AppKBFSLabel is the service label for the kbfs launchd service in Keybase.app 42 AppKBFSLabel ServiceLabel = "keybase.kbfs" 43 // AppUpdaterLabel is the service label for the updater launchd service in Keybase.app 44 AppUpdaterLabel ServiceLabel = "keybase.updater" 45 // BrewServiceLabel is the service label for the updater launchd service in homebrew 46 BrewServiceLabel ServiceLabel = "homebrew.mxcl.keybase" 47 // BrewKBFSLabel is the service label for the kbfs launchd service in homebrew 48 BrewKBFSLabel ServiceLabel = "homebrew.mxcl.kbfs" 49 // UnknownLabel is an empty/unknown label 50 UnknownLabel ServiceLabel = "" 51 52 // See osx/Installer/Installer.m : KBExitAuthCanceledError 53 installHelperExitCodeAuthCanceled int = 6 54 // See osx/Installer/Installer.m : KBExitFuseCriticalUpdate 55 installHelperExitCodeFuseCriticalUpdate int = 8 56 // our own exit code 57 exitCodeFuseCriticalUpdateFailed int = 300 58 ) 59 60 // KeybaseServiceStatus returns service status for Keybase service 61 func KeybaseServiceStatus(context Context, label string, wait time.Duration, log Log) (status keybase1.ServiceStatus) { 62 if label == "" { 63 status = keybase1.ServiceStatus{Status: keybase1.StatusFromCode(keybase1.StatusCode_SCServiceStatusError, "No service label")} 64 return 65 } 66 kbService := launchd.NewService(label) 67 68 status, err := serviceStatusFromLaunchd(kbService, context.GetServiceInfoPath(), wait, log) 69 status.BundleVersion = libkb.VersionString() 70 if err != nil { 71 return 72 } 73 if status.InstallStatus == keybase1.InstallStatus_NOT_INSTALLED { 74 return 75 } 76 77 installStatus, installAction, kbStatus := ResolveInstallStatus(status.Version, status.BundleVersion, status.LastExitStatus, log) 78 status.InstallStatus = installStatus 79 status.InstallAction = installAction 80 status.Status = kbStatus 81 return 82 } 83 84 // KBFSServiceStatus returns service status for KBFS 85 func KBFSServiceStatus(context Context, label string, wait time.Duration, log Log) (status keybase1.ServiceStatus) { 86 if label == "" { 87 status = keybase1.ServiceStatus{Status: keybase1.StatusFromCode(keybase1.StatusCode_SCServiceStatusError, "No service label")} 88 return 89 } 90 kbfsService := launchd.NewService(label) 91 92 status, err := serviceStatusFromLaunchd(kbfsService, context.GetKBFSInfoPath(), wait, log) 93 if err != nil { 94 return 95 } 96 bundleVersion, err := KBFSBundleVersion(context, "") 97 if err != nil { 98 status.Status = keybase1.StatusFromCode(keybase1.StatusCode_SCServiceStatusError, err.Error()) 99 return 100 } 101 status.BundleVersion = bundleVersion 102 if status.InstallStatus == keybase1.InstallStatus_NOT_INSTALLED { 103 return 104 } 105 106 installStatus, installAction, kbStatus := ResolveInstallStatus(status.Version, status.BundleVersion, status.LastExitStatus, log) 107 status.InstallStatus = installStatus 108 status.InstallAction = installAction 109 status.Status = kbStatus 110 return 111 } 112 113 // UpdaterServiceStatus returns service status for the Updater service 114 func UpdaterServiceStatus(context Context, label string) keybase1.ServiceStatus { 115 if label == "" { 116 return keybase1.ServiceStatus{Status: keybase1.StatusFromCode(keybase1.StatusCode_SCServiceStatusError, "No service label")} 117 } 118 serviceStatus := keybase1.ServiceStatus{Label: label} 119 updaterService := launchd.NewService(label) 120 status, err := updaterService.WaitForStatus(defaultLaunchdWait, 500*time.Millisecond) 121 if err != nil { 122 serviceStatus.Status = keybase1.StatusFromCode(keybase1.StatusCode_SCServiceStatusError, err.Error()) 123 return serviceStatus 124 } 125 if status != nil { 126 serviceStatus.Pid = status.Pid() 127 serviceStatus.LastExitStatus = status.LastExitStatus() 128 } 129 if serviceStatus.Pid != "" { 130 serviceStatus.InstallStatus = keybase1.InstallStatus_INSTALLED 131 serviceStatus.InstallAction = keybase1.InstallAction_NONE 132 } else { 133 serviceStatus.InstallStatus = keybase1.InstallStatus_NOT_INSTALLED 134 serviceStatus.InstallAction = keybase1.InstallAction_INSTALL 135 } 136 serviceStatus.Status = keybase1.StatusOK("") 137 return serviceStatus 138 } 139 140 func serviceStatusFromLaunchd(ls launchd.Service, infoPath string, wait time.Duration, log Log) (status keybase1.ServiceStatus, err error) { 141 status = keybase1.ServiceStatus{ 142 Label: ls.Label(), 143 } 144 145 launchdStatus, err := ls.WaitForStatus(wait, 500*time.Millisecond) 146 if err != nil { 147 status.InstallStatus = keybase1.InstallStatus_ERROR 148 status.InstallAction = keybase1.InstallAction_NONE 149 status.Status = keybase1.StatusFromCode(keybase1.StatusCode_SCServiceStatusError, err.Error()) 150 return 151 } 152 153 if launchdStatus == nil { 154 status.InstallStatus = keybase1.InstallStatus_NOT_INSTALLED 155 status.InstallAction = keybase1.InstallAction_INSTALL 156 status.Status = keybase1.Status{Name: "OK"} 157 return 158 } 159 160 status.Label = launchdStatus.Label() 161 status.Pid = launchdStatus.Pid() 162 status.LastExitStatus = launchdStatus.LastExitStatus() 163 164 // Check service info file (if present) and if the service is running (has a PID) 165 var serviceInfo *libkb.ServiceInfo 166 if infoPath != "" { 167 if status.Pid != "" { 168 serviceInfo, err = libkb.WaitForServiceInfoFile(infoPath, status.Label, status.Pid, wait, log) 169 if err != nil { 170 status.InstallStatus = keybase1.InstallStatus_ERROR 171 status.InstallAction = keybase1.InstallAction_REINSTALL 172 status.Status = keybase1.StatusFromCode(keybase1.StatusCode_SCServiceStatusError, err.Error()) 173 return 174 } 175 } 176 if serviceInfo != nil { 177 status.Version = serviceInfo.Version 178 } 179 } 180 181 if status.Pid == "" { 182 status.InstallStatus = keybase1.InstallStatus_ERROR 183 status.InstallAction = keybase1.InstallAction_REINSTALL 184 err = fmt.Errorf("%s is not running", status.Label) 185 status.Status = keybase1.StatusFromCode(keybase1.StatusCode_SCServiceStatusError, err.Error()) 186 return 187 } 188 189 status.Status = keybase1.Status{Name: "OK"} 190 return 191 } 192 193 func serviceStatusesFromLaunchd(context Context, ls []launchd.Service, wait time.Duration, log Log) []keybase1.ServiceStatus { 194 c := []keybase1.ServiceStatus{} 195 for _, l := range ls { 196 s, _ := serviceStatusFromLaunchd(l, "", wait, log) 197 c = append(c, s) 198 } 199 return c 200 } 201 202 // ListServices returns status for all services 203 func ListServices(context Context, wait time.Duration, log Log) (*keybase1.ServicesStatus, error) { 204 services, err := launchd.ListServices([]string{"keybase.service", "homebrew.mxcl.keybase"}) 205 if err != nil { 206 return nil, err 207 } 208 kbfs, err := launchd.ListServices([]string{"keybase.kbfs.", "homebrew.mxcl.kbfs"}) 209 if err != nil { 210 return nil, err 211 } 212 updater, err := launchd.ListServices([]string{"keybase.updater."}) 213 if err != nil { 214 return nil, err 215 } 216 217 return &keybase1.ServicesStatus{ 218 Service: serviceStatusesFromLaunchd(context, services, wait, log), 219 Kbfs: serviceStatusesFromLaunchd(context, kbfs, wait, log), 220 Updater: serviceStatusesFromLaunchd(context, updater, wait, log), 221 }, nil 222 } 223 224 // DefaultLaunchdEnvVars returns default environment vars for launchd 225 func DefaultLaunchdEnvVars(label string) []launchd.EnvVar { 226 return []launchd.EnvVar{ 227 launchd.NewEnvVar("KEYBASE_LABEL", label), 228 launchd.NewEnvVar("KEYBASE_SERVICE_TYPE", "launchd"), 229 } 230 } 231 232 // DefaultServiceLabel returns the default label for Keybase service in launchd 233 func DefaultServiceLabel(runMode libkb.RunMode) string { 234 return defaultServiceLabel(runMode, libkb.IsBrewBuild) 235 } 236 237 func defaultServiceLabel(runMode libkb.RunMode, isBrew bool) string { 238 label := AppServiceLabel.String() 239 if isBrew { 240 label = BrewServiceLabel.String() 241 } 242 if runMode != libkb.ProductionRunMode { 243 label = label + "." + string(runMode) 244 } 245 return label 246 } 247 248 // DefaultKBFSLabel returns the default label for KBFS service in launchd 249 func DefaultKBFSLabel(runMode libkb.RunMode) string { 250 return defaultKBFSLabel(runMode, libkb.IsBrewBuild) 251 } 252 253 func defaultKBFSLabel(runMode libkb.RunMode, isBrew bool) string { 254 label := AppKBFSLabel.String() 255 if isBrew { 256 label = BrewKBFSLabel.String() 257 } 258 if runMode != libkb.ProductionRunMode { 259 label = label + "." + string(runMode) 260 } 261 return label 262 } 263 264 // DefaultUpdaterLabel returns the default label for the update service in launchd 265 func DefaultUpdaterLabel(runMode libkb.RunMode) string { 266 label := AppUpdaterLabel.String() 267 if runMode != libkb.ProductionRunMode { 268 label = label + "." + string(runMode) 269 } 270 return label 271 } 272 273 const defaultPlistComment = "It's not advisable to edit this plist, it may be overwritten" 274 275 func keybasePlist(context Context, binPath string, label string, log Log) (launchd.Plist, error) { 276 // TODO: Remove -d when doing real release 277 logFile := filepath.Join(context.GetLogDir(), libkb.ServiceLogFileName) 278 startLogFile := filepath.Join(context.GetLogDir(), libkb.StartLogFileName) 279 err := libkb.MakeParentDirs(log, startLogFile) 280 if err != nil { 281 return launchd.Plist{}, err 282 } 283 plistArgs := []string{"-d", fmt.Sprintf("--log-file=%s", logFile), "service"} 284 envVars := DefaultLaunchdEnvVars(label) 285 envVars = append(envVars, launchd.NewEnvVar("KEYBASE_RUN_MODE", string(context.GetRunMode()))) 286 return launchd.NewPlist(label, binPath, plistArgs, envVars, startLogFile, defaultPlistComment), nil 287 } 288 289 func installKeybaseService(context Context, service launchd.Service, plist launchd.Plist, wait time.Duration, log Log) (*keybase1.ServiceStatus, error) { 290 err := launchd.Install(plist, wait, log) 291 if err != nil { 292 log.Warning("error installing keybase service via launchd: %s", err) 293 return nil, err 294 } 295 296 st, err := serviceStatusFromLaunchd(service, context.GetServiceInfoPath(), wait, log) 297 return &st, err 298 } 299 300 // UninstallKeybaseServices removes the keybase service (includes homebrew) 301 func UninstallKeybaseServices(context Context, log Log) error { 302 runMode := context.GetRunMode() 303 err0 := fallbackKillProcess(context, log, defaultServiceLabel(runMode, false), context.GetServiceInfoPath(), "") 304 err1 := launchd.Uninstall(defaultServiceLabel(runMode, false), defaultLaunchdWait, log) 305 err2 := launchd.Uninstall(defaultServiceLabel(runMode, true), defaultLaunchdWait, log) 306 return libkb.CombineErrors(err0, err1, err2) 307 } 308 309 func kbfsPlist(context Context, kbfsBinPath string, label string, mountDir string, skipMount bool, log Log) (launchd.Plist, error) { 310 logFile := filepath.Join(context.GetLogDir(), libkb.KBFSLogFileName) 311 startLogFile := filepath.Join(context.GetLogDir(), libkb.StartLogFileName) 312 if err := libkb.MakeParentDirs(log, startLogFile); err != nil { 313 return launchd.Plist{}, err 314 } 315 // TODO: Remove debug flag when doing real release 316 plistArgs := []string{ 317 "-debug", 318 fmt.Sprintf("-log-file=%s", logFile), 319 fmt.Sprintf("-runtime-dir=%s", context.GetRuntimeDir()), 320 } 321 322 if context.GetRunMode() == libkb.DevelRunMode { 323 plistArgs = append(plistArgs, fmt.Sprintf("-server-root=%s", context.GetRuntimeDir())) 324 } 325 326 if skipMount { 327 plistArgs = append(plistArgs, "-mount-type=none") 328 } 329 330 plistArgs = append(plistArgs, mountDir) 331 332 envVars := DefaultLaunchdEnvVars(label) 333 envVars = append(envVars, launchd.NewEnvVar("KEYBASE_RUN_MODE", string(context.GetRunMode()))) 334 plist := launchd.NewPlist(label, kbfsBinPath, plistArgs, envVars, startLogFile, defaultPlistComment) 335 return plist, nil 336 } 337 338 func installKBFSService(context Context, service launchd.Service, plist launchd.Plist, wait time.Duration, log Log) (*keybase1.ServiceStatus, error) { 339 err := launchd.Install(plist, wait, log) 340 if err != nil { 341 log.Warning("error installing kbfs service via launchd: %s", err) 342 return nil, err 343 } 344 345 st, err := serviceStatusFromLaunchd(service, "", wait, log) 346 return &st, err 347 } 348 349 // UninstallKBFSServices removes KBFS service (including homebrew) 350 func UninstallKBFSServices(context Context, log Log) error { 351 runMode := context.GetRunMode() 352 err0 := fallbackKillProcess(context, log, defaultKBFSLabel(runMode, false), context.GetKBFSInfoPath(), "") 353 err1 := launchd.Uninstall(defaultKBFSLabel(runMode, false), defaultLaunchdWait, log) 354 err2 := launchd.Uninstall(defaultKBFSLabel(runMode, true), defaultLaunchdWait, log) 355 return libkb.CombineErrors(err0, err1, err2) 356 } 357 358 // NewServiceLabel constructs a service label 359 func NewServiceLabel(s string) (ServiceLabel, error) { 360 switch s { 361 case string(AppServiceLabel): 362 return AppServiceLabel, nil 363 case string(BrewServiceLabel): 364 return BrewServiceLabel, nil 365 case string(AppKBFSLabel): 366 return AppKBFSLabel, nil 367 case string(BrewKBFSLabel): 368 return BrewKBFSLabel, nil 369 case string(AppUpdaterLabel): 370 return AppUpdaterLabel, nil 371 } 372 return UnknownLabel, fmt.Errorf("Unknown service label: %s", s) 373 } 374 375 func (l ServiceLabel) String() string { 376 return string(l) 377 } 378 379 // ComponentName returns the component name for a service label 380 func (l ServiceLabel) ComponentName() ComponentName { 381 switch l { 382 case AppServiceLabel, BrewServiceLabel: 383 return ComponentNameService 384 case AppKBFSLabel, BrewKBFSLabel: 385 return ComponentNameKBFS 386 case AppUpdaterLabel: 387 return ComponentNameUpdater 388 } 389 return ComponentNameUnknown 390 } 391 392 // ServiceStatus returns status for a service named by label 393 func ServiceStatus(context Context, label ServiceLabel, wait time.Duration, log Log) (*keybase1.ServiceStatus, error) { 394 switch label.ComponentName() { 395 case ComponentNameService: 396 st := KeybaseServiceStatus(context, string(label), wait, log) 397 return &st, nil 398 case ComponentNameKBFS: 399 st := KBFSServiceStatus(context, string(label), wait, log) 400 return &st, nil 401 case ComponentNameUpdater: 402 st := UpdaterServiceStatus(context, string(label)) 403 return &st, nil 404 default: 405 return nil, fmt.Errorf("Invalid label: %s", label) 406 } 407 } 408 409 // InstallAuto installs everything it can without asking for privileges or 410 // extensions. If the user has already installed Fuse, we install everything. 411 func InstallAuto(context Context, binPath string, sourcePath string, timeout time.Duration, log Log) keybase1.InstallResult { 412 var components []string 413 status := KeybaseFuseStatus("", log) 414 if status.InstallStatus == keybase1.InstallStatus_INSTALLED { 415 components = []string{ 416 ComponentNameCLI.String(), 417 ComponentNameUpdater.String(), 418 ComponentNameService.String(), 419 ComponentNameKBFS.String(), 420 ComponentNameHelper.String(), 421 ComponentNameFuse.String(), 422 ComponentNameMountDir.String(), 423 ComponentNameRedirector.String(), 424 ComponentNameKBFS.String(), 425 ComponentNameKBNM.String(), 426 } 427 } else { 428 components = []string{ 429 ComponentNameCLI.String(), 430 ComponentNameUpdater.String(), 431 ComponentNameService.String(), 432 ComponentNameKBFS.String(), 433 ComponentNameKBNM.String(), 434 } 435 } 436 437 // A force unmount is needed to change from one mountpoint to another 438 // if the mount is in use after an upgrade, and install-auto is 439 // invoked from the updater. 440 forceUnmount := true 441 return Install(context, binPath, sourcePath, components, forceUnmount, timeout, log) 442 } 443 444 const mountsPresentErrorCode = 7 // See Installer/Installer.m 445 446 func installFuse(runMode libkb.RunMode, log Log) error { 447 err := libnativeinstaller.InstallFuse(runMode, log) 448 switch e := err.(type) { 449 case nil: 450 return nil 451 case (*exec.ExitError): 452 if waitStatus, ok := e.Sys().(syscall.WaitStatus); ok { 453 if waitStatus.ExitStatus() != mountsPresentErrorCode { 454 return err 455 } 456 // Otherwise, continue with the logic after switch. 457 } else { 458 return err 459 } 460 default: 461 return err 462 } 463 464 log.Info("Can't install/upgrade fuse when mounts are present. " + 465 "Assuming it's the redirector and trying to uninstall it first.") 466 if err = libnativeinstaller.UninstallRedirector(runMode, log); err != nil { 467 log.Info("Uninstalling redirector failed. " + 468 "Fuse should be able to update next time the OS reboots.") 469 return err 470 } 471 defer func() { 472 if err := libnativeinstaller.InstallRedirector(runMode, log); err != nil { 473 log.Info("Installing redirector failed. %s", err) 474 } 475 }() 476 log.Info( 477 "Uninstalling redirector succeeded. Trying to install KBFuse again.") 478 if err = libnativeinstaller.InstallFuse(runMode, log); err != nil { 479 log.Info("Installing fuse failed again. " + 480 "Fuse should be able to update next time the OS reboots.") 481 return err 482 } 483 return nil 484 } 485 486 // Install installs specified components 487 func Install(context Context, binPath string, sourcePath string, components []string, force bool, timeout time.Duration, log Log) keybase1.InstallResult { 488 var err error 489 componentResults := []keybase1.ComponentResult{} 490 491 log.Debug("Installing components: %s", components) 492 493 if libkb.IsIn(string(ComponentNameCLI), components, false) { 494 err = installCommandLine(context, binPath, true, log) // Always force CLI install 495 componentResults = append(componentResults, componentResult(string(ComponentNameCLI), err)) 496 if err != nil { 497 log.Errorf("Error installing CLI: %s", err) 498 } 499 } 500 501 if libkb.IsIn(string(ComponentNameApp), components, false) { 502 err = libnativeinstaller.InstallAppBundle(context, sourcePath, log) 503 componentResults = append(componentResults, componentResult(string(ComponentNameApp), err)) 504 if err != nil { 505 log.Errorf("Error installing app bundle: %s", err) 506 } 507 } 508 509 if libkb.IsIn(string(ComponentNameUpdater), components, false) { 510 err = InstallUpdater(context, binPath, force, timeout, log) 511 componentResults = append(componentResults, componentResult(string(ComponentNameUpdater), err)) 512 if err != nil { 513 log.Errorf("Error installing updater: %s", err) 514 } 515 } 516 517 if libkb.IsIn(string(ComponentNameService), components, false) { 518 err = InstallService(context, binPath, force, timeout, log) 519 componentResults = append(componentResults, componentResult(string(ComponentNameService), err)) 520 if err != nil { 521 log.Errorf("Error installing service: %s", err) 522 } 523 } 524 525 helperCanceled := false 526 if libkb.IsIn(string(ComponentNameHelper), components, false) { 527 err = libnativeinstaller.InstallHelper(context.GetRunMode(), log) 528 cr := componentResult(string(ComponentNameHelper), err) 529 componentResults = append(componentResults, cr) 530 if err != nil { 531 log.Errorf("Error installing Helper: %v", err) 532 } 533 shouldUninstallKBFS := false 534 shouldUninstallHelper := false 535 switch cr.ExitCode { 536 case installHelperExitCodeAuthCanceled: 537 log.Debug("Auth canceled; uninstalling mountdir and fuse") 538 helperCanceled = true 539 shouldUninstallKBFS = true 540 case installHelperExitCodeFuseCriticalUpdate: 541 log.Debug("FUSE critical update; uninstalling mountdir and fuse") 542 shouldUninstallKBFS = true 543 helperCanceled = true 544 shouldUninstallHelper = true 545 } 546 if shouldUninstallKBFS { 547 // Unmount the user's KBFS directory. 548 mountDir, err := context.GetMountDir() 549 if err == nil { 550 err = UninstallKBFS(context, mountDir, true, log) 551 } 552 if err != nil { 553 log.Errorf("Error uninstalling KBFS: %s", err) 554 } 555 556 // For older systems, check `/keybase` too, just in case. 557 var oldMountDir string 558 switch context.GetRunMode() { 559 case libkb.ProductionRunMode: 560 oldMountDir = "/keybase" 561 case libkb.StagingRunMode: 562 oldMountDir = "/keybase.staging" 563 default: 564 oldMountDir = "/keybase.devel" 565 } 566 err = unmount(oldMountDir, true, log) 567 if err != nil { 568 log.Debug("Error unmounting old mount dir %s: %v", oldMountDir, 569 err) 570 } 571 572 err = libnativeinstaller.UninstallMountDir( 573 context.GetRunMode(), log) 574 if err != nil { 575 log.Errorf("Error uninstalling mount directory: %s", err) 576 } 577 578 err = libnativeinstaller.UninstallRedirector( 579 context.GetRunMode(), log) 580 if err != nil { 581 log.Errorf("Error stopping redirector: %s", err) 582 } 583 584 err = libnativeinstaller.UninstallFuse(context.GetRunMode(), log) 585 if err != nil { 586 log.Errorf("Error uninstalling FUSE: %s", err) 587 if shouldUninstallKBFS { 588 log.Errorf("Returning critical update failure result since FUSE uninstall failed") 589 return newInstallResult([]keybase1.ComponentResult{{ 590 Name: "helper", 591 Status: keybase1.StatusFromCode(keybase1.StatusCode_SCGeneric, 592 "FUSE uninstall failed"), 593 ExitCode: exitCodeFuseCriticalUpdateFailed, 594 }}) 595 } 596 } 597 } 598 if shouldUninstallHelper { 599 err = libnativeinstaller.UninstallHelper(context.GetRunMode(), log) 600 if err != nil { 601 log.Errorf("Error uninstalling helper: %s", err) 602 } 603 } 604 } 605 606 if !helperCanceled && 607 libkb.IsIn(string(ComponentNameFuse), components, false) { 608 err = installFuse(context.GetRunMode(), log) 609 componentResults = append(componentResults, componentResult(string(ComponentNameFuse), err)) 610 if err != nil { 611 log.Errorf("Error installing KBFuse: %s", err) 612 } 613 } 614 615 if !helperCanceled && 616 libkb.IsIn(string(ComponentNameMountDir), components, false) { 617 err = libnativeinstaller.InstallMountDir(context.GetRunMode(), log) 618 componentResults = append(componentResults, componentResult(string(ComponentNameMountDir), err)) 619 if err != nil { 620 log.Errorf("Error installing mount directory: %s", err) 621 } 622 } 623 624 if libkb.IsIn(string(ComponentNameKBFS), components, false) { 625 err = InstallKBFS(context, binPath, force, true, timeout, log) 626 componentResults = append(componentResults, componentResult(string(ComponentNameKBFS), err)) 627 if err != nil { 628 log.Errorf("Error installing KBFS: %s", err) 629 } 630 } 631 632 if !helperCanceled && 633 libkb.IsIn(string(ComponentNameRedirector), components, false) { 634 err = libnativeinstaller.InstallRedirector(context.GetRunMode(), log) 635 componentResults = append(componentResults, componentResult(string(ComponentNameRedirector), err)) 636 if err != nil { 637 log.Errorf("Error starting redirector: %s", err) 638 } 639 } 640 641 if libkb.IsIn(string(ComponentNameKBNM), components, false) { 642 err = InstallKBNM(context, binPath, log) 643 componentResults = append(componentResults, componentResult(string(ComponentNameKBNM), err)) 644 if err != nil { 645 log.Errorf("Error installing KBNM: %s", err) 646 } 647 } 648 649 if libkb.IsIn(string(ComponentNameCLIPaths), components, false) { 650 err = libnativeinstaller.InstallCommandLinePrivileged(context.GetRunMode(), log) 651 componentResults = append(componentResults, componentResult(string(ComponentNameCLIPaths), err)) 652 if err != nil { 653 log.Errorf("Error installing command line (privileged): %s", err) 654 } 655 } 656 657 return newInstallResult(componentResults) 658 } 659 660 func installCommandLine(context Context, binPath string, force bool, log Log) error { 661 bp, err := chooseBinPath(binPath) 662 if err != nil { 663 return err 664 } 665 linkPath, err := defaultLinkPath() 666 if err != nil { 667 return err 668 } 669 if linkPath == bp { 670 return fmt.Errorf("We can't symlink to ourselves: %s", bp) 671 } 672 log.Info("Checking %s (%s)", linkPath, bp) 673 err = installCommandLineForBinPath(bp, linkPath, force, log) 674 if err != nil { 675 log.Errorf("Command line not installed properly (%s)", err) 676 return err 677 } 678 679 // Now the git remote helper. Lives next to the keybase binary, same dir 680 gitBinFilename := "git-remote-keybase" 681 gitBinPath := filepath.Join(filepath.Dir(bp), gitBinFilename) 682 gitLinkPath := filepath.Join(filepath.Dir(linkPath), gitBinFilename) 683 err = installCommandLineForBinPath(gitBinPath, gitLinkPath, force, log) 684 if err != nil { 685 log.Errorf("Git remote helper not installed properly (%s)", err) 686 return err 687 } 688 689 return nil 690 } 691 692 func createCommandLine(binPath string, linkPath string, log Log) error { 693 if _, err := os.Lstat(linkPath); err == nil { 694 err := os.Remove(linkPath) 695 if err != nil { 696 return err 697 } 698 } 699 700 log.Info("Linking %s to %s", linkPath, binPath) 701 return os.Symlink(binPath, linkPath) 702 } 703 704 func installCommandLineForBinPath(binPath string, linkPath string, force bool, log Log) error { 705 fi, err := os.Lstat(linkPath) 706 if os.IsNotExist(err) { 707 // Doesn't exist, create 708 return createCommandLine(binPath, linkPath, log) 709 } 710 isLink := (fi.Mode()&os.ModeSymlink != 0) 711 if !isLink { 712 if force { 713 log.Warning("Path is not a symlink: %s, forcing overwrite", linkPath) 714 return createCommandLine(binPath, linkPath, log) 715 } 716 return fmt.Errorf("Path is not a symlink: %s", linkPath) 717 } 718 719 // Check that the symlink evals to this binPath or error 720 dest, err := filepath.EvalSymlinks(linkPath) 721 if err == nil && binPath != dest { 722 err = fmt.Errorf("We are not symlinked to %s", linkPath) 723 } 724 if err != nil { 725 if force { 726 log.Warning("We are not symlinked to %s, forcing overwrite", linkPath) 727 return createCommandLine(binPath, linkPath, log) 728 } 729 return fmt.Errorf("We are not symlinked to %s", linkPath) 730 } 731 732 return nil 733 } 734 735 // InstallService installs the launchd service 736 func InstallService(context Context, binPath string, force bool, timeout time.Duration, log Log) error { 737 resolvedBinPath, err := chooseBinPath(binPath) 738 if err != nil { 739 return err 740 } 741 log.Debug("Using binPath: %s", resolvedBinPath) 742 743 label := DefaultServiceLabel(context.GetRunMode()) 744 service := launchd.NewService(label) 745 plist, err := keybasePlist(context, resolvedBinPath, label, log) 746 if err != nil { 747 return err 748 } 749 if err = UninstallKeybaseServices(context, log); err != nil { 750 log.Debug("unable to uninstall service: %s", err) 751 } 752 log.Debug("Installing service (%s, timeout=%s)", label, timeout) 753 if _, err := installKeybaseService(context, service, plist, timeout, log); err != nil { 754 log.Errorf("Error installing Keybase service: %s", err) 755 pid, err := fallbackStartProcessAndWaitForInfo(context, service, plist, context.GetServiceInfoPath(), timeout, log) 756 if err != nil { 757 return err 758 } 759 log.Debug("fallback keybase service started, pid=%d", pid) 760 return nil 761 } 762 log.Debug("keybase service installed via launchd successfully") 763 return nil 764 } 765 766 // InstallKBFS installs the KBFS launchd service 767 func InstallKBFS(context Context, binPath string, force bool, skipMountIfNotAvailable bool, timeout time.Duration, log Log) error { 768 runMode := context.GetRunMode() 769 label := DefaultKBFSLabel(runMode) 770 kbfsService := launchd.NewService(label) 771 kbfsBinPath, err := KBFSBinPath(runMode, binPath) 772 if err != nil { 773 return err 774 } 775 // Unmount any existing KBFS directory for the user. 776 mountDir, err := context.GetMountDir() 777 if err != nil { 778 return err 779 } 780 781 skipMount := false 782 _, err = os.Stat(mountDir) 783 if err != nil { 784 if skipMountIfNotAvailable { 785 skipMount = true 786 } else { 787 return err 788 } 789 } 790 791 plist, err := kbfsPlist(context, kbfsBinPath, label, mountDir, skipMount, log) 792 if err != nil { 793 return err 794 } 795 796 if err = UninstallKBFSServices(context, log); err != nil { 797 log.Debug("unable to uninstall kbfs %s", err) 798 } 799 log.Debug("Installing KBFS (%s, timeout=%s)", label, timeout) 800 if _, err := installKBFSService(context, kbfsService, plist, timeout, log); err != nil { 801 log.Errorf("error installing KBFS: %s", err) 802 pid, err := fallbackStartProcessAndWaitForInfo(context, kbfsService, plist, context.GetKBFSInfoPath(), timeout, log) 803 if err != nil { 804 return err 805 } 806 log.Debug("fallback KBFS service started, pid=%d", pid) 807 return nil 808 } 809 810 log.Debug("KBFS installed via launchd successfully") 811 return nil 812 } 813 814 func uninstallCommandLine(log Log) error { 815 linkPath, err := defaultLinkPath() 816 if err != nil { 817 return nil 818 } 819 820 err = uninstallLink(linkPath, log) 821 if err != nil { 822 return err 823 } 824 825 // Now the git binary. 826 gitBinFilename := "git-remote-keybase" 827 gitLinkPath := filepath.Join(filepath.Dir(linkPath), gitBinFilename) 828 return uninstallLink(gitLinkPath, log) 829 } 830 831 // InstallKBNM installs the Keybase NativeMessaging whitelist 832 func InstallKBNM(context Context, binPath string, log Log) error { 833 // Find path of the keybase binary 834 keybasePath, err := chooseBinPath(binPath) 835 if err != nil { 836 return err 837 } 838 // kbnm binary is next to the keybase binary, same dir 839 hostPath := filepath.Join(filepath.Dir(keybasePath), "kbnm") 840 841 log.Info("Installing KBNM NativeMessaging whitelists for binary: %s", hostPath) 842 return kbnminstaller.InstallKBNM(hostPath) 843 } 844 845 // UninstallKBNM removes the Keybase NativeMessaging whitelist 846 func UninstallKBNM(log Log) error { 847 log.Info("Uninstalling KBNM NativeMessaging whitelists") 848 return kbnminstaller.UninstallKBNM() 849 } 850 851 // Uninstall uninstalls all keybase services 852 func Uninstall(context Context, components []string, log Log) keybase1.UninstallResult { 853 var err error 854 componentResults := []keybase1.ComponentResult{} 855 856 log.Debug("Uninstalling components: %s", components) 857 858 if libkb.IsIn(string(ComponentNameRedirector), components, false) { 859 err = libnativeinstaller.UninstallRedirector(context.GetRunMode(), log) 860 componentResults = append(componentResults, componentResult(string(ComponentNameRedirector), err)) 861 if err != nil { 862 log.Errorf("Error stopping the redirector: %s", err) 863 } 864 } 865 866 if libkb.IsIn(string(ComponentNameKBFS), components, false) { 867 var mountDir string 868 mountDir, err = context.GetMountDir() 869 if err == nil { 870 err = UninstallKBFS(context, mountDir, true, log) 871 } 872 componentResults = append(componentResults, componentResult(string(ComponentNameKBFS), err)) 873 if err != nil { 874 log.Errorf("Error uninstalling KBFS: %s", err) 875 } 876 } 877 878 if libkb.IsIn(string(ComponentNameService), components, false) { 879 err = UninstallKeybaseServices(context, log) 880 componentResults = append(componentResults, componentResult(string(ComponentNameService), err)) 881 if err != nil { 882 log.Errorf("Error uninstalling service: %s", err) 883 } 884 } 885 886 if libkb.IsIn(string(ComponentNameUpdater), components, false) { 887 err = UninstallUpdaterService(context, log) 888 componentResults = append(componentResults, componentResult(string(ComponentNameUpdater), err)) 889 if err != nil { 890 log.Errorf("Error uninstalling updater: %s", err) 891 } 892 } 893 894 if libkb.IsIn(string(ComponentNameMountDir), components, false) { 895 err = libnativeinstaller.UninstallMountDir(context.GetRunMode(), log) 896 componentResults = append(componentResults, componentResult(string(ComponentNameMountDir), err)) 897 if err != nil { 898 log.Errorf("Error uninstalling mount dir: %s", err) 899 } 900 } 901 902 if libkb.IsIn(string(ComponentNameFuse), components, false) { 903 err = libnativeinstaller.UninstallFuse(context.GetRunMode(), log) 904 componentResults = append(componentResults, componentResult(string(ComponentNameFuse), err)) 905 if err != nil { 906 log.Errorf("Error uninstalling fuse: %s", err) 907 } 908 } 909 910 if libkb.IsIn(string(ComponentNameApp), components, false) { 911 err = libnativeinstaller.UninstallApp(context.GetRunMode(), log) 912 componentResults = append(componentResults, componentResult(string(ComponentNameApp), err)) 913 if err != nil { 914 log.Errorf("Error uninstalling app: %s", err) 915 } 916 } 917 918 if libkb.IsIn(string(ComponentNameKBNM), components, false) { 919 err = UninstallKBNM(log) 920 componentResults = append(componentResults, componentResult(string(ComponentNameKBNM), err)) 921 if err != nil { 922 log.Errorf("Error uninstalling kbnm: %s", err) 923 } 924 } 925 926 if libkb.IsIn(string(ComponentNameCLIPaths), components, false) { 927 err = libnativeinstaller.UninstallCommandLinePrivileged(context.GetRunMode(), log) 928 componentResults = append(componentResults, componentResult(string(ComponentNameCLIPaths), err)) 929 if err != nil { 930 log.Errorf("Error uninstalling command line (privileged): %s", err) 931 } 932 } 933 934 if libkb.IsIn(string(ComponentNameHelper), components, false) { 935 err = libnativeinstaller.UninstallHelper(context.GetRunMode(), log) 936 componentResults = append(componentResults, componentResult(string(ComponentNameHelper), err)) 937 if err != nil { 938 log.Errorf("Error uninstalling helper: %s", err) 939 } 940 } 941 942 if libkb.IsIn(string(ComponentNameCLI), components, false) { 943 err = uninstallCommandLine(log) 944 componentResults = append(componentResults, componentResult(string(ComponentNameCLI), err)) 945 if err != nil { 946 log.Errorf("Error uninstalling command line: %s", err) 947 } 948 } 949 950 return newUninstallResult(componentResults) 951 } 952 953 // UninstallKBFSOnStop removes KBFS services and unmounts and removes /keybase from the system 954 func UninstallKBFSOnStop(context Context, log Log) error { 955 runMode := context.GetRunMode() 956 mountDir, err := context.GetMountDir() 957 if err != nil { 958 return err 959 } 960 log.Info("UninstallKBFSOnStop: uninstalling from mountdir: %s", mountDir) 961 962 if err := UninstallKBFS(context, mountDir, false, log); err != nil { 963 return err 964 } 965 966 log.Info("Uninstalled mount: %s", mountDir) 967 if err := libnativeinstaller.UninstallMountDir(runMode, log); err != nil { 968 return fmt.Errorf("Error uninstalling mount: %s", err) 969 } 970 971 return nil 972 } 973 974 func unmount(mountDir string, forceUnmount bool, log Log) error { 975 log.Debug("Checking if mounted: %s", mountDir) 976 if _, serr := os.Stat(mountDir); os.IsNotExist(serr) { 977 return nil 978 } 979 980 mounted, err := mounter.IsMounted(mountDir, log) 981 if err != nil { 982 return err 983 } 984 log.Debug("Mounted: %s", strconv.FormatBool(mounted)) 985 if mounted { 986 err = mounter.Unmount(mountDir, forceUnmount, log) 987 if err != nil { 988 return err 989 } 990 } 991 empty, err := libkb.IsDirEmpty(mountDir) 992 if err != nil { 993 return err 994 } 995 if !empty { 996 return fmt.Errorf("Mount has files after unmounting: %s", mountDir) 997 } 998 return nil 999 } 1000 1001 // UninstallKBFS uninstalls all KBFS services, unmounts and optionally removes the mount directory 1002 func UninstallKBFS(context Context, mountDir string, forceUnmount bool, log Log) error { 1003 err := UninstallKBFSServices(context, log) 1004 if err != nil { 1005 log.Warning("Couldn't stop KBFS: %+v", err) 1006 // Continue despite the error, since the uninstall doesn't 1007 // seem to be resilient against the "fallback" PID getting out 1008 // of sync with the true KBFS PID. TODO: fix the fallback PID 1009 // logic? 1010 } 1011 1012 return unmount(mountDir, forceUnmount, log) 1013 } 1014 1015 // AutoInstallWithStatus runs the auto install and returns a result 1016 func AutoInstallWithStatus(context Context, binPath string, force bool, timeout time.Duration, log Log) keybase1.InstallResult { 1017 _, res, err := autoInstall(context, binPath, force, timeout, log) 1018 if err != nil { 1019 return keybase1.InstallResult{Status: keybase1.StatusFromCode(keybase1.StatusCode_SCInstallError, err.Error())} 1020 } 1021 return newInstallResult(res) 1022 } 1023 1024 // AutoInstall runs the auto install 1025 func AutoInstall(context Context, binPath string, force bool, timeout time.Duration, log Log) (newProc bool, err error) { 1026 if context.GetRunMode() != libkb.ProductionRunMode { 1027 return false, fmt.Errorf("Auto install is only supported in production") 1028 } 1029 1030 newProc, _, err = autoInstall(context, binPath, force, timeout, log) 1031 return 1032 } 1033 1034 func autoInstall(context Context, binPath string, force bool, timeout time.Duration, log Log) (newProc bool, componentResults []keybase1.ComponentResult, err error) { 1035 log.Debug("+ AutoInstall for launchd") 1036 defer func() { 1037 log.Debug("- AutoInstall -> %v, %v", newProc, err) 1038 }() 1039 label := DefaultServiceLabel(context.GetRunMode()) 1040 if label == "" { 1041 err = fmt.Errorf("No service label to install") 1042 return 1043 } 1044 resolvedBinPath, err := chooseBinPath(binPath) 1045 if err != nil { 1046 return 1047 } 1048 log.Debug("Using binPath: %s", resolvedBinPath) 1049 1050 service := launchd.NewService(label) 1051 plist, err := keybasePlist(context, resolvedBinPath, label, log) 1052 if err != nil { 1053 return 1054 } 1055 1056 // Check if plist is valid. If so we're already installed and return. 1057 plistValid, err := service.CheckPlist(plist) 1058 if err != nil || plistValid { 1059 return 1060 } 1061 1062 err = InstallService(context, binPath, true, timeout, log) 1063 componentResults = append(componentResults, componentResult(string(ComponentNameService), err)) 1064 if err != nil { 1065 return 1066 } 1067 1068 newProc = true 1069 return 1070 } 1071 1072 // CheckIfValidLocation checks if the current environment is running from a valid location. 1073 // For example, this will return an error if this isn't running from /Applications/Keybase.app on MacOS. 1074 func CheckIfValidLocation() *keybase1.Error { 1075 keybasePath, err := BinPath() 1076 if err != nil { 1077 return keybase1.FromError(err) 1078 } 1079 inDMG, _, err := isPathInDMG(keybasePath) 1080 if err != nil { 1081 return keybase1.FromError(err) 1082 } 1083 if inDMG { 1084 return keybase1.NewError(keybase1.StatusCode_SCInvalidLocationError, "You should copy Keybase to /Applications before running.") 1085 } 1086 return nil 1087 } 1088 1089 // isPathInDMG errors if the path is inside dmg 1090 func isPathInDMG(p string) (inDMG bool, bundlePath string, err error) { 1091 var stat syscall.Statfs_t 1092 err = syscall.Statfs(p, &stat) 1093 if err != nil { 1094 return 1095 } 1096 1097 // mntRootFS identifies the root filesystem (http://www.opensource.apple.com/source/xnu/xnu-344.26/bsd/sys/mount.h) 1098 const mntRootFS = 0x00004000 1099 1100 if (stat.Flags & mntRootFS) != 0 { 1101 // We're on the root filesystem so we're not in a DMG 1102 return 1103 } 1104 1105 bundlePath = bundleDirForPath(p) 1106 if bundlePath != "" { 1107 // Look for Applications symlink in the same folder as Keybase.app, and if 1108 // we find it, we're really likely to be in a mounted dmg 1109 appLink := filepath.Join(filepath.Dir(bundlePath), "Applications") 1110 fi, ferr := os.Lstat(appLink) 1111 if os.IsNotExist(ferr) { 1112 return 1113 } 1114 isLink := (fi.Mode()&os.ModeSymlink != 0) 1115 if isLink { 1116 inDMG = true 1117 return 1118 } 1119 } 1120 1121 return 1122 } 1123 1124 func bundleDirForPath(p string) string { 1125 paths := libkb.SplitPath(p) 1126 pathJoined := "" 1127 if strings.HasPrefix(p, "/") { 1128 pathJoined = "/" 1129 } 1130 found := false 1131 for _, sp := range paths { 1132 pathJoined = filepath.Join(pathJoined, sp) 1133 if sp == "Keybase.app" { 1134 found = true 1135 break 1136 } 1137 } 1138 if !found { 1139 return "" 1140 } 1141 return filepath.Clean(pathJoined) 1142 } 1143 1144 func newInstallResult(componentResults []keybase1.ComponentResult) keybase1.InstallResult { 1145 return keybase1.InstallResult{ComponentResults: componentResults, Status: statusFromResults(componentResults)} 1146 } 1147 1148 func newUninstallResult(componentResults []keybase1.ComponentResult) keybase1.UninstallResult { 1149 return keybase1.UninstallResult{ComponentResults: componentResults, Status: statusFromResults(componentResults)} 1150 } 1151 1152 func statusFromResults(componentResults []keybase1.ComponentResult) keybase1.Status { 1153 var errorMessages []string 1154 for _, cs := range componentResults { 1155 if cs.Status.Code != 0 { 1156 errorMessages = append(errorMessages, fmt.Sprintf("%s (%s)", cs.Status.Desc, cs.Name)) 1157 } 1158 } 1159 1160 if len(errorMessages) > 0 { 1161 return keybase1.StatusFromCode(keybase1.StatusCode_SCInstallError, strings.Join(errorMessages, ". ")) 1162 } 1163 1164 return keybase1.StatusOK("") 1165 } 1166 1167 func componentResult(name string, err error) keybase1.ComponentResult { 1168 if err != nil { 1169 exitCode := 0 1170 if exitError, ok := err.(*exec.ExitError); ok { 1171 ws := exitError.Sys().(syscall.WaitStatus) 1172 exitCode = ws.ExitStatus() 1173 } 1174 return keybase1.ComponentResult{Name: name, Status: keybase1.StatusFromCode(keybase1.StatusCode_SCInstallError, err.Error()), ExitCode: exitCode} 1175 } 1176 return keybase1.ComponentResult{Name: name, Status: keybase1.StatusOK("")} 1177 } 1178 1179 // KBFSBinPath returns the path to the KBFS executable. 1180 // If binPath (directory) is specified, it will override the default (which is in 1181 // the same directory where the keybase executable is). 1182 func KBFSBinPath(runMode libkb.RunMode, binPath string) (string, error) { 1183 // If it's brew lookup path by formula name 1184 if libkb.IsBrewBuild { 1185 if runMode != libkb.ProductionRunMode { 1186 return "", fmt.Errorf("Not supported in this run mode") 1187 } 1188 kbfsBinName := kbfsBinName() 1189 prefix, err := brewPath(kbfsBinName) 1190 if err != nil { 1191 return "", err 1192 } 1193 return filepath.Join(prefix, "bin", kbfsBinName), nil 1194 } 1195 1196 return kbfsBinPathDefault(runMode, binPath) 1197 } 1198 1199 func brewPath(formula string) (string, error) { 1200 // Get the homebrew install path prefix for this formula 1201 prefixOutput, err := exec.Command("brew", "--prefix", formula).Output() 1202 if err != nil { 1203 return "", fmt.Errorf("Error checking brew path: %s", err) 1204 } 1205 prefix := strings.TrimSpace(string(prefixOutput)) 1206 return prefix, nil 1207 } 1208 1209 // OSVersion returns the OS version 1210 func OSVersion() (semver.Version, error) { 1211 out, err := exec.Command("sw_vers", "-productVersion").Output() 1212 if err != nil { 1213 return semver.Version{}, err 1214 } 1215 swver := strings.TrimSpace(string(out)) 1216 // The version might not be semver compliant for beta macOS (e.g. "10.12") 1217 if strings.Count(swver, ".") == 1 { 1218 swver += ".0" 1219 } 1220 return semver.Make(swver) 1221 } 1222 1223 // InstallUpdater installs the updater launchd service 1224 func InstallUpdater(context Context, keybaseBinPath string, force bool, timeout time.Duration, log Log) error { 1225 if context.GetRunMode() != libkb.ProductionRunMode { 1226 return fmt.Errorf("Updater not supported in this run mode") 1227 } 1228 keybaseBinPath, err := chooseBinPath(keybaseBinPath) 1229 if err != nil { 1230 return err 1231 } 1232 updaterBinPath := filepath.Join(filepath.Dir(keybaseBinPath), "updater") 1233 if err != nil { 1234 return err 1235 } 1236 log.Debug("Using updater path: %s", updaterBinPath) 1237 1238 label := DefaultUpdaterLabel(context.GetRunMode()) 1239 service := launchd.NewService(label) 1240 plist, err := updaterPlist(context, label, updaterBinPath, keybaseBinPath, log) 1241 if err != nil { 1242 return err 1243 } 1244 1245 if err = UninstallUpdaterService(context, log); err != nil { 1246 log.Debug("unable uninstall updater service: %s", err) 1247 } 1248 log.Debug("Installing updater service (%s, timeout=%s)", label, timeout) 1249 if _, err := installUpdaterService(context, service, plist, timeout, log); err != nil { 1250 log.Errorf("Error installing updater service: %s", err) 1251 _, err = fallbackStartProcess(context, service, plist, log) 1252 return err 1253 } 1254 return nil 1255 } 1256 1257 func updaterPlist(context Context, label string, serviceBinPath string, keybaseBinPath string, log Log) (launchd.Plist, error) { 1258 plistArgs := []string{fmt.Sprintf("-path-to-keybase=%s", keybaseBinPath)} 1259 envVars := DefaultLaunchdEnvVars(label) 1260 comment := "It's not advisable to edit this plist, it may be overwritten" 1261 logFile := filepath.Join(context.GetLogDir(), libkb.UpdaterLogFileName) 1262 err := libkb.MakeParentDirs(log, logFile) 1263 if err != nil { 1264 return launchd.Plist{}, err 1265 } 1266 return launchd.NewPlist(label, serviceBinPath, plistArgs, envVars, logFile, comment), nil 1267 } 1268 1269 func installUpdaterService(context Context, service launchd.Service, plist launchd.Plist, wait time.Duration, log Log) (*keybase1.ServiceStatus, error) { 1270 err := launchd.Install(plist, wait, log) 1271 if err != nil { 1272 log.Warning("error installing updater service via launchd: %s", err) 1273 return nil, err 1274 } 1275 1276 st, err := serviceStatusFromLaunchd(service, "", wait, log) 1277 return &st, err 1278 } 1279 1280 // UninstallUpdaterService removes updater launchd service 1281 func UninstallUpdaterService(context Context, log Log) error { 1282 runMode := context.GetRunMode() 1283 pidFile := filepath.Join(context.GetCacheDir(), "updater.pid") 1284 err0 := fallbackKillProcess(context, log, DefaultUpdaterLabel(runMode), "", pidFile) 1285 err1 := launchd.Uninstall(DefaultUpdaterLabel(runMode), defaultLaunchdWait, log) 1286 return libkb.CombineErrors(err0, err1) 1287 } 1288 1289 // kbfsBinName returns the name for the KBFS executable 1290 func kbfsBinName() string { 1291 return "kbfs" 1292 } 1293 1294 func updaterBinName() (string, error) { 1295 return "updater", nil 1296 } 1297 1298 // RunApp starts the app 1299 func RunApp(context Context, log Log) error { 1300 appPath, err := libnativeinstaller.AppBundleForPath() 1301 if err != nil { 1302 return err 1303 } 1304 ver, err := OSVersion() 1305 if err != nil { 1306 log.Errorf("Error trying to determine OS version: %s", err) 1307 return nil 1308 } 1309 if ver.LT(semver.MustParse("10.0.0")) { 1310 return fmt.Errorf("App isn't supported on this OS version: %s", ver) 1311 } 1312 1313 log.Info("Opening %s", appPath) 1314 // If app is already open this is a no-op, the -g option will cause to open 1315 // in background. 1316 out, err := exec.Command("/usr/bin/open", "-g", appPath).Output() 1317 if err != nil { 1318 return fmt.Errorf("Error trying to open %s: %s; %s", appPath, err, out) 1319 } 1320 return nil 1321 } 1322 1323 // InstallLogPath doesn't exist on darwin as an independent log file (see desktop app log) 1324 func InstallLogPath() (string, error) { 1325 return "", nil 1326 } 1327 1328 // WatchdogLogPath doesn't exist on darwin as an independent log file (see desktop app log) 1329 func WatchdogLogPath(string) (string, error) { 1330 return "", nil 1331 } 1332 1333 // SystemLogPath is where privileged keybase processes log to on darwin 1334 func SystemLogPath() string { 1335 return "/Library/Logs/keybase.system.log" 1336 } 1337 1338 func fallbackPIDFilename(service launchd.Service) string { 1339 return filepath.Join(os.TempDir(), "kbfb."+service.Label()) 1340 } 1341 1342 func fallbackStartProcessAndWaitForInfo(context Context, service launchd.Service, plist launchd.Plist, infoPath string, timeout time.Duration, log Log) (int, error) { 1343 pid, err := fallbackStartProcess(context, service, plist, log) 1344 if err != nil { 1345 return 0, err 1346 } 1347 log.Debug("%s process started: %d, waiting for service info file %s to exist", service.Label(), pid, context.GetServiceInfoPath()) 1348 spid := strconv.Itoa(pid) 1349 _, err = libkb.WaitForServiceInfoFile(infoPath, service.Label(), spid, timeout, log) 1350 if err != nil { 1351 log.Warning("error waiting for %s info file %s: %s", service.Label(), infoPath, err) 1352 return 0, err 1353 } 1354 log.Debug("%s info file %s exists, fallback service start worked", service.Label(), infoPath) 1355 return pid, nil 1356 } 1357 1358 func fallbackStartProcess(context Context, service launchd.Service, plist launchd.Plist, log Log) (int, error) { 1359 log.Info("falling back to starting %s process manually", service.Label()) 1360 1361 cmd := plist.FallbackCommand() 1362 log.Info("fallback command: %s %v (env: %v)", cmd.Path, cmd.Args, cmd.Env) 1363 err := cmd.Start() 1364 if err != nil { 1365 log.Warning("error starting fallback command for %s (%s): %s", service.Label(), cmd.Path, err) 1366 return 0, err 1367 } 1368 1369 if cmd.Process == nil { 1370 log.Warning("no process after starting command %s", cmd.Path) 1371 return 0, fmt.Errorf("failed to start %s (%s)", service.Label(), cmd.Path) 1372 } 1373 1374 log.Info("fallback command started: %s, pid = %d", cmd.Path, cmd.Process.Pid) 1375 1376 // save pid in a fallback file so uninstall can check 1377 f, err := os.Create(fallbackPIDFilename(service)) 1378 if err != nil { 1379 log.Warning("failed to create fallback pid file %s: %s", fallbackPIDFilename(service), err) 1380 } else { 1381 if _, err := f.Write([]byte(fmt.Sprintf("%d", cmd.Process.Pid))); err != nil { 1382 log.Warning("failed to write to fallback pid file %s: %s", fallbackPIDFilename(service), err) 1383 } 1384 f.Close() 1385 } 1386 1387 return cmd.Process.Pid, nil 1388 } 1389 1390 func fallbackKillProcess(context Context, log Log, label string, infoPath, pidPath string) error { 1391 svc := launchd.NewService(label) 1392 svc.SetLogger(log) 1393 1394 fpid := fallbackPIDFilename(svc) 1395 1396 exists, err := libkb.FileExists(fpid) 1397 if err != nil { 1398 return err 1399 } 1400 if !exists { 1401 log.Debug("no fallback pid file exists for %s (%s)", svc.Label(), fpid) 1402 return nil 1403 } 1404 1405 log.Debug("fallback pid file exists for %s", svc.Label()) 1406 p, err := os.ReadFile(fpid) 1407 if err != nil { 1408 return err 1409 } 1410 pid := string(bytes.TrimSpace(p)) 1411 1412 found := false 1413 if infoPath != "" { 1414 serviceInfo, err := libkb.LoadServiceInfo(infoPath) 1415 if err != nil { 1416 log.Warning("error loading service info for %s in file %s: %s", svc.Label(), infoPath, err) 1417 return err 1418 } 1419 if serviceInfo != nil { 1420 if strconv.Itoa(serviceInfo.Pid) != pid { 1421 log.Warning("service info pid %d does not match fallback pid %s, not killing anything", serviceInfo.Pid, pid) 1422 return errors.New("fallback PID mismatch") 1423 } 1424 found = true 1425 } 1426 } 1427 1428 if !found && pidPath != "" { 1429 lp, err := os.ReadFile(pidPath) 1430 if err != nil { 1431 return err 1432 } 1433 lpid := string(bytes.TrimSpace(lp)) 1434 if lpid != pid { 1435 log.Warning("pid in file %s (%d) does not match fallback pid %s, not killing anything", pidPath, lpid, pid) 1436 return errors.New("fallback PID mismatch") 1437 } 1438 found = true 1439 } 1440 1441 if !found { 1442 log.Warning("neither infoPath or pidPath specified, cannot verify fallback PID.") 1443 return errors.New("unable to verify fallback PID") 1444 } 1445 1446 log.Debug("stopping process %s for %s", pid, svc.Label()) 1447 cmd := exec.Command("kill", pid) 1448 out, err := cmd.CombinedOutput() 1449 if err != nil { 1450 log.Warning("error stopping process %s for %s: %s", pid, svc.Label(), err) 1451 log.Debug("command output: %s", out) 1452 return err 1453 } 1454 log.Debug("process %s for %s stopped", pid, svc.Label()) 1455 if err := os.Remove(fpid); err != nil { 1456 log.Warning("error removing fallback pid file %s: %s", fpid, err) 1457 return err 1458 } 1459 log.Debug("fallback pid file %s for %s removed", fpid, svc.Label()) 1460 1461 return nil 1462 } 1463 1464 // StartUpdateIfNeeded starts to update the app if there's one available. It 1465 // calls `updater check` internally so it ignores the snooze. 1466 func StartUpdateIfNeeded(ctx context.Context, log logger.Logger) error { 1467 updaterPath, err := UpdaterBinPath() 1468 if err != nil { 1469 return err 1470 } 1471 cmd := exec.Command(updaterPath, "check") 1472 // Run it in a new process group so when we are killed eventually by the 1473 // updater, we don't bring down the updater too. 1474 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} 1475 if err = cmd.Start(); err != nil { 1476 return err 1477 } 1478 pid := -1 1479 if cmd.Process != nil { 1480 pid = cmd.Process.Pid 1481 } 1482 log.Debug("Started background updater process (%s). pid=%d", updaterPath, pid) 1483 if err = cmd.Wait(); err != nil { 1484 log.Debug("updater cmd failed: %s", err) 1485 } 1486 // Ignore the exit status here as user may have hit "Ignore". If we are 1487 // here without getting killed, it's likely user has hit "Ignore". Just 1488 // just return `nil` and GUI would check for update info again where it'd 1489 // know we don't need to update anymore. 1490 return nil 1491 }