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 }