github.com/keybase/client/go@v0.0.0-20240520164431-4f512a4c85a3/client/fork_server.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 client
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"os/exec"
    10  	"time"
    11  
    12  	"github.com/keybase/cli"
    13  	"github.com/keybase/client/go/libcmdline"
    14  	"github.com/keybase/client/go/libkb"
    15  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    16  	"github.com/keybase/client/go/service"
    17  )
    18  
    19  // GetExtraFlags gets the extra fork-related flags for this platform
    20  func GetExtraFlags() []cli.Flag {
    21  	return []cli.Flag{
    22  		cli.BoolFlag{
    23  			Name:  "auto-fork",
    24  			Usage: "Enable auto-fork of background service.",
    25  		},
    26  		cli.BoolFlag{
    27  			Name:  "no-auto-fork, F",
    28  			Usage: "Disable auto-fork of background service.",
    29  		},
    30  	}
    31  }
    32  
    33  // AutoForkServer just forks the server and sets the autoFork flag to true
    34  func AutoForkServer(g *libkb.GlobalContext, cl libkb.CommandLine) (bool, error) {
    35  	return ForkServer(g, cl, keybase1.ForkType_AUTO)
    36  }
    37  
    38  func spawnServer(g *libkb.GlobalContext, cl libkb.CommandLine, forkType keybase1.ForkType) (pid int, err error) {
    39  	// If we're running under systemd, start the service as a user unit instead
    40  	// of forking it directly. We do this here in the generic auto-fork branch,
    41  	// rather than a higher-level systemd branch, because we want to handle the
    42  	// case where the service was previously autoforked, and then the user
    43  	// upgrades their keybase package to a version with systemd support. The
    44  	// flock-checking code will short-circuit before we get here, if the
    45  	// service is running, so we don't have to worry about a conflict.
    46  	//
    47  	// We only do this in prod mode, because keybase.service always starts
    48  	// /usr/bin/keybase, which is probably not what you want if you're
    49  	// autoforking in dev mode. To run the service you just built in prod mode,
    50  	// you can either do `keybase --run-mode=prod service` manually, or you can
    51  	// add a systemd override file (see https://askubuntu.com/q/659267/73244).
    52  	if g.Env.WantsSystemd() {
    53  		g.Log.Info("Starting keybase.service.")
    54  		// Prefer "restart" to "start" so that we don't race against shutdown.
    55  		startCmd := exec.Command("systemctl", "--user", "restart", "keybase.service")
    56  		startCmd.Stdout = os.Stderr
    57  		startCmd.Stderr = os.Stderr
    58  		err = startCmd.Run()
    59  		if err != nil {
    60  			g.Log.Error("Failed to start keybase.service.")
    61  		}
    62  		return
    63  	}
    64  
    65  	cmd, args, err := makeServerCommandLine(g, cl, forkType)
    66  	if err != nil {
    67  		return
    68  	}
    69  
    70  	pid, err = libcmdline.SpawnDetachedProcess(cmd, args, g.Log)
    71  	if err != nil {
    72  		err = fmt.Errorf("Error spawning background process: %s", err)
    73  	} else {
    74  		g.Log.Info("Starting background server with pid=%d", pid)
    75  	}
    76  
    77  	return pid, err
    78  }
    79  
    80  // ForkServer forks a new background Keybase service, and waits until it's
    81  // pingable. It will only do something useful on Unixes; it won't work on
    82  // Windows (probably?). Returns an error if anything bad happens; otherwise,
    83  // assume that the server was successfully started up. Returns (true, nil) if
    84  // the server was actually forked, or (false, nil) if it was previously up.
    85  func ForkServer(g *libkb.GlobalContext, cl libkb.CommandLine, forkType keybase1.ForkType) (bool, error) {
    86  	srv := service.NewService(g, true /* isDaemon */)
    87  	forked := false
    88  
    89  	// If we try to get an exclusive lock and succeed, it means we
    90  	// need to relaunch the daemon since it's dead
    91  	g.Log.Debug("Getting flock")
    92  	err := srv.GetExclusiveLockWithoutAutoUnlock()
    93  	if err == nil {
    94  		g.Log.Debug("Flocked! Server must have died")
    95  		mctx := libkb.NewMetaContextTODO(g)
    96  		err := srv.ReleaseLock(mctx)
    97  		if err != nil {
    98  			return false, err
    99  		}
   100  		_, err = spawnServer(g, cl, forkType)
   101  		if err != nil {
   102  			g.Log.Errorf("Error in spawning server process: %s", err)
   103  			return false, err
   104  		}
   105  		err = pingLoop(g)
   106  		if err != nil {
   107  			g.Log.Errorf("Ping failure after server fork: %s", err)
   108  			return false, err
   109  		}
   110  		forked = true
   111  	} else {
   112  		g.Log.Debug("The server is still up")
   113  		err = nil
   114  	}
   115  
   116  	return forked, err
   117  }
   118  
   119  func pingLoop(g *libkb.GlobalContext) error {
   120  	var err error
   121  	for i := 0; i < 20; i++ {
   122  		_, err = getSocketWithRetry(g)
   123  		if err == nil {
   124  			g.Log.Debug("Connected (%d)", i)
   125  			return nil
   126  		}
   127  		g.Log.Debug("Failed to connect to socket (%d): %s", i, err)
   128  		time.Sleep(200 * time.Millisecond)
   129  	}
   130  	return nil
   131  }
   132  
   133  func makeServerCommandLine(g *libkb.GlobalContext, cl libkb.CommandLine,
   134  	forkType keybase1.ForkType) (arg0 string, args []string, err error) {
   135  	// ForkExec requires an absolute path to the binary. LookPath() gets this
   136  	// for us, or correctly leaves arg0 alone if it's already a path.
   137  	arg0, err = exec.LookPath(os.Args[0])
   138  	if err != nil {
   139  		return
   140  	}
   141  
   142  	// Fixme: This isn't ideal, it would be better to specify when the args
   143  	// are defined if they should be reexported to the server, and if so, then
   144  	// we should automate the reconstruction of the argument vector.  Let's do
   145  	// this when we yank out keybase/cli
   146  	bools := []string{
   147  		"no-debug",
   148  		"api-dump-unsafe",
   149  		"plain-logging",
   150  		"disable-cert-pinning",
   151  	}
   152  
   153  	strings := []string{
   154  		"home",
   155  		"server",
   156  		"config",
   157  		"session",
   158  		"proxy",
   159  		"username",
   160  		"gpg-home",
   161  		"gpg",
   162  		"secret-keyring",
   163  		"pid-file",
   164  		"socket-file",
   165  		"gpg-options",
   166  		"local-rpc-debug-unsafe",
   167  		"run-mode",
   168  		"timers",
   169  		"tor-mode",
   170  		"tor-proxy",
   171  		"tor-hidden-address",
   172  		"proxy-type",
   173  	}
   174  	args = append(args, arg0)
   175  
   176  	// Always pass --debug to the server for more verbose logging, as other
   177  	// startup mechanisms do (launchd, run_keybase, etc). This can be
   178  	// overridden with --no-debug though.
   179  	args = append(args, "--debug")
   180  
   181  	for _, b := range bools {
   182  		if isSet, isTrue := cl.GetBool(b, true); isSet && isTrue {
   183  			args = append(args, "--"+b)
   184  		}
   185  	}
   186  
   187  	for _, s := range strings {
   188  		if v := cl.GetGString(s); len(v) > 0 {
   189  			args = append(args, "--"+s, v)
   190  		}
   191  	}
   192  
   193  	// If there is no explicit log file add one when autoforking.
   194  	// otherwise it was added in the previous block already.
   195  	if g.Env.GetLogFile() == "" {
   196  		args = append(args, "--log-file", g.Env.GetDefaultLogFile())
   197  	}
   198  
   199  	args = append(args, "service")
   200  
   201  	var chdir string
   202  	chdir, err = g.Env.GetServiceSpawnDir()
   203  	if err != nil {
   204  		return
   205  	}
   206  
   207  	g.Log.Debug("| Setting run directory for keybase service to %s", chdir)
   208  	args = append(args, "--chdir", chdir)
   209  
   210  	if forkType == keybase1.ForkType_AUTO {
   211  		args = append(args, "--auto-forked")
   212  	} else if forkType == keybase1.ForkType_WATCHDOG {
   213  		args = append(args, "--watchdog-forked")
   214  	} else if forkType == keybase1.ForkType_LAUNCHD {
   215  		args = append(args, "--launchd-forked")
   216  	}
   217  
   218  	g.Log.Debug("| Made server args: %s %v", arg0, args)
   219  
   220  	return
   221  }