github.com/mutagen-io/mutagen@v0.18.0-rc1/cmd/mutagen/daemon/run.go (about)

     1  package daemon
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/fs"
     7  	"os"
     8  	"os/signal"
     9  
    10  	"github.com/spf13/cobra"
    11  
    12  	"google.golang.org/grpc"
    13  
    14  	"github.com/mutagen-io/mutagen/cmd"
    15  
    16  	"github.com/mutagen-io/mutagen/pkg/daemon"
    17  	"github.com/mutagen-io/mutagen/pkg/forwarding"
    18  	"github.com/mutagen-io/mutagen/pkg/grpcutil"
    19  	"github.com/mutagen-io/mutagen/pkg/ipc"
    20  	"github.com/mutagen-io/mutagen/pkg/logging"
    21  	daemonsvc "github.com/mutagen-io/mutagen/pkg/service/daemon"
    22  	forwardingsvc "github.com/mutagen-io/mutagen/pkg/service/forwarding"
    23  	promptingsvc "github.com/mutagen-io/mutagen/pkg/service/prompting"
    24  	synchronizationsvc "github.com/mutagen-io/mutagen/pkg/service/synchronization"
    25  	"github.com/mutagen-io/mutagen/pkg/synchronization"
    26  )
    27  
    28  // runMain is the entry point for the run command.
    29  func runMain(_ *cobra.Command, _ []string) error {
    30  	// Attempt to acquire the daemon lock and defer its release.
    31  	lock, err := daemon.AcquireLock()
    32  	if err != nil {
    33  		return fmt.Errorf("unable to acquire daemon lock: %w", err)
    34  	}
    35  	defer lock.Release()
    36  
    37  	// Create a channel to track termination signals. We do this before creating
    38  	// and starting other infrastructure so that we can ensure things terminate
    39  	// smoothly, not mid-initialization.
    40  	signalTermination := make(chan os.Signal, 1)
    41  	signal.Notify(signalTermination, cmd.TerminationSignals...)
    42  
    43  	// Create the root logger.
    44  	logLevel := logging.LevelInfo
    45  	if envLogLevel := os.Getenv("MUTAGEN_LOG_LEVEL"); envLogLevel != "" {
    46  		if l, ok := logging.NameToLevel(envLogLevel); !ok {
    47  			return fmt.Errorf("invalid log level specified in environment: %s", envLogLevel)
    48  		} else {
    49  			logLevel = l
    50  		}
    51  	}
    52  	logger := logging.NewLogger(logLevel, os.Stderr)
    53  
    54  	// Create a forwarding session manager and defer its shutdown.
    55  	forwardingManager, err := forwarding.NewManager(logger.Sublogger("forward"))
    56  	if err != nil {
    57  		return fmt.Errorf("unable to create forwarding session manager: %w", err)
    58  	}
    59  	defer forwardingManager.Shutdown()
    60  
    61  	// Create a synchronization session manager and defer its shutdown.
    62  	synchronizationManager, err := synchronization.NewManager(logger.Sublogger("sync"))
    63  	if err != nil {
    64  		return fmt.Errorf("unable to create synchronization session manager: %w", err)
    65  	}
    66  	defer synchronizationManager.Shutdown()
    67  
    68  	// Create the gRPC server and defer its termination. We use a hard stop
    69  	// rather than a graceful stop so that it doesn't hang on open requests.
    70  	server := grpc.NewServer(
    71  		grpc.MaxSendMsgSize(grpcutil.MaximumMessageSize),
    72  		grpc.MaxRecvMsgSize(grpcutil.MaximumMessageSize),
    73  	)
    74  	defer server.Stop()
    75  
    76  	// Create the daemon server, defer its shutdown, and register it.
    77  	daemonServer := daemonsvc.NewServer()
    78  	defer daemonServer.Shutdown()
    79  	daemonsvc.RegisterDaemonServer(server, daemonServer)
    80  
    81  	// Create and register the prompt server.
    82  	promptingsvc.RegisterPromptingServer(server, promptingsvc.NewServer())
    83  
    84  	// Create and register the forwarding server.
    85  	forwardingServer := forwardingsvc.NewServer(forwardingManager)
    86  	forwardingsvc.RegisterForwardingServer(server, forwardingServer)
    87  
    88  	// Create and register the synchronization server.
    89  	synchronizationServer := synchronizationsvc.NewServer(synchronizationManager)
    90  	synchronizationsvc.RegisterSynchronizationServer(server, synchronizationServer)
    91  
    92  	// Compute the path to the daemon IPC endpoint.
    93  	endpoint, err := daemon.EndpointPath()
    94  	if err != nil {
    95  		return fmt.Errorf("unable to compute endpoint path: %w", err)
    96  	}
    97  
    98  	// Create the daemon listener and defer its closure. Since we hold the
    99  	// daemon lock, we preemptively remove any existing socket since it should
   100  	// be stale.
   101  	if err := os.Remove(endpoint); err != nil && !errors.Is(err, fs.ErrNotExist) {
   102  		return fmt.Errorf("unable to remove existing daemon endpoint: %w", err)
   103  	}
   104  	listener, err := ipc.NewListener(endpoint)
   105  	if err != nil {
   106  		return fmt.Errorf("unable to create daemon listener: %w", err)
   107  	}
   108  	defer listener.Close()
   109  
   110  	// Serve incoming requests and watch for server failure.
   111  	serverErrors := make(chan error, 1)
   112  	go func() {
   113  		serverErrors <- server.Serve(listener)
   114  	}()
   115  
   116  	// Wait for termination from a signal, the daemon service, or the gRPC
   117  	// server. We treat termination via the daemon service as a non-error.
   118  	select {
   119  	case s := <-signalTermination:
   120  		logger.Info("Terminating due to signal:", s)
   121  		return fmt.Errorf("terminated by signal: %s", s)
   122  	case <-daemonServer.Termination:
   123  		logger.Info("Daemon termination requested")
   124  		return nil
   125  	case err = <-serverErrors:
   126  		logger.Error("Daemon server failure:", err)
   127  		return fmt.Errorf("daemon server termination: %w", err)
   128  	}
   129  }
   130  
   131  // runCommand is the run command.
   132  var runCommand = &cobra.Command{
   133  	Use:          "run",
   134  	Short:        "Run the Mutagen daemon",
   135  	Args:         cmd.DisallowArguments,
   136  	Hidden:       true,
   137  	RunE:         runMain,
   138  	SilenceUsage: true,
   139  }
   140  
   141  // runConfiguration stores configuration for the run command.
   142  var runConfiguration struct {
   143  	// help indicates whether or not to show help information and exit.
   144  	help bool
   145  }
   146  
   147  func init() {
   148  	// Grab a handle for the command line flags.
   149  	flags := runCommand.Flags()
   150  
   151  	// Disable alphabetical sorting of flags in help output.
   152  	flags.SortFlags = false
   153  
   154  	// Manually add a help flag to override the default message. Cobra will
   155  	// still implement its logic automatically.
   156  	flags.BoolVarP(&runConfiguration.help, "help", "h", false, "Show help information")
   157  }