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

     1  // Copyright 2019 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package service
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"time"
    14  
    15  	"golang.org/x/net/context"
    16  
    17  	"github.com/keybase/client/go/engine"
    18  	"github.com/keybase/client/go/install"
    19  	"github.com/keybase/client/go/libkb"
    20  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    21  	"github.com/keybase/client/go/status"
    22  	"github.com/keybase/go-framed-msgpack-rpc/rpc"
    23  	jsonw "github.com/keybase/go-jsonw"
    24  )
    25  
    26  type ConfigHandler struct {
    27  	libkb.Contextified
    28  	xp     rpc.Transporter
    29  	svc    *Service
    30  	connID libkb.ConnectionID
    31  }
    32  
    33  var _ keybase1.ConfigInterface = (*ConfigHandler)(nil)
    34  
    35  func NewConfigHandler(xp rpc.Transporter, i libkb.ConnectionID, g *libkb.GlobalContext, svc *Service) *ConfigHandler {
    36  	return &ConfigHandler{
    37  		Contextified: libkb.NewContextified(g),
    38  		xp:           xp,
    39  		svc:          svc,
    40  		connID:       i,
    41  	}
    42  }
    43  
    44  func (h ConfigHandler) GetCurrentStatus(ctx context.Context, sessionID int) (res keybase1.CurrentStatus, err error) {
    45  	mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("CFG")
    46  	return status.GetCurrentStatus(mctx)
    47  }
    48  
    49  func (h ConfigHandler) GuiGetValue(ctx context.Context, path string) (ret keybase1.ConfigValue, err error) {
    50  	return h.getValue(ctx, path, h.G().Env.GetGUIConfig())
    51  }
    52  
    53  func (h ConfigHandler) GetValue(ctx context.Context, path string) (ret keybase1.ConfigValue, err error) {
    54  	return h.getValue(ctx, path, h.G().Env.GetConfig())
    55  }
    56  
    57  func (h ConfigHandler) getValue(_ context.Context, path string, reader libkb.JSONReader) (ret keybase1.ConfigValue, err error) {
    58  	var i interface{}
    59  	i, err = reader.GetInterfaceAtPath(path)
    60  	if err != nil {
    61  		return ret, err
    62  	}
    63  	if i == nil {
    64  		ret.IsNull = true
    65  	} else {
    66  		switch v := i.(type) {
    67  		case int:
    68  			ret.I = &v
    69  		case string:
    70  			ret.S = &v
    71  		case bool:
    72  			ret.B = &v
    73  		case float64:
    74  			tmp := int(v)
    75  			ret.I = &tmp
    76  		default:
    77  			var b []byte
    78  			b, err = json.Marshal(v)
    79  			if err == nil {
    80  				tmp := string(b)
    81  				ret.O = &tmp
    82  			}
    83  		}
    84  	}
    85  	return ret, err
    86  }
    87  
    88  func (h ConfigHandler) GuiSetValue(ctx context.Context, arg keybase1.GuiSetValueArg) (err error) {
    89  	return h.setValue(ctx, keybase1.SetValueArg(arg), h.G().Env.GetGUIConfig())
    90  }
    91  
    92  func (h ConfigHandler) SetValue(ctx context.Context, arg keybase1.SetValueArg) (err error) {
    93  	return h.setValue(ctx, arg, h.G().Env.GetConfigWriter())
    94  }
    95  
    96  func (h ConfigHandler) setValue(_ context.Context, arg keybase1.SetValueArg, w libkb.JSONWriter) (err error) {
    97  	if arg.Path == "users" {
    98  		err = fmt.Errorf("The field 'users' cannot be edited for fear of config corruption")
    99  		return err
   100  	}
   101  	switch {
   102  	case arg.Value.IsNull:
   103  		err = w.SetNullAtPath(arg.Path)
   104  	case arg.Value.S != nil:
   105  		err = w.SetStringAtPath(arg.Path, *arg.Value.S)
   106  	case arg.Value.I != nil:
   107  		err = w.SetIntAtPath(arg.Path, *arg.Value.I)
   108  	case arg.Value.F != nil:
   109  		err = w.SetFloatAtPath(arg.Path, *arg.Value.F)
   110  	case arg.Value.B != nil:
   111  		err = w.SetBoolAtPath(arg.Path, *arg.Value.B)
   112  	case arg.Value.O != nil:
   113  		var jw *jsonw.Wrapper
   114  		jw, err = jsonw.Unmarshal([]byte(*arg.Value.O))
   115  		if err == nil {
   116  			err = w.SetWrapperAtPath(arg.Path, jw)
   117  		}
   118  	default:
   119  		err = fmt.Errorf("Bad type for setting a value")
   120  	}
   121  	if err == nil {
   122  		reloadErr := h.G().ConfigReload()
   123  		if reloadErr != nil {
   124  			h.G().Log.Debug("setValue: error reloading: %+v", reloadErr)
   125  		}
   126  	}
   127  	return err
   128  }
   129  
   130  func (h ConfigHandler) GuiClearValue(ctx context.Context, path string) error {
   131  	return h.clearValue(ctx, path, h.G().Env.GetGUIConfig())
   132  }
   133  
   134  func (h ConfigHandler) ClearValue(ctx context.Context, path string) error {
   135  	return h.clearValue(ctx, path, h.G().Env.GetConfigWriter())
   136  }
   137  
   138  func (h ConfigHandler) clearValue(_ context.Context, path string, w libkb.JSONWriter) error {
   139  	w.DeleteAtPath(path)
   140  	return h.G().ConfigReload()
   141  }
   142  
   143  func (h ConfigHandler) GetClientStatus(ctx context.Context, sessionID int) (res []keybase1.ClientStatus, err error) {
   144  	mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("CFG")
   145  	defer mctx.Trace("GetClientStatus", &err)()
   146  	return libkb.GetClientStatus(mctx), nil
   147  }
   148  
   149  func (h ConfigHandler) GetConfig(ctx context.Context, sessionID int) (res keybase1.Config, err error) {
   150  	mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("CFG")
   151  	defer mctx.Trace("GetConfig", &err)()
   152  	forkType := keybase1.ForkType_NONE
   153  	if h.svc != nil {
   154  		forkType = h.svc.ForkType
   155  	}
   156  	return status.GetConfig(mctx, forkType)
   157  }
   158  
   159  func (h ConfigHandler) GetFullStatus(ctx context.Context, sessionID int) (res *keybase1.FullStatus, err error) {
   160  	mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("CFG")
   161  	defer mctx.Trace("GetFullStatus", &err)()
   162  	return status.GetFullStatus(mctx)
   163  }
   164  
   165  func (h ConfigHandler) IsServiceRunning(ctx context.Context, sessionID int) (res bool, err error) {
   166  	mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("CFG")
   167  	defer mctx.Trace("IsServiceRunning", &err)()
   168  
   169  	// set service status
   170  	if mctx.G().Env.GetStandalone() {
   171  		res = false
   172  	} else {
   173  		res = true
   174  	}
   175  	return
   176  }
   177  
   178  func (h ConfigHandler) IsKBFSRunning(ctx context.Context, sessionID int) (res bool, err error) {
   179  	mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("CFG")
   180  	defer mctx.Trace("IsKBFSRunning", &err)()
   181  
   182  	clients := libkb.GetClientStatus(mctx)
   183  
   184  	kbfs := status.GetFirstClient(clients, keybase1.ClientType_KBFS)
   185  
   186  	return kbfs != nil, nil
   187  }
   188  
   189  func (h ConfigHandler) GetNetworkStats(ctx context.Context, arg keybase1.GetNetworkStatsArg) (res []keybase1.InstrumentationStat, err error) {
   190  	mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("CFG")
   191  	defer mctx.Trace("GetNetworkStats", &err)()
   192  	switch arg.NetworkSrc {
   193  	case keybase1.NetworkSource_LOCAL:
   194  		return mctx.G().LocalNetworkInstrumenterStorage.Stats(ctx)
   195  	case keybase1.NetworkSource_REMOTE:
   196  		return mctx.G().RemoteNetworkInstrumenterStorage.Stats(ctx)
   197  	default:
   198  		return nil, fmt.Errorf("Unknown network source %d", arg.NetworkSrc)
   199  	}
   200  }
   201  
   202  func (h ConfigHandler) LogSend(ctx context.Context, arg keybase1.LogSendArg) (res keybase1.LogSendID, err error) {
   203  	mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("CFG")
   204  	defer mctx.Trace("LogSend", &err)()
   205  
   206  	fstatus, err := status.GetFullStatus(mctx)
   207  	if err != nil {
   208  		return "", err
   209  	}
   210  	statusJSON := status.MergeStatusJSON(fstatus, "fstatus", arg.StatusJSON)
   211  
   212  	numBytes := status.LogSendDefaultBytesDesktop
   213  	if arg.SendMaxBytes {
   214  		numBytes = status.LogSendMaxBytes
   215  	}
   216  
   217  	// pass empty networkStatsJSON here since we call LogSend with addNetworkStats=true below
   218  	logSendContext := status.NewLogSendContext(h.G(), fstatus, statusJSON, "", arg.Feedback)
   219  	return logSendContext.LogSend(arg.SendLogs, numBytes,
   220  		false /* mergeExtendedStatus */, true /* addNetworkStats */)
   221  }
   222  
   223  func (h ConfigHandler) GetAllProvisionedUsernames(ctx context.Context, sessionID int) (res keybase1.AllProvisionedUsernames, err error) {
   224  	defaultUsername, all, err := libkb.GetAllProvisionedUsernames(libkb.NewMetaContext(ctx, h.G()).WithLogTag("CFG"))
   225  	if err != nil {
   226  		return res, err
   227  	}
   228  
   229  	// If the default is missing, fill it in from the first provisioned.
   230  	if defaultUsername.IsNil() && len(all) > 0 {
   231  		defaultUsername = all[0]
   232  	}
   233  	hasProvisionedUser := !defaultUsername.IsNil()
   234  
   235  	// Callers expect ProvisionedUsernames to contain the DefaultUsername, so
   236  	// we ensure it is here as a final sanity check before returning.
   237  	hasDefaultUsername := false
   238  	provisionedUsernames := []string{}
   239  	for _, username := range all {
   240  		provisionedUsernames = append(provisionedUsernames, username.String())
   241  		hasDefaultUsername = hasDefaultUsername || username.Eq(defaultUsername)
   242  	}
   243  
   244  	if !hasDefaultUsername && hasProvisionedUser {
   245  		provisionedUsernames = append(provisionedUsernames, defaultUsername.String())
   246  	}
   247  
   248  	return keybase1.AllProvisionedUsernames{
   249  		DefaultUsername:      defaultUsername.String(),
   250  		ProvisionedUsernames: provisionedUsernames,
   251  		HasProvisionedUser:   hasProvisionedUser,
   252  	}, nil
   253  }
   254  
   255  func (h ConfigHandler) SetUserConfig(ctx context.Context, arg keybase1.SetUserConfigArg) (err error) {
   256  	eng := engine.NewUserConfigEngine(h.G(), &engine.UserConfigEngineArg{
   257  		Key:   arg.Key,
   258  		Value: arg.Value,
   259  	})
   260  	m := libkb.NewMetaContext(ctx, h.G())
   261  	err = engine.RunEngine2(m, eng)
   262  	if err != nil {
   263  		return err
   264  	}
   265  	return nil
   266  }
   267  
   268  func (h ConfigHandler) SetPath(_ context.Context, arg keybase1.SetPathArg) error {
   269  	h.G().Log.Debug("SetPath calling mergeIntoPath(%s)", arg.Path)
   270  	return mergeIntoPath(h.G(), arg.Path)
   271  }
   272  
   273  func mergeIntoPath(g *libkb.GlobalContext, p2 string) error {
   274  
   275  	svcPath := os.Getenv("PATH")
   276  	g.Log.Debug("mergeIntoPath: service path = %s", svcPath)
   277  	g.Log.Debug("mergeIntoPath: merge path   = %s", p2)
   278  
   279  	pathenv := filepath.SplitList(svcPath)
   280  	pathset := make(map[string]bool)
   281  	for _, p := range pathenv {
   282  		pathset[p] = true
   283  	}
   284  
   285  	var clientAdditions []string
   286  	for _, dir := range filepath.SplitList(p2) {
   287  		if _, ok := pathset[dir]; ok {
   288  			continue
   289  		}
   290  		clientAdditions = append(clientAdditions, dir)
   291  	}
   292  
   293  	pathenv = append(pathenv, clientAdditions...)
   294  	combined := strings.Join(pathenv, string(os.PathListSeparator))
   295  
   296  	if combined == svcPath {
   297  		g.Log.Debug("No path changes needed")
   298  		return nil
   299  	}
   300  
   301  	g.Log.Debug("mergeIntoPath: merged path = %s", combined)
   302  	os.Setenv("PATH", combined)
   303  	return nil
   304  }
   305  
   306  func (h ConfigHandler) HelloIAm(_ context.Context, arg keybase1.ClientDetails) error {
   307  	arg.Redact()
   308  	h.G().Log.Debug("HelloIAm: %d - %v", h.connID, arg)
   309  	return h.G().ConnectionManager.Label(h.connID, arg)
   310  }
   311  
   312  func (h ConfigHandler) CheckAPIServerOutOfDateWarning(_ context.Context) (keybase1.OutOfDateInfo, error) {
   313  	return h.G().GetOutOfDateInfo(), nil
   314  }
   315  
   316  func (h ConfigHandler) GetUpdateInfo(ctx context.Context) (res keybase1.UpdateInfo, err error) {
   317  	mctx := libkb.NewMetaContext(ctx, h.G())
   318  	defer mctx.Trace("GetUpdateInfo", &err)()
   319  	outOfDateInfo := h.G().GetOutOfDateInfo()
   320  	if len(outOfDateInfo.UpgradeTo) != 0 {
   321  		// This is from the API server. Consider client critically out of date
   322  		// if we are asked to upgrade by the API server.
   323  		return keybase1.UpdateInfo{
   324  			Status:  keybase1.UpdateInfoStatus_CRITICALLY_OUT_OF_DATE,
   325  			Message: outOfDateInfo.CustomMessage,
   326  		}, nil
   327  	}
   328  	needUpdate, err := install.GetNeedUpdate() // This is from the updater.
   329  	if err != nil {
   330  		return keybase1.UpdateInfo{
   331  			Status: keybase1.UpdateInfoStatus_UP_TO_DATE,
   332  		}, err
   333  	}
   334  	if needUpdate {
   335  		return keybase1.UpdateInfo{
   336  			Status: keybase1.UpdateInfoStatus_NEED_UPDATE,
   337  		}, nil
   338  	}
   339  	return keybase1.UpdateInfo{
   340  		Status: keybase1.UpdateInfoStatus_UP_TO_DATE,
   341  	}, nil
   342  }
   343  
   344  func (h ConfigHandler) StartUpdateIfNeeded(ctx context.Context) error {
   345  	return install.StartUpdateIfNeeded(ctx, h.G().Log)
   346  }
   347  
   348  func (h ConfigHandler) WaitForClient(_ context.Context, arg keybase1.WaitForClientArg) (bool, error) {
   349  	return h.G().ConnectionManager.WaitForClientType(arg.ClientType, arg.Timeout.Duration()), nil
   350  }
   351  
   352  func (h ConfigHandler) GetBootstrapStatus(ctx context.Context, sessionID int) (keybase1.BootstrapStatus, error) {
   353  	eng := engine.NewBootstrap(h.G())
   354  	m := libkb.NewMetaContext(ctx, h.G())
   355  	if err := engine.RunEngine2(m, eng); err != nil {
   356  		return keybase1.BootstrapStatus{}, err
   357  	}
   358  	status := eng.Status()
   359  	h.G().Log.CDebugf(ctx, "GetBootstrapStatus: attempting to get HTTP server address")
   360  	for i := 0; i < 40; i++ { // wait at most 2 seconds
   361  		addr, err := h.svc.httpSrv.Addr()
   362  		if err != nil {
   363  			h.G().Log.CDebugf(ctx, "GetBootstrapStatus: failed to get HTTP server address: %s", err)
   364  		} else {
   365  			h.G().Log.CDebugf(ctx, "GetBootstrapStatus: http server: addr: %s token: %s", addr,
   366  				h.svc.httpSrv.Token())
   367  			status.HttpSrvInfo = &keybase1.HttpSrvInfo{
   368  				Address: addr,
   369  				Token:   h.svc.httpSrv.Token(),
   370  			}
   371  			break
   372  		}
   373  		time.Sleep(50 * time.Millisecond)
   374  	}
   375  	if status.HttpSrvInfo == nil {
   376  		h.G().Log.CDebugf(ctx, "GetBootstrapStatus: failed to get HTTP srv info after max attempts")
   377  	}
   378  	return status, nil
   379  }
   380  
   381  func (h ConfigHandler) RequestFollowingAndUnverifiedFollowers(ctx context.Context, sessionID int) error {
   382  	if err := assertLoggedIn(ctx, h.G()); err != nil {
   383  		return err
   384  	}
   385  	// Queue up a load for follower info
   386  	return h.svc.trackerLoader.Queue(ctx, h.G().ActiveDevice.UID())
   387  }
   388  
   389  func (h ConfigHandler) GetRememberPassphrase(ctx context.Context, sessionID int) (bool, error) {
   390  	username := h.G().Env.GetUsername()
   391  	if username.IsNil() {
   392  		h.G().Log.CDebugf(ctx, "GetRememberPassphrase: got nil username; using legacy remember_passphrase setting")
   393  	}
   394  	return h.G().Env.GetRememberPassphrase(username), nil
   395  }
   396  
   397  func (h ConfigHandler) SetRememberPassphrase(ctx context.Context, arg keybase1.SetRememberPassphraseArg) error {
   398  	m := libkb.NewMetaContext(ctx, h.G())
   399  
   400  	username := m.G().Env.GetUsername()
   401  	if username.IsNil() {
   402  		m.Debug("SetRememberPassphrase: got nil username; using legacy remember_passphrase setting")
   403  	}
   404  	remember, err := h.GetRememberPassphrase(ctx, arg.SessionID)
   405  	if err != nil {
   406  		return err
   407  	}
   408  	if remember == arg.Remember {
   409  		m.Debug("SetRememberPassphrase: no change necessary (remember = %v)", remember)
   410  		return nil
   411  	}
   412  
   413  	// set the config variable
   414  	w := h.G().Env.GetConfigWriter()
   415  	if err := w.SetRememberPassphrase(username, arg.Remember); err != nil {
   416  		return err
   417  	}
   418  	err = h.G().ConfigReload()
   419  	if err != nil {
   420  		return err
   421  	}
   422  
   423  	if err := h.G().ReplaceSecretStore(ctx); err != nil {
   424  		m.Debug("error replacing secret store for SetRememberPassphrase(%v): %s", arg.Remember, err)
   425  		return err
   426  	}
   427  
   428  	m.Debug("SetRememberPassphrase(%s, %v) success", username.String(), arg.Remember)
   429  
   430  	return nil
   431  }
   432  
   433  type rawGetPkgCheck struct {
   434  	Status libkb.AppStatus      `json:"status"`
   435  	Res    keybase1.UpdateInfo2 `json:"res"`
   436  }
   437  
   438  func (r *rawGetPkgCheck) GetAppStatus() *libkb.AppStatus {
   439  	return &r.Status
   440  }
   441  
   442  func (h ConfigHandler) GetUpdateInfo2(ctx context.Context, arg keybase1.GetUpdateInfo2Arg) (res keybase1.UpdateInfo2, err error) {
   443  	m := libkb.NewMetaContext(ctx, h.G())
   444  
   445  	var version string
   446  	var platform string
   447  
   448  	if arg.Platform != nil {
   449  		platform = *arg.Platform
   450  	} else {
   451  		platform = libkb.GetPlatformString()
   452  	}
   453  	if arg.Version != nil {
   454  		version = *arg.Version
   455  	} else {
   456  		version = libkb.VersionString()
   457  	}
   458  
   459  	apiArg := libkb.APIArg{
   460  		Endpoint: "pkg/check",
   461  		Args: libkb.HTTPArgs{
   462  			"version":  libkb.S{Val: version},
   463  			"platform": libkb.S{Val: platform},
   464  		},
   465  		RetryCount: 3,
   466  	}
   467  	var raw rawGetPkgCheck
   468  	if err = m.G().API.GetDecode(m, apiArg, &raw); err != nil {
   469  		return res, err
   470  	}
   471  	return raw.Res, nil
   472  }
   473  
   474  func (h ConfigHandler) GetProxyData(ctx context.Context) (keybase1.ProxyData, error) {
   475  	config := h.G().Env.GetConfig()
   476  	proxyAddress := config.GetProxy()
   477  	proxyType := libkb.ProxyTypeStrToEnumFunc(config.GetProxyType())
   478  	certPinning := config.IsCertPinningEnabled()
   479  
   480  	var convertedProxyType keybase1.ProxyType
   481  	if proxyType == libkb.NoProxy {
   482  		convertedProxyType = keybase1.ProxyType_No_Proxy
   483  	} else if proxyType == libkb.HTTPConnect {
   484  		convertedProxyType = keybase1.ProxyType_HTTP_Connect
   485  	} else if proxyType == libkb.Socks {
   486  		convertedProxyType = keybase1.ProxyType_Socks
   487  	} else {
   488  		return keybase1.ProxyData{AddressWithPort: "", ProxyType: keybase1.ProxyType_No_Proxy, CertPinning: true},
   489  			fmt.Errorf("Failed to convert proxy type into a protocol compatible proxy type!")
   490  	}
   491  
   492  	return keybase1.ProxyData{AddressWithPort: proxyAddress, ProxyType: convertedProxyType, CertPinning: certPinning}, nil
   493  }
   494  
   495  func (h ConfigHandler) SetProxyData(ctx context.Context, arg keybase1.ProxyData) error {
   496  	configWriter := h.G().Env.GetConfigWriter()
   497  
   498  	rpcProxyType := arg.ProxyType
   499  
   500  	var convertedProxyType libkb.ProxyType
   501  	if rpcProxyType == keybase1.ProxyType_No_Proxy {
   502  		convertedProxyType = libkb.NoProxy
   503  	} else if rpcProxyType == keybase1.ProxyType_HTTP_Connect {
   504  		convertedProxyType = libkb.HTTPConnect
   505  	} else if rpcProxyType == keybase1.ProxyType_Socks {
   506  		convertedProxyType = libkb.Socks
   507  	} else {
   508  		// Got a bogus proxy type that we couldn't convert to a libkb enum so return an error
   509  		return fmt.Errorf("failed to convert given proxy type to a native libkb proxy type")
   510  	}
   511  
   512  	proxyTypeStr, ok := libkb.ProxyTypeEnumToStr[convertedProxyType]
   513  
   514  	if !ok {
   515  		// Got a bogus proxy type that we couldn't convert to a string
   516  		return fmt.Errorf("failed to convert proxy type into a string")
   517  	}
   518  
   519  	err := configWriter.SetStringAtPath("proxy", arg.AddressWithPort)
   520  	if err != nil {
   521  		return err
   522  	}
   523  	err = configWriter.SetBoolAtPath("disable-cert-pinning", !arg.CertPinning)
   524  	if err != nil {
   525  		return err
   526  	}
   527  	err = configWriter.SetStringAtPath("proxy-type", proxyTypeStr)
   528  	if err != nil {
   529  		return err
   530  	}
   531  
   532  	// Reload the config file in order to actually start using the proxy
   533  	err = h.G().ConfigReload()
   534  	if err != nil {
   535  		return err
   536  	}
   537  
   538  	return nil
   539  }
   540  
   541  func (h ConfigHandler) ToggleRuntimeStats(ctx context.Context) error {
   542  	configWriter := h.G().Env.GetConfigWriter()
   543  	curValue := h.G().Env.GetRuntimeStatsEnabled()
   544  	err := configWriter.SetBoolAtPath("runtime_stats_enabled", !curValue)
   545  	if err != nil {
   546  		return err
   547  	}
   548  	if err := h.G().ConfigReload(); err != nil {
   549  		return err
   550  	}
   551  	if curValue {
   552  		<-h.G().RuntimeStats.Stop(ctx)
   553  	} else {
   554  		h.G().RuntimeStats.Start(ctx)
   555  	}
   556  	return nil
   557  }
   558  
   559  func (h ConfigHandler) AppendGUILogs(ctx context.Context, content string) error {
   560  	wr := h.G().GetGUILogWriter()
   561  	_, err := io.WriteString(wr, content)
   562  	return err
   563  }
   564  
   565  func (h ConfigHandler) GenerateWebAuthToken(ctx context.Context) (ret string, err error) {
   566  	if err := assertLoggedIn(ctx, h.G()); err != nil {
   567  		return ret, err
   568  	}
   569  
   570  	nist, err := h.G().ActiveDevice.NISTWebAuthToken(ctx)
   571  	if err != nil {
   572  		return ret, err
   573  	}
   574  	if nist == nil {
   575  		return ret, fmt.Errorf("cannot generate a token when you are logged off")
   576  	}
   577  	uri := libkb.SiteURILookup[h.G().Env.GetRunMode()] + "/_/login/nist?tok=" + nist.Token().String()
   578  	return uri, nil
   579  }
   580  
   581  func (h ConfigHandler) UpdateLastLoggedInAndServerConfig(
   582  	ctx context.Context, serverConfigPath string) error {
   583  	arg := libkb.APIArg{
   584  		Endpoint:    "user/features",
   585  		SessionType: libkb.APISessionTypeREQUIRED,
   586  	}
   587  	mctx := libkb.NewMetaContext(ctx, h.G())
   588  	resp, err := h.G().API.Get(mctx, arg)
   589  	if err != nil {
   590  		return err
   591  	}
   592  	jw := resp.Body
   593  	isAdmin, err := jw.AtPath("features.admin.value").GetBool()
   594  	if err != nil {
   595  		return err
   596  	}
   597  
   598  	// Try to read from the old config file. But ignore any error and just
   599  	// create a new one.
   600  	oldBytes, err := os.ReadFile(serverConfigPath)
   601  	if err != nil {
   602  		jw = jsonw.NewDictionary()
   603  	} else if jw, err = jsonw.Unmarshal(oldBytes); err != nil {
   604  		jw = jsonw.NewDictionary()
   605  	}
   606  	username := h.G().GetEnv().GetUsername().String()
   607  	if err = jw.SetValueAtPath(fmt.Sprintf("%s.chatIndexProfilingEnabled", username), jsonw.NewBool(isAdmin)); err != nil {
   608  		return err
   609  	}
   610  	if err = jw.SetValueAtPath(fmt.Sprintf("%s.dbCleanEnabled", username), jsonw.NewBool(isAdmin)); err != nil {
   611  		return err
   612  	}
   613  	if err = jw.SetValueAtPath(fmt.Sprintf("%s.printRPCStaus", username), jsonw.NewBool(isAdmin)); err != nil {
   614  		return err
   615  	}
   616  	if err = jw.SetKey("lastLoggedInUser", jsonw.NewString(username)); err != nil {
   617  		return err
   618  	}
   619  	newBytes, err := jw.Marshal()
   620  	if err != nil {
   621  		return err
   622  	}
   623  	return libkb.NewFile(serverConfigPath, newBytes, 0644).Save(h.G().Log)
   624  }