/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/service/main.go
1 576 строк
49 KB
chrisnojima
unify video trim + compression across share extension and in-chat attach. misc share fixes (#29485)
06 авг 2026, 18:54
Не верифицирован
06 авг 2026, 18:54
400178f
Код
Авторство
О чём код?
// Copyright 2015 Keybase, Inc. All rights reserved. Use of // this source code is governed by the included BSD license. package service import ( "bufio" "context" "errors" "fmt" "io" "net" "os" "path/filepath" "runtime" "runtime/pprof" "runtime/trace" "sync" "time" "github.com/keybase/cli" "github.com/keybase/client/go/avatars" "github.com/keybase/client/go/badges" "github.com/keybase/client/go/chat" "github.com/keybase/client/go/chat/attachments" "github.com/keybase/client/go/chat/bots" "github.com/keybase/client/go/chat/commands" "github.com/keybase/client/go/chat/globals" "github.com/keybase/client/go/chat/maps" "github.com/keybase/client/go/chat/search" "github.com/keybase/client/go/chat/storage" "github.com/keybase/client/go/chat/types" "github.com/keybase/client/go/chat/unfurl" "github.com/keybase/client/go/chat/wallet" "github.com/keybase/client/go/contacts" "github.com/keybase/client/go/engine" "github.com/keybase/client/go/ephemeral" "github.com/keybase/client/go/externals" "github.com/keybase/client/go/gregor" "github.com/keybase/client/go/home" "github.com/keybase/client/go/kbhttp/manager" "github.com/keybase/client/go/libcmdline" "github.com/keybase/client/go/libkb" "github.com/keybase/client/go/offline" "github.com/keybase/client/go/protocol/chat1" gregor1 "github.com/keybase/client/go/protocol/gregor1" "github.com/keybase/client/go/protocol/keybase1" "github.com/keybase/client/go/protocol/stellar1" "github.com/keybase/client/go/pvl" "github.com/keybase/client/go/runtimestats" "github.com/keybase/client/go/stellar" "github.com/keybase/client/go/stellar/remote" "github.com/keybase/client/go/stellar/stellargregor" "github.com/keybase/client/go/systemd" "github.com/keybase/client/go/teambot" "github.com/keybase/client/go/teams" "github.com/keybase/client/go/tlfupgrade" "github.com/keybase/go-framed-msgpack-rpc/rpc" ) type Service struct { libkb.Contextified globals.ChatContextified isDaemon bool chdirTo string lockPid *libkb.LockPIDFile ForkType keybase1.ForkType startCh chan struct{} stopCh chan keybase1.ExitCode logForwarder *logFwd gregor *gregorHandler rekeyMaster *rekeyMaster badger *badges.Badger reachability *reachability home *home.Home tlfUpgrader *tlfupgrade.BackgroundTLFUpdater teamUpgrader *teams.Upgrader walletState *stellar.WalletState offlineRPCCache *offline.RPCCache trackerLoader *TrackerLoader httpSrv *manager.Srv avatarSrv *avatars.Srv referrerListener InstallReferrerListener // Android only loginAttemptMu sync.Mutex loginAttempt libkb.LoginAttempt loginSuccess bool oneshotUsername string oneshotPaperkey string // Closed after the first real startup login attempt (offline or online) // finishes, so RPCs that report login state (GetBootstrapStatus) can wait // for it instead of racing a login that runs off the Init path on mobile. initialLoginAttemptDone chan struct{} initialLoginAttemptOnce sync.Once } type Shutdowner interface { Shutdown() } func NewService(g *libkb.GlobalContext, isDaemon bool) *Service { chatG := globals.NewChatContextified(&globals.ChatContext{}) allG := globals.NewContext(g, chatG.ChatG()) return &Service{ Contextified: libkb.NewContextified(g), ChatContextified: chatG, isDaemon: isDaemon, startCh: make(chan struct{}), stopCh: make(chan keybase1.ExitCode), logForwarder: newLogFwd(), rekeyMaster: newRekeyMaster(g), badger: badges.NewBadger(g), gregor: newGregorHandler(allG), home: home.NewHome(g), tlfUpgrader: tlfupgrade.NewBackgroundTLFUpdater(g), trackerLoader: NewTrackerLoader(g), teamUpgrader: teams.NewUpgrader(), walletState: stellar.NewWalletState(g, remote.NewRemoteNet(g)), offlineRPCCache: offline.NewRPCCache(g), httpSrv: manager.NewSrv(g), initialLoginAttemptDone: make(chan struct{}), } } func (d *Service) GetStartChannel() <-chan struct{} { return d.startCh } func (d *Service) RegisterProtocols(srv *rpc.Server, xp rpc.Transporter, connID libkb.ConnectionID, logReg *logRegister) (err error) { g := d.G() cg := globals.NewContext(g, d.ChatG()) contactsProv := NewCachedContactsProvider(g) protocols := []rpc.Protocol{ keybase1.AccountProtocol(NewAccountHandler(xp, g)), keybase1.BTCProtocol(NewCryptocurrencyHandler(xp, g)), keybase1.CryptocurrencyProtocol(NewCryptocurrencyHandler(xp, g)), keybase1.ConfigProtocol(NewConfigHandler(xp, connID, g, d)), keybase1.CryptoProtocol(NewCryptoHandler(g)), keybase1.CtlProtocol(NewCtlHandler(xp, d, g)), keybase1.DelegateUiCtlProtocol(NewDelegateUICtlHandler(xp, connID, g, d.rekeyMaster)), keybase1.DeviceProtocol(NewDeviceHandler(xp, g, d.gregor)), keybase1.FavoriteProtocol(NewFavoriteHandler(xp, g)), keybase1.TlfProtocol(newTlfHandler(xp, cg)), keybase1.IdentifyProtocol(NewIdentifyHandler(xp, g, d)), keybase1.IncomingShareProtocol(NewIncomingShareHandler(xp, g)), keybase1.InstallProtocol(NewInstallHandler(xp, g)), keybase1.KbfsProtocol(NewKBFSHandler(xp, g, d.ChatG(), d)), keybase1.NotifySimpleFSProtocol(NewNotifySimpleFSHandler(xp, g, d.ChatG(), d)), keybase1.KbfsMountProtocol(NewKBFSMountHandler(xp, g)), keybase1.KvstoreProtocol(NewKVStoreHandler(xp, g)), keybase1.LogProtocol(NewLogHandler(xp, logReg, g)), keybase1.LoginProtocol(NewLoginHandler(xp, g)), keybase1.NotifyCtlProtocol(NewNotifyCtlHandler(xp, connID, g)), keybase1.PGPProtocol(NewPGPHandler(xp, connID, g)), keybase1.PprofProtocol(NewPprofHandler(xp, g)), keybase1.ReachabilityProtocol(newReachabilityHandler(xp, g, d)), keybase1.RevokeProtocol(NewRevokeHandler(xp, g)), keybase1.ProveProtocol(NewProveHandler(xp, g)), keybase1.SaltpackProtocol(NewSaltpackHandler(xp, g)), keybase1.ScanProofsProtocol(NewScanProofsHandler(xp, g)), keybase1.SecretKeysProtocol(NewSecretKeysHandler(xp, g)), keybase1.SessionProtocol(NewSessionHandler(xp, g)), keybase1.SignupProtocol(NewSignupHandler(xp, g)), keybase1.SigsProtocol(NewSigsHandler(xp, g)), keybase1.TestProtocol(NewTestHandler(xp, g)), keybase1.TrackProtocol(NewTrackHandler(xp, g)), CancelingProtocol(g, keybase1.ApiserverProtocol(NewAPIServerHandler(xp, g)), libkb.RPCCancelerReasonLogout), keybase1.PaperprovisionProtocol(NewPaperProvisionHandler(xp, g)), keybase1.SelfprovisionProtocol(NewSelfProvisionHandler(xp, g)), keybase1.RekeyProtocol(NewRekeyHandler2(xp, g, d.rekeyMaster)), keybase1.NotifyFSRequestProtocol(newNotifyFSRequestHandler(xp, g)), keybase1.GregorProtocol(newGregorRPCHandler(xp, g, d.gregor)), CancelingProtocol(g, chat1.LocalProtocol(newChatLocalHandler(xp, cg, d.gregor)), libkb.RPCCancelerReasonAll), keybase1.SimpleFSProtocol(NewSimpleFSHandler(xp, g)), keybase1.LogsendProtocol(NewLogsendHandler(xp, g)), CancelingProtocol(g, keybase1.TeamsProtocol(NewTeamsHandler(xp, connID, cg, d)), libkb.RPCCancelerReasonLogout), keybase1.TeamSearchProtocol(newTeamSearchHandler(xp, g)), keybase1.BadgerProtocol(newBadgerHandler(xp, g, d.badger)), keybase1.MerkleProtocol(newMerkleHandler(xp, g)), keybase1.GitProtocol(NewGitHandler(xp, g)), keybase1.HomeProtocol(NewHomeHandler(xp, g, d.home)), keybase1.AvatarsProtocol(NewAvatarHandler(xp, g, g.GetAvatarLoader())), keybase1.PhoneNumbersProtocol(NewPhoneNumbersHandler(xp, g)), keybase1.ContactsProtocol(NewContactsHandler(xp, g, contactsProv)), keybase1.EmailsProtocol(NewEmailsHandler(xp, g)), keybase1.InviteFriendsProtocol(NewInviteFriendsHandler(xp, g)), keybase1.Identify3Protocol(newIdentify3Handler(xp, g)), keybase1.AuditProtocol(NewAuditHandler(xp, g)), keybase1.UserSearchProtocol(NewUserSearchHandler(xp, g, contactsProv)), keybase1.BotProtocol(NewBotHandler(xp, g)), keybase1.FeaturedBotProtocol(NewFeaturedBotHandler(xp, g)), keybase1.WotProtocol(NewWebOfTrustHandler(xp, g)), } appStateHandler := newAppStateHandler(xp, g) protocols = append(protocols, keybase1.AppStateProtocol(appStateHandler)) walletHandler := newWalletHandler(xp, g, d.walletState) protocols = append(protocols, CancelingProtocol(g, stellar1.LocalProtocol(walletHandler), libkb.RPCCancelerReasonLogout)) userHandler := NewUserHandler(xp, g, d.ChatG(), d) protocols = append(protocols, keybase1.UserProtocol(userHandler)) protocols = append(protocols, keybase1.DebuggingProtocol(NewDebuggingHandler(xp, g, userHandler, walletHandler))) for _, proto := range protocols { if err = srv.Register(proto); err != nil { return err } } return nil } func (d *Service) Handle(c net.Conn) { start := time.Now() xp := rpc.NewTransport(c, libkb.NewRPCLogFactory(d.G()), d.G().LocalNetworkInstrumenterStorage, libkb.MakeWrapError(d.G()), rpc.DefaultMaxFrameLength) server := rpc.NewServer(xp, libkb.MakeWrapError(d.G())) cl := make(chan error, 1) connID := d.G().NotifyRouter.AddConnection(xp, cl) var logReg *logRegister if d.isDaemon { // Create a new log register object that the Log handler can use to // register a logger. When this function finishes, the logger // will be removed. logReg = newLogRegister(d.logForwarder, d.G().Log) defer logReg.UnregisterLogger() } if err := d.RegisterProtocols(server, xp, connID, logReg); err != nil { d.G().Log.Warning("RegisterProtocols error: %s", err) return } // Run the server and wait for it to finish. <-server.Run() // err is always non-nil. err := server.Err() cl <- err if !errors.Is(err, io.EOF) { d.G().Log.Warning("Run error: %s", err) } d.G().Log.Debug("Handle() complete for connection %d [time=%v]", connID, time.Since(start)) } func (d *Service) Run() (err error) { mctx := libkb.NewMetaContextBackground(d.G()).WithLogTag("SVC") defer func() { d.stopProfile() if d.startCh != nil { close(d.startCh) } d.G().NotifyRouter.HandleServiceShutdown() if err != nil { mctx.Info("Service#Run() exiting with error %s (code %d)", err.Error(), d.G().ExitCode) } else { mctx.Info("Service#Run() exiting (code %d)", d.G().ExitCode) } }() mctx.Debug("+ service starting up; forkType=%v", d.ForkType) mctx.PerfDebug("+ service starting up; forkType=%v", d.ForkType) d.startProfile() // Sets this global context to "service" mode which will toggle a flag // and will also set in motion various go-routine based managers d.G().SetService() uir := NewUIRouter(d.G()) d.G().SetUIRouter(uir) // register the service's logForwarder as the external handler for the log module: d.G().Log.SetExternalHandler(d.logForwarder) err = d.writeServiceInfo() if err != nil { return } if len(d.chdirTo) != 0 { etmp := os.Chdir(d.chdirTo) if etmp != nil { mctx.Warning("Could not change directory to %s: %s", d.chdirTo, etmp) } else { mctx.Info("Changing runtime dir to %s", d.chdirTo) } } switch d.G().Env.GetServiceType() { case "": // Not set, do nothing. case "launchd": d.ForkType = keybase1.ForkType_LAUNCHD case "systemd": d.ForkType = keybase1.ForkType_SYSTEMD default: mctx.Warning("Unknown service type: %q", d.G().Env.GetServiceType()) } if err = d.GetExclusiveLock(); err != nil { return } if err = d.cleanupSocketFile(); err != nil { return } // Open both local DBs concurrently. They are at different paths so there // is no lock contention between them. Any DB call that arrives before an // open finishes will block on sync.Once inside doWhileOpenAndNukeIfCorrupted. go func() { _ = d.G().LocalDb.ForceOpen() }() go func() { _ = d.G().LocalChatDb.ForceOpen() }() var l net.Listener if l, err = d.ConfigRPCServer(); err != nil { return err } if err = d.SetupCriticalSubServices(); err != nil { return err } if err = d.runOneshot(mctx); err != nil { return err } d.SetupChatModules(nil) d.RunBackgroundOperations(uir) // At this point initialization is complete, and we're about to start the // listen loop. This is the natural point to report "startup successful" to // the supervisor (currently just systemd on Linux). This isn't necessary // for correctness, but it allows commands like "systemctl start keybase.service" // to report startup errors to the terminal, by delaying their return // until they get this notification (Type=notify, in systemd lingo). systemd.NotifyStartupFinished() d.G().ExitCode, err = d.ListenLoopWithStopper(l) return err } func (d *Service) SetupCriticalSubServices() error { allG := globals.NewContext(d.G(), d.ChatG()) mctx := d.MetaContext(context.TODO()) d.G().RuntimeStats = runtimestats.NewRunner(allG) teams.ServiceInit(d.G()) stellar.ServiceInit(d.G(), d.walletState, d.badger) pvl.NewPvlSourceAndInstall(d.G()) avatars.CreateSourceFromEnvAndInstall(d.G()) externals.NewParamProofStoreAndInstall(d.G()) externals.NewExternalURLStoreAndInstall(d.G()) ephemeral.ServiceInit(mctx) teambot.ServiceInit(mctx) d.avatarSrv = avatars.ServiceInit(d.G(), d.httpSrv, d.G().GetAvatarLoader()) contacts.ServiceInit(d.G()) maps.ServiceInit(allG, d.httpSrv) return nil } func (d *Service) RunBackgroundOperations(uir *UIRouter) { // These are all background-ish operations that the service performs. // We should revisit these on mobile, or at least, when mobile apps are // backgrounded. d.G().Log.Debug("RunBackgroundOperations: starting") ctx := context.Background() d.tryLogin(ctx, libkb.LoginAttemptOnline) d.chatOutboxPurgeCheck() d.hourlyChecks() d.slowChecks() // 6 hours d.startupGregor() d.startChatModules() d.addGlobalHooks() d.configurePath() d.configureRekey(uir) d.runBackgroundPerUserKeyUpgrade() d.runBackgroundPerUserKeyUpkeep() d.runBackgroundWalletUpkeep() d.runBackgroundBoxAuditRetry() d.runBackgroundBoxAuditScheduler() d.runBackgroundContactSync() d.runBackgroundInviteFriendsPoll() d.runTLFUpgrade() d.runTrackerLoader(ctx) d.runRuntimeStats(ctx) d.runTeamUpgrader(ctx) d.runHomePoller(ctx) d.runMerkleAudit(ctx) d.startInstallReferrerListener(d.MetaContext(ctx)) } func (d *Service) purgeOldChatAttachmentData() { purge := func(dir, glob string) { files, err := filepath.Glob(filepath.Join(dir, glob)) if err != nil { d.G().Log.Debug("purgeOldChatAttachmentData: failed to get %s files: %s", glob, err) } else { d.G().Log.Debug("purgeOldChatAttachmentData: %d files to delete for %s", len(files), glob) for _, f := range files { if err := os.Remove(f); err != nil { d.G().Log.Debug("purgeOldChatAttachmentData: failed to remove: name: %s err: %s", f, err) } } } } cacheDir := d.G().GetCacheDir() oldCacheDir := filepath.Dir(cacheDir) for _, dir := range []string{cacheDir, oldCacheDir} { purge(dir, "kbchat*") purge(dir, "avatar*") purge(dir, "prev*") purge(dir, "rncontacts*") } } // A share is what launches the app, so a blind sweep here could delete the // payload for the share currently on screen. Anything this old is from a // previous run. const incomingShareMaxAge = 24 * time.Hour // The iOS share extension writes payloads into the app group's cache and is // gone before the app reads them, so nothing on either side owns deleting them. // They sit outside GetCacheDir (that is the app sandbox, this is the group // container), so purgeOldChatAttachmentData never sees them. func (d *Service) purgeOldIncomingShareData() { dir := IncomingShareFolder(d.G().Env.GetMobileSharedHome()) if dir == "" { return } entries, err := os.ReadDir(dir) if err != nil { if !os.IsNotExist(err) { d.G().Log.Debug("purgeOldIncomingShareData: read dir: %s", err) } return } cutoff := time.Now().Add(-incomingShareMaxAge) for _, entry := range entries { // manifest.json lives alongside the payload dirs and is the live handoff // to the app; only the directories are ours to remove. if !entry.IsDir() { continue } info, err := entry.Info() if err != nil { continue } // Writing the processed copy into the payload dir bumps its mtime, so a // share still being worked on keeps renewing its lease. if info.ModTime().After(cutoff) { continue } p := filepath.Join(dir, entry.Name()) if err := os.RemoveAll(p); err != nil { d.G().Log.Debug("purgeOldIncomingShareData: failed to remove %s: %s", p, err) } } } func (d *Service) startChatModules() { kuid := d.G().Env.GetUID() if !kuid.IsNil() { uid := kuid.ToBytes() g := globals.NewContext(d.G(), d.ChatG()) g.PushHandler.Start(context.Background(), uid) g.MessageDeliverer.Start(context.Background(), uid) g.ConvLoader.Start(context.Background(), uid) g.FetchRetrier.Start(context.Background(), uid) g.EphemeralPurger.Start(context.Background(), uid) g.EphemeralTracker.Start(context.Background(), uid) g.InboxSource.Start(context.Background(), uid) g.Indexer.Start(globals.ChatCtx(context.Background(), g, keybase1.TLFIdentifyBehavior_CHAT_SKIP, nil, nil), uid) g.ArchiveRegistry.Start(globals.ChatCtx(context.Background(), g, keybase1.TLFIdentifyBehavior_CHAT_SKIP, nil, nil), uid) g.CoinFlipManager.Start(context.Background(), uid) g.TeamMentionLoader.Start(context.Background(), uid) g.LiveLocationTracker.Start(context.Background(), uid) g.BotCommandManager.Start(context.Background(), uid) g.UIInboxLoader.Start(context.Background(), uid) g.PushShutdownHook(d.stopChatModules) } d.purgeOldChatAttachmentData() d.purgeOldIncomingShareData() } func (d *Service) stopChatModules(m libkb.MetaContext) error { <-d.ChatG().PushHandler.Stop(m.Ctx()) <-d.ChatG().MessageDeliverer.Stop(m.Ctx()) <-d.ChatG().ConvLoader.Stop(m.Ctx()) <-d.ChatG().FetchRetrier.Stop(m.Ctx()) <-d.ChatG().EphemeralPurger.Stop(m.Ctx()) <-d.ChatG().EphemeralTracker.Stop(m.Ctx()) <-d.ChatG().InboxSource.Stop(m.Ctx()) <-d.ChatG().Indexer.Stop(m.Ctx()) <-d.ChatG().ArchiveRegistry.Stop(m.Ctx()) <-d.ChatG().CoinFlipManager.Stop(m.Ctx()) <-d.ChatG().TeamMentionLoader.Stop(m.Ctx()) <-d.ChatG().LiveLocationTracker.Stop(m.Ctx()) <-d.ChatG().BotCommandManager.Stop(m.Ctx()) <-d.ChatG().UIInboxLoader.Stop(m.Ctx()) <-d.ChatG().JourneyCardManager.Stop(m.Ctx()) return nil } func (d *Service) SetupChatModules(ri func() chat1.RemoteInterface) { g := globals.NewContext(d.G(), d.ChatG()) if ri == nil { ri = d.gregor.GetClient } // Add OnLogout/OnDbNuke hooks for any in-memory sources storage.SetupGlobalHooks(g) // Set up main chat data sources boxer := chat.NewBoxer(g) g.CtxFactory = chat.NewCtxFactory(g) g.Badger = d.badger inboxSource := chat.NewInboxSource(g, g.Env.GetInboxSourceType(), ri) g.InboxSource = inboxSource d.badger.SetLocalChatState(inboxSource) chatStorage := storage.New(g, nil) g.ConvSource = chat.NewConversationSource(g, g.Env.GetConvSourceType(), boxer, chatStorage, ri) chatStorage.SetAssetDeleter(g.ConvSource) g.RegexpSearcher = search.NewRegexpSearcher(g) g.Indexer = search.NewIndexer(g) g.AddDbNukeHook(g.Indexer, "Indexer") g.ArchiveRegistry = chat.NewChatArchiveRegistry(g, ri) g.AddDbNukeHook(g.ArchiveRegistry, "ChatArchiveRegistry") g.ServerCacheVersions = storage.NewServerVersions(g) // Syncer and retriers chatSyncer := chat.NewSyncer(g) g.Syncer = chatSyncer g.FetchRetrier = chat.NewFetchRetrier(g) g.ConvLoader = chat.NewBackgroundConvLoader(g) g.EphemeralPurger = chat.NewBackgroundEphemeralPurger(g) g.EphemeralTracker = chat.NewEphemeralTracker(g) g.AddLogoutHook(g.EphemeralTracker, "EphemeralTracker") g.AddDbNukeHook(g.EphemeralTracker, "EphemeralTracker") g.ActivityNotifier = chat.NewNotifyRouterActivityRouter(g) // Set up push handler with the badger d.badger.SetInboxVersionSource(storage.NewInboxVersionSource(g)) pushHandler := chat.NewPushHandler(g) g.PushHandler = pushHandler // Message sending apparatus s3signer := attachments.NewS3Signer(ri) store := attachments.NewS3Store(g, g.GetRuntimeDir()) attachmentLRUSize := 1000 g.AttachmentUploader = attachments.NewUploader(g, store, s3signer, ri, attachmentLRUSize) g.AddDbNukeHook(g.AttachmentUploader, "AttachmentUploader") sender := chat.NewBlockingSender(g, chat.NewBoxer(g), ri) g.MessageDeliverer = chat.NewDeliverer(g, sender, d.gregor) // team channel source g.TeamChannelSource = chat.NewTeamChannelSource(g) g.AddLogoutHook(g.TeamChannelSource, "TeamChannelSource") g.AddDbNukeHook(g.TeamChannelSource, "TeamChannelSource") if g.Standalone { g.AttachmentURLSrv = types.DummyAttachmentHTTPSrv{} } else { g.AttachmentURLSrv = chat.NewAttachmentHTTPSrv(g, d.httpSrv, chat.NewCachingAttachmentFetcher(g, store, attachmentLRUSize), ri) } g.AddDbNukeHook(g.AttachmentURLSrv, "AttachmentURLSrv") g.StellarLoader = stellar.DefaultLoader(g.ExternalG()) g.StellarSender = wallet.NewSender(g) g.StellarPushHandler = g.ExternalG().GetStellar() convStorage := chat.NewDevConversationBackedStorage(g, ri) g.Unfurler = unfurl.NewUnfurler(g, store, s3signer, convStorage, chat.NewNonblockingSender(g, sender), ri) g.CommandsSource = commands.NewSource(g) g.CoinFlipManager = chat.NewFlipManager(g, ri) g.JourneyCardManager = chat.NewJourneyCardManager(g, ri) g.AddDbNukeHook(g.JourneyCardManager, "JourneyCardManager") g.TeamMentionLoader = chat.NewTeamMentionLoader(g) g.ExternalAPIKeySource = chat.NewRemoteExternalAPIKeySource(g, ri) g.LiveLocationTracker = maps.NewLiveLocationTracker(g) g.BotCommandManager = bots.NewCachingBotCommandManager(g, ri, chat.CreateNameInfoSource) g.UIInboxLoader = chat.NewUIInboxLoader(g) if loader, ok := g.UIInboxLoader.(*chat.UIInboxLoader); ok { g.AddLogoutHook(loader, "chat/UIInboxLoader") } g.UIThreadLoader = chat.NewUIThreadLoader(g, ri) g.ParticipantsSource = chat.NewCachingParticipantSource(g, ri) g.EmojiSource = chat.NewDevConvEmojiSource(g, ri) // Set up Offlinables on Syncer chatSyncer.RegisterOfflinable(g.InboxSource) chatSyncer.RegisterOfflinable(g.FetchRetrier) chatSyncer.RegisterOfflinable(g.MessageDeliverer) chatSyncer.RegisterOfflinable(g.UIThreadLoader) // Add a tlfHandler into the user changed handler group so we can keep identify info // fresh g.AddUserChangedHandler(chat.NewIdentifyChangedHandler(g)) g.ChatHelper = chat.NewHelper(g, ri) } func (d *Service) configureRekey(uir *UIRouter) { rkm := d.rekeyMaster rkm.uiRouter = uir d.gregor.PushHandler(rkm) // the rekey master needs to query gregor state, so we have // this unfortunate dependency injection rkm.gregor = d.gregor rkm.Start() } func (d *Service) runTLFUpgrade() { d.tlfUpgrader.Run() } func (d *Service) runTrackerLoader(ctx context.Context) { d.trackerLoader.Run(ctx) } func (d *Service) runRuntimeStats(ctx context.Context) { d.G().RuntimeStats.Start(ctx) } func (d *Service) runTeamUpgrader(ctx context.Context) { d.teamUpgrader.Run(libkb.NewMetaContext(ctx, d.G())) } func (d *Service) runHomePoller(ctx context.Context) { d.home.RunUpdateLoop(libkb.NewMetaContext(ctx, d.G())) } func (d *Service) runMerkleAudit(ctx context.Context) { if libkb.IsMobilePlatform() { d.G().Log.Debug("MerkleAudit disabled (not desktop, not starting)") return } eng := engine.NewMerkleAudit(d.G(), &engine.MerkleAuditArgs{}) m := libkb.NewMetaContextBackground(d.G()) if err := engine.RunEngine2(m, eng); err != nil { m.Warning("merkle root background audit error: %v", err) } d.G().PushShutdownHook(eng.Shutdown) } func (d *Service) startupGregor() { g := d.G() if g.Env.GetGregorDisabled() { g.Log.Debug("Gregor explicitly disabled") } else if !g.Env.GetTorMode().UseSession() { g.Log.Debug("Gregor disabled in Tor mode") } else { g.Log.Debug("connecting to gregord for push notifications") // Create gregorHandler instance first so any handlers can connect // to it before we actually connect to gregor (either gregor is down // or we aren't logged in) d.gregor.Init() d.reachability = newReachability(d.G(), d.gregor) d.gregor.setReachability(d.reachability) d.G().ConnectivityMonitor = d.reachability d.gregor.badger = d.badger d.G().GregorState = d.gregor d.G().GregorListener = d.gregor // Add default handlers userHandler := newUserHandler(d.G()) userHandler.PushUserBlockedHandler(d.home) d.gregor.PushHandler(userHandler) // TODO -- get rid of this? d.gregor.PushHandler(newRekeyLogHandler(d.G())) d.gregor.PushHandler(newTeamHandler(d.G(), d.badger)) d.gregor.PushHandler(stellargregor.New(d.G(), d.walletState)) d.gregor.PushHandler(d.home) d.gregor.PushHandler(newEKHandler(d.G())) d.gregor.PushHandler(newTeambotHandler(d.G())) d.gregor.PushHandler(newAvatarGregorHandler(d.G(), d.G().GetAvatarLoader())) d.gregor.PushHandler(newPhoneNumbersGregorHandler(d.G())) d.gregor.PushHandler(newEmailsGregorHandler(d.G())) d.gregor.PushHandler(newKBFSFavoritesHandler(d.G())) // Connect to gregord if gcErr := d.tryGregordConnect(); gcErr != nil { g.Log.Debug("error connecting to gregord: %s", gcErr) } } } func (d *Service) addGlobalHooks() { d.G().AddLoginHook(d) d.G().AddLogoutHook(d, "service/Service") } func (d *Service) StartLoopbackServer(loginMode libkb.LoginAttempt) error { ctx := context.Background() var l net.Listener var err error if err = d.GetExclusiveLock(); err != nil { return err } // Open both local DBs concurrently. They are at different paths so there // is no lock contention between them. Any DB call that arrives before an // open finishes will block on sync.Once inside doWhileOpenAndNukeIfCorrupted. go func() { _ = d.G().LocalDb.ForceOpen() }() go func() { _ = d.G().LocalChatDb.ForceOpen() }() if l, err = d.G().MakeLoopbackServer(); err != nil { return err } // Make sure we have the same keys in memory in standalone mode as we do in // regular service mode. d.tryLogin(ctx, loginMode) go func() { _ = d.ListenLoop(l) }() return nil } func (d *Service) ensureRuntimeDir() (string, error) { runtimeDir := d.G().Env.GetRuntimeDir() return runtimeDir, os.MkdirAll(runtimeDir, libkb.PermDir) } // If the daemon is already running, we need to be able to check what version // it is, in case the client has been updated. func (d *Service) writeServiceInfo() error { _, err := d.ensureRuntimeDir() if err != nil { return err } // Write runtime info file rtInfo := libkb.KeybaseServiceInfo(d.G()) return rtInfo.WriteFile(d.G().Env.GetServiceInfoPath(), d.G().Log) } func (d *Service) chatOutboxPurgeCheck() { ticker := libkb.NewBgTicker(5 * time.Minute) m := libkb.NewMetaContextBackground(d.G()).WithLogTag("OBOXPRGE") d.G().PushShutdownHook(func(mctx libkb.MetaContext) error { m.Debug("stopping chatOutboxPurgeCheck loop") ticker.Stop() return nil }) go func() { for { <-ticker.C uid := d.G().Env.GetUID() if uid.IsNil() { continue } gregorUID := gregor1.UID(uid.ToBytes()) g := globals.NewContext(d.G(), d.ChatG()) ephemeralPurged, err := storage.NewOutbox(g, gregorUID).OutboxPurge(context.Background()) if err != nil { m.Debug("OutboxPurge error: %s", err) continue } if len(ephemeralPurged) > 0 { act := chat1.NewChatActivityWithFailedMessage(chat1.FailedMessageInfo{ OutboxRecords: ephemeralPurged, IsEphemeralPurge: true, }) d.ChatG().ActivityNotifier.Activity(context.Background(), gregorUID, chat1.TopicType_NONE, &act, chat1.ChatActivitySource_LOCAL) } } }() } func (d *Service) hourlyChecks() { ticker := libkb.NewBgTicker(1 * time.Hour) m := libkb.NewMetaContextBackground(d.G()).WithLogTag("HRLY") d.G().PushShutdownHook(func(mctx libkb.MetaContext) error { m.Debug("stopping hourlyChecks loop") ticker.Stop() return nil }) go func() { // do this quickly if err := m.LogoutAndDeprovisionIfRevoked(); err != nil { m.Debug("LogoutAndDeprovisionIfRevoked error: %s", err) } for { <-ticker.C m.Debug("+ hourly check loop") m.Debug("| checking if current device revoked") if err := m.LogoutAndDeprovisionIfRevoked(); err != nil { m.Debug("LogoutAndDeprovisionIfRevoked error: %s", err) } m.Debug("| checking tracks on an hour timer") err := libkb.CheckTracking(m.G()) if err != nil { m.Debug("CheckTracking error: %s", err) } m.Debug("- hourly check loop") } }() } func (d *Service) deviceCloneSelfCheck() error { m := libkb.NewMetaContextBackground(d.G()) m = m.WithLogTag("CLONE") before, after, err := libkb.UpdateDeviceCloneState(m) if err != nil { return err } newClones := after - before m.Debug("deviceCloneSelfCheck: is there a new clone? %v", newClones > 0) if newClones > 0 { m.Debug("deviceCloneSelfCheck: notifying user %v -> %v restarts", before, after) d.G().NotifyRouter.HandleDeviceCloneNotification(newClones) } return nil } func (d *Service) slowChecks() { ticker := libkb.NewBgTicker(6 * time.Hour) d.G().PushShutdownHook(func(mctx libkb.MetaContext) error { d.G().Log.Debug("stopping slowChecks loop") ticker.Stop() return nil }) go func() { ctx := context.Background() m := libkb.NewMetaContext(ctx, d.G()).WithLogTag("SLOWCHK") // Do this once fast if err := d.deviceCloneSelfCheck(); err != nil { m.Debug("deviceCloneSelfCheck error: %s", err) } for { <-ticker.C m.Debug("+ slow checks loop") m.Debug("| checking if current device should log out") if err := m.LogoutSelfCheck(); err != nil { m.Debug("LogoutSelfCheck error: %s", err) } if m.G().MobileNetState.State().IsLimited() { m.Debug("| skipping clone check, limited net state") continue } m.Debug("| checking if current device is a clone") if err := d.deviceCloneSelfCheck(); err != nil { m.Debug("deviceCloneSelfCheck error: %s", err) } m.Debug("- slow checks loop") } }() } func (d *Service) tryGregordConnect() error { loggedIn := d.G().ActiveDevice.Valid() if !loggedIn { // We only respect the loggedIn flag in the no-error case. d.G().Log.Debug("not logged in, so not connecting to gregord") return nil } return d.gregordConnect() } func (d *Service) runBackgroundPerUserKeyUpgrade() { eng := engine.NewPerUserKeyUpgradeBackground(d.G(), &engine.PerUserKeyUpgradeBackgroundArgs{}) go func() { m := libkb.NewMetaContextBackground(d.G()) err := engine.RunEngine2(m, eng) if err != nil { m.Warning("per-user-key background upgrade error: %v", err) } }() d.G().PushShutdownHook(func(mctx libkb.MetaContext) error { d.G().Log.Debug("stopping per-user-key background upgrade") eng.Shutdown() return nil }) } func (d *Service) runBackgroundPerUserKeyUpkeep() { eng := engine.NewPerUserKeyUpkeepBackground(d.G(), &engine.PerUserKeyUpkeepBackgroundArgs{}) go func() { m := libkb.NewMetaContextBackground(d.G()) err := engine.RunEngine2(m, eng) if err != nil { m.Warning("per-user-key background upkeep error: %v", err) } }() d.G().PushShutdownHook(func(mctx libkb.MetaContext) error { d.G().Log.Debug("stopping per-user-key background upkeep") eng.Shutdown() return nil }) } func (d *Service) runBackgroundWalletUpkeep() { eng := engine.NewWalletUpkeepBackground(d.G(), &engine.WalletUpkeepBackgroundArgs{}) go func() { m := libkb.NewMetaContextBackground(d.G()) err := engine.RunEngine2(m, eng) if err != nil { m.Warning("background WalletUpkeep error: %v", err) } }() d.G().PushShutdownHook(func(mctx libkb.MetaContext) error { d.G().Log.Debug("stopping background WalletUpkeep") eng.Shutdown() return nil }) } func (d *Service) runBackgroundBoxAuditRetry() { eng := engine.NewBoxAuditRetryBackground(d.G()) go func() { m := libkb.NewMetaContextBackground(d.G()) err := engine.RunEngine2(m, eng) if err != nil { m.Warning("background BoxAuditorRetry error: %v", err) } }() d.G().PushShutdownHook(func(mctx libkb.MetaContext) error { d.G().Log.Debug("stopping background BoxAuditorRetry") eng.Shutdown() return nil }) } func (d *Service) runBackgroundBoxAuditScheduler() { eng := engine.NewBoxAuditSchedulerBackground(d.G()) go func() { m := libkb.NewMetaContextBackground(d.G()) err := engine.RunEngine2(m, eng) if err != nil { m.Warning("background BoxAuditorScheduler error: %v", err) } }() d.G().PushShutdownHook(func(mctx libkb.MetaContext) error { d.G().Log.Debug("stopping background BoxAuditorScheduler") eng.Shutdown() return nil }) } func (d *Service) runBackgroundContactSync() { eng := engine.NewContactSyncBackground(d.G()) go func() { m := libkb.NewMetaContextBackground(d.G()) err := engine.RunEngine2(m, eng) if err != nil { m.Warning("background ContactSync error: %v", err) } }() d.G().PushShutdownHook(func(mctx libkb.MetaContext) error { d.G().Log.Debug("stopping background ContactSync") eng.Shutdown() return nil }) } func (d *Service) runBackgroundInviteFriendsPoll() { eng := engine.NewInviteFriendsPollBackground(d.G()) go func() { m := libkb.NewMetaContextBackground(d.G()) err := engine.RunEngine2(m, eng) if err != nil { m.Warning("background InviteFriendsPoll error: %v", err) } }() d.G().PushShutdownHook(func(mctx libkb.MetaContext) error { d.G().Log.Debug("stopping background InviteFriendsPoll") eng.Shutdown() return nil }) } func (d *Service) OnLogin(mctx libkb.MetaContext) error { d.rekeyMaster.Login() if err := d.gregordConnect(); err != nil { return err } uid := d.G().Env.GetUID() if !uid.IsNil() { d.startChatModules() d.runTLFUpgrade() d.runTrackerLoader(mctx.Ctx()) } return nil } func (d *Service) OnLogout(m libkb.MetaContext) (err error) { defer m.Trace("Service#OnLogout", &err)() defer m.PerfTrace("Service#OnLogout", &err)() log := func(s string) { m.Debug("Service#OnLogout: %s", s) } log("canceling live RPCs") d.G().RPCCanceler.CancelLiveContexts(libkb.RPCCancelerReasonLogout) log("shutting down chat modules") if err := d.stopChatModules(m); err != nil { log(fmt.Sprintf("unable to stopChatModules %v", err)) } log("shutting down gregor") if d.gregor != nil { _ = d.gregor.Reset() } log("shutting down rekeyMaster") d.rekeyMaster.Logout() log("shutting down badger") if d.badger != nil { d.badger.Clear(context.TODO()) } log("shutting down TLF upgrader") if d.tlfUpgrader != nil { _ = d.tlfUpgrader.Shutdown(m) } log("resetting wallet state on logout") if d.walletState != nil { d.walletState.Reset(m) } log("shutting down tracker loader") if d.trackerLoader != nil { <-d.trackerLoader.Shutdown(m.Ctx()) } return nil } func (d *Service) gregordConnect() (err error) { var uri *rpc.FMPURI defer d.G().Trace("gregordConnect", &err)() uri, err = rpc.ParseFMPURI(d.G().Env.GetGregorURI()) if err != nil { return err } d.G().Log.Debug("| gregor URI: %s", uri) // If we are already connected, then shutdown and reset the gregor // handler if d.gregor.IsConnected() { if err := d.gregor.Reset(); err != nil { return err } } // Connect to gregord return d.gregor.Connect(uri) } // ReleaseLock releases the locking pidfile by closing, unlocking and // deleting it. func (d *Service) ReleaseLock(mctx libkb.MetaContext) error { d.G().Log.Debug("Releasing lock file") return d.lockPid.Close() } // GetExclusiveLockWithoutAutoUnlock grabs the exclusive lock over running // keybase and continues to hold the lock. The caller is then required to // manually release this lock via ReleaseLock() func (d *Service) GetExclusiveLockWithoutAutoUnlock() error { if _, err := d.ensureRuntimeDir(); err != nil { return err } return d.lockPIDFile() } // GetExclusiveLock grabs the exclusive lock over running keybase // and then installs a shutdown hook to release the lock automatically // on shutdown. func (d *Service) GetExclusiveLock() error { if err := d.GetExclusiveLockWithoutAutoUnlock(); err != nil { return err } d.G().PushShutdownHook(d.ReleaseLock) return nil } func (d *Service) cleanupSocketFile() error { // Short circuit if we're running under socket activation -- the socket // file is already set up for us, and we mustn't delete it. if systemd.IsSocketActivated() { return nil } sf, err := d.G().Env.GetSocketBindFile() if err != nil { return err } if exists, err := libkb.FileExists(sf); err != nil { return err } else if exists { d.G().Log.Debug("removing stale socket file: %s", sf) if err = os.Remove(sf); err != nil { d.G().Log.Warning("error removing stale socket file: %s", err) return err } } return nil } func (d *Service) lockPIDFile() (err error) { var fn string if fn, err = d.G().Env.GetPidFile(); err != nil { return err } if err = libkb.MakeParentDirs(d.G().Log, fn); err != nil { return err } d.lockPid = libkb.NewLockPIDFile(d.G(), fn) if err = d.lockPid.Lock(); err != nil { return err } d.G().Log.Debug("Lock pidfile: %s\n", fn) return nil } func (d *Service) ConfigRPCServer() (net.Listener, error) { // First, check to see if we've been launched with socket activation by // systemd. If so, the socket is already open. Otherwise open it ourselves. // NOTE: We no longer configure our keybse.service and kbfs.service units // to be socket-activated by default. It was causing too much trouble when // non-systemd instances deleted the socket files. It's possible this issue // will get fixed in future versions of systemd; see // https://github.com/systemd/systemd/issues/7274. listener, err := systemd.GetListenerFromEnvironment() if err != nil { d.G().Log.Error("unexpected error in GetListenerFromEnvironment: %#v", err) return nil, err } else if listener != nil { d.G().Log.Debug("Systemd socket activation in use. Accepting connections on fd 3.") } else { d.G().Log.Debug("No socket activation in the environment. Binding to a new socket.") listener, err = d.G().BindToSocket() if err != nil { return nil, err } } if d.startCh != nil { close(d.startCh) d.startCh = nil } return listener, nil } func (d *Service) Stop(exitCode keybase1.ExitCode) { d.G().Log.Debug("Beginning the process of stopping the service") d.stopCh <- exitCode } func (d *Service) ListenLoopWithStopper(l net.Listener) (exitCode keybase1.ExitCode, err error) { ch := make(chan error) go func() { ch <- d.ListenLoop(l) }() exitCode = <-d.stopCh l.Close() d.G().Log.Debug("Left listen loop w/ exit code %d\n", exitCode) return exitCode, <-ch } func (d *Service) ListenLoop(l net.Listener) (err error) { d.G().Log.Debug("+ Enter ListenLoop()") for { var c net.Conn if c, err = l.Accept(); err != nil { if libkb.IsSocketClosedError(err) { d.G().Log.Debug("ListenLoop socket/pipe is closed") err = nil } d.G().Log.Debug("+ Leaving ListenLoop() w/ error %v", err) return } go d.Handle(c) } } func (d *Service) runOneshot(mctx libkb.MetaContext) (err error) { if len(d.oneshotUsername) == 0 { return nil } mctx.Debug("Oneshot login with username: %s", d.oneshotUsername) pk, err := d.getPaperKey(mctx) if err != nil { return err } eng := engine.NewLoginOneshot(mctx.G(), keybase1.LoginOneshotArg{ Username: d.oneshotUsername, PaperKey: pk, }) return engine.RunEngine2(mctx, eng) } func (d *Service) getPaperKey(mctx libkb.MetaContext) (key string, err error) { if len(d.oneshotPaperkey) > 0 { return d.oneshotPaperkey, nil } envVar := "KEYBASE_PAPERKEY" key = os.Getenv(envVar) if len(key) > 0 { return key, nil } mctx.Info("Reading paperkey from standard input in oneshot mode") key, err = bufio.NewReader(os.Stdin).ReadString('\n') if errors.Is(err, io.EOF) && len(key) > 0 { err = nil } if len(key) < 5 { return "", fmt.Errorf("bad paper key read from standard input in oneshot mode") } return key, err } func (d *Service) ParseArgv(ctx *cli.Context) error { d.chdirTo = ctx.String("chdir") if ctx.Bool("auto-forked") { d.ForkType = keybase1.ForkType_AUTO } else if ctx.Bool("watchdog-forked") { d.ForkType = keybase1.ForkType_WATCHDOG } else if ctx.Bool("launchd-forked") { d.ForkType = keybase1.ForkType_LAUNCHD } d.oneshotUsername = ctx.String("oneshot-username") d.oneshotPaperkey = ctx.String("oneshot-paperkey") if len(d.oneshotPaperkey) > 0 && len(d.oneshotUsername) == 0 { return fmt.Errorf("Cannot use --oneshot-paperkey without the --oneshot-username option") } return nil } func NewCmdService(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command { return cli.Command{ Name: "service", Usage: "start the Keybase service to power all other CLI options", Flags: []cli.Flag{ cli.StringFlag{ Name: "chdir", Usage: "Specify where to run as a daemon (via chdir)", }, cli.StringFlag{ Name: "label", Usage: "Specifying a label can help identify services.", }, cli.BoolFlag{ Name: "auto-forked", Usage: "Specify if this binary was auto-forked from the client", }, cli.BoolFlag{ Name: "watchdog-forked", Usage: "Specify if this binary was started by the watchdog", }, cli.StringFlag{ Name: "u, oneshot-username", Usage: "In oneshot mode, startup with username", }, cli.StringFlag{ Name: "p, oneshot-paperkey", Usage: "In oneshot mode, startup with paperkey; DANGEROUS to pass paperkey as a parameter", }, }, Description: `"keybase service" starts up the "service" process that powers all of Keybase. Usually it runs in the background as part of your OS's packaging of the Keybase, but it can also run as a foreground process (useful in development or for bots). There is special support for running the service and immediately logging in via "oneshot mode", which is particularly userful for bots. Specify --onershot-username and a paper key to enable this option. We recommend passing the paper key via standard input, or the KEBASE_PAPERKEY= environment variable, but this subcommand will dangerously accept the paper key via command line option.`, Action: func(c *cli.Context) { cl.ChooseCommand(NewService(g, true /* isDaemon */), "service", c) cl.SetService() }, } } func (d *Service) GetUsage() libkb.Usage { return libkb.ServiceUsage } func GetCommands(cl *libcmdline.CommandLine, g *libkb.GlobalContext) []cli.Command { return []cli.Command{ NewCmdService(cl, g), } } func (d *Service) GregorDismiss(id gregor.MsgID) error { if d.gregor == nil { return errors.New("can't gregor dismiss without a gregor") } return d.gregor.DismissItem(context.Background(), gregor1.IncomingClient{Cli: d.gregor.cli}, id) } func (d *Service) GregorInject(cat string, body []byte) (gregor.MsgID, error) { if d.gregor == nil { return nil, errors.New("can't gregor inject without a gregor") } return d.gregor.InjectItem(context.TODO(), cat, body, gregor1.TimeOrOffset{}) } func (d *Service) GregorInjectOutOfBandMessage(sys string, body []byte) error { if d.gregor == nil { return errors.New("can't gregor inject without a gregor") } return d.gregor.InjectOutOfBandMessage(context.TODO(), sys, body) } func (d *Service) HasGregor() bool { return d.gregor != nil && d.gregor.IsConnected() } func (d *Service) SimulateGregorCrashForTesting() { if d.HasGregor() { d.gregor.simulateCrashForTesting() } else { d.G().Log.Warning("Can't simulate a gregor crash without a gregor") } } func (d *Service) SetGregorPushStateFilter(f func(m gregor.Message) bool) { d.gregor.SetPushStateFilter(f) } func (d *Service) GetUserAvatar(username string) (string, error) { if d.avatarSrv == nil { return "", fmt.Errorf("AvatarSrv is not ready") } return d.avatarSrv.GetUserAvatar(username) } // configurePath is a somewhat unfortunate hack, but as it currently stands, // when the keybase service is run out of launchd, its path is minimal and // often can't find the GPG location. We have hacks around this for CLI operation, // in which the CLI forwards its path to the service, and the service enlarges // its path accordingly. In this way, the service can get access to path additions // inserted by the user's shell startup scripts. However the same mechanism doesn't // apply to a GUI-driven workload, since the Electron GUI, like the Go service, is // launched from launchd and therefore has the wrong path. This function currently // noops on all platforms aside from macOS, but we can expand it later as needs be. func (d *Service) configurePath() { defer d.G().Trace("Service#configurePath", nil)() var newDirs string switch runtime.GOOS { case "darwin", "ios": newDirs = "/usr/local/bin:/usr/local/MacGPG2/bin" default: } if newDirs != "" { err := mergeIntoPath(d.G(), newDirs) if err != nil { d.G().Log.Debug("Error merging into path: %+v", err) } } } // tryLogin runs LoginOffline which will load the local session file and unlock the // local device keys without making any network requests. // // If that fails for any reason, LoginProvisionedDevice is used, which should get // around any issue where the session.json file is out of date or missing since the // last time the service started. // awaitInitialLoginAttempt blocks until the first startup login attempt has // finished (however it went), the context is done, or maxWait elapses. Used // by RPCs whose answer depends on login state so they don't race the login // that runs off the Init path on mobile. func (d *Service) awaitInitialLoginAttempt(m libkb.MetaContext, maxWait time.Duration) { select { case <-d.initialLoginAttemptDone: case <-m.Ctx().Done(): m.Debug("awaitInitialLoginAttempt: context done: %v", m.Ctx().Err()) case <-time.After(maxWait): m.Debug("awaitInitialLoginAttempt: gave up after %v", maxWait) } } func (d *Service) tryLogin(ctx context.Context, mode libkb.LoginAttempt) { if mode != libkb.LoginAttemptNone { // Signal on every exit path; sync.Once makes repeat calls no-ops. defer d.initialLoginAttemptOnce.Do(func() { close(d.initialLoginAttemptDone) }) } d.loginAttemptMu.Lock() defer d.loginAttemptMu.Unlock() m := libkb.NewMetaContext(ctx, d.G()) if d.loginAttempt == libkb.LoginAttemptOnline { m.Debug("login online attempt already tried, nothing to do") return } if d.loginSuccess { m.Debug("already logged in successfully, so nothing left to do") return } if mode == libkb.LoginAttemptOffline && d.loginAttempt == libkb.LoginAttemptOffline { m.Debug("already tried a login attempt offline") return } if mode == libkb.LoginAttemptNone { m.Debug("no login attempt made due to loginAttemptNone flag passed") return } d.loginAttempt = mode eng := engine.NewLoginOffline(d.G()) err := engine.RunEngine2(m, eng) if err == nil { m.Debug("login offline success") d.loginSuccess = true return } if mode == libkb.LoginAttemptOffline { m.Debug("not continuing with online login") return } m.Debug("error running LoginOffline on service startup: %s", err) m.Debug("trying LoginProvisionedDevice") // Standalone mode quirk here. We call tryLogin when client is // launched in standalone to unlock the same keys that we would // have in service mode. But NewLoginProvisionedDevice engine // needs KbKeyrings and not every command sets it up. Ensure // Keyring is available. // TODO: We will be phasing out KbKeyrings usage flag, or even // usage flags entirely. Then this will not be needed because // Keyrings will always be loaded. if m.G().Keyrings == nil { m.Debug("tryLogin: Configuring Keyrings") err := m.G().ConfigureKeyring() if err != nil { m.Debug("error configuring keyring: %s", err) } } deng := engine.NewLoginProvisionedDevice(d.G(), "") deng.SecretStoreOnly = true if err := engine.RunEngine2(m, deng); err != nil { m.Debug("error running LoginProvisionedDevice on service startup: %s", err) } else { m.Debug("success running LoginOffline on service startup") d.loginSuccess = true } } func (d *Service) startProfile() { cpu := os.Getenv("KEYBASE_CPUPROFILE") if cpu != "" { f, err := os.Create(cpu) if err != nil { d.G().Log.Warning("error creating cpu profile: %s", err) } else { defer f.Close() d.G().Log.Debug("+ starting service cpu profile in %s", cpu) err := pprof.StartCPUProfile(f) if err != nil { d.G().Log.Warning("error starting CPU profile: %s", err) } } } tr := os.Getenv("KEYBASE_SVCTRACE") if tr != "" { f, err := os.Create(tr) if err != nil { d.G().Log.Warning("error creating service trace: %s", err) } else { d.G().Log.Debug("+ starting service trace: %s", tr) err := trace.Start(f) if err != nil { d.G().Log.Warning("error starting trace: %s", err) } } } } func (d *Service) stopProfile() { if os.Getenv("KEYBASE_CPUPROFILE") != "" { d.G().Log.Debug("stopping cpu profile") pprof.StopCPUProfile() } if os.Getenv("KEYBASE_SVCTRACE") != "" { d.G().Log.Debug("stopping service execution trace") trace.Stop() } mem := os.Getenv("KEYBASE_MEMPROFILE") if mem == "" { return } f, err := os.Create(mem) if err != nil { d.G().Log.Warning("could not create memory profile: %s", err) return } defer f.Close() runtime.GC() // get up-to-date statistics if err := pprof.WriteHeapProfile(f); err != nil { d.G().Log.Warning("could not write memory profile: %s", err) } d.G().Log.Debug("wrote memory profile %s", mem) var mems runtime.MemStats runtime.ReadMemStats(&mems) d.G().Log.Debug("runtime mem alloc: %v", mems.Alloc) d.G().Log.Debug("runtime total alloc: %v", mems.TotalAlloc) d.G().Log.Debug("runtime heap alloc: %v", mems.HeapAlloc) d.G().Log.Debug("runtime heap sys: %v", mems.HeapSys) } func (d *Service) StartStandaloneChat(g *libkb.GlobalContext) error { g.ConnectionManager = libkb.NewConnectionManager() g.NotifyRouter = libkb.NewNotifyRouter(g) d.SetupChatModules(nil) d.startupGregor() d.startChatModules() return nil } func assertLoggedIn(ctx context.Context, g *libkb.GlobalContext) error { loggedIn := g.ActiveDevice.Valid() if !loggedIn { return libkb.LoginRequiredError{} } return nil }