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

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"os/signal"
     8  	"time"
     9  
    10  	"github.com/spf13/cobra"
    11  
    12  	"github.com/mutagen-io/mutagen/cmd"
    13  
    14  	"github.com/mutagen-io/mutagen/pkg/agent"
    15  	"github.com/mutagen-io/mutagen/pkg/housekeeping"
    16  	"github.com/mutagen-io/mutagen/pkg/logging"
    17  	"github.com/mutagen-io/mutagen/pkg/mutagen"
    18  	"github.com/mutagen-io/mutagen/pkg/synchronization/endpoint/remote"
    19  )
    20  
    21  const (
    22  	// housekeepingInterval is the interval at which housekeeping will be
    23  	// invoked by the agent.
    24  	housekeepingInterval = 24 * time.Hour
    25  )
    26  
    27  // housekeepRegularly is the entry point for the housekeeping Goroutine.
    28  func housekeepRegularly(ctx context.Context, logger *logging.Logger) {
    29  	// Perform an initial housekeeping operation since the ticker won't fire
    30  	// straight away.
    31  	logger.Info("Performing initial housekeeping")
    32  	housekeeping.Housekeep()
    33  
    34  	// Create a ticker to regulate housekeeping and defer its shutdown.
    35  	ticker := time.NewTicker(housekeepingInterval)
    36  	defer ticker.Stop()
    37  
    38  	// Loop and wait for the ticker or cancellation.
    39  	for {
    40  		select {
    41  		case <-ctx.Done():
    42  			return
    43  		case <-ticker.C:
    44  			logger.Info("Performing regular housekeeping")
    45  			housekeeping.Housekeep()
    46  		}
    47  	}
    48  }
    49  
    50  // synchronizerMain is the entry point for the synchronizer command.
    51  func synchronizerMain(_ *cobra.Command, _ []string) error {
    52  	// Create a channel to track termination signals. We do this before creating
    53  	// and starting other infrastructure so that we can ensure things terminate
    54  	// smoothly, not mid-initialization.
    55  	signalTermination := make(chan os.Signal, 1)
    56  	signal.Notify(signalTermination, cmd.TerminationSignals...)
    57  
    58  	// Set up a logger on the standard error stream.
    59  	logLevel := logging.LevelInfo
    60  	if synchronizerConfiguration.logLevel != "" {
    61  		if l, ok := logging.NameToLevel(synchronizerConfiguration.logLevel); !ok {
    62  			return fmt.Errorf("invalid log level specified: %s", synchronizerConfiguration.logLevel)
    63  		} else {
    64  			logLevel = l
    65  		}
    66  	}
    67  	logger := logging.NewLogger(logLevel, os.Stderr)
    68  
    69  	// Set up regular housekeeping and defer its shutdown.
    70  	ctx, cancel := context.WithCancel(context.Background())
    71  	defer cancel()
    72  	go housekeepRegularly(ctx, logger.Sublogger("housekeeping"))
    73  
    74  	// Create a stream using standard input/output.
    75  	stream := newStdioStream()
    76  
    77  	// Perform an agent handshake.
    78  	if err := agent.ServerHandshake(stream); err != nil {
    79  		return fmt.Errorf("server handshake failed: %w", err)
    80  	}
    81  
    82  	// Perform a version handshake.
    83  	if err := mutagen.ServerVersionHandshake(stream); err != nil {
    84  		return fmt.Errorf("version handshake error: %w", err)
    85  	}
    86  
    87  	// Serve a synchronizer on standard input/output and monitor for its
    88  	// termination.
    89  	synchronizationTermination := make(chan error, 1)
    90  	go func() {
    91  		synchronizationTermination <- remote.ServeEndpoint(logger, stream)
    92  	}()
    93  
    94  	// Wait for termination from a signal or the synchronizer.
    95  	select {
    96  	case s := <-signalTermination:
    97  		return fmt.Errorf("terminated by signal: %s", s)
    98  	case err := <-synchronizationTermination:
    99  		return fmt.Errorf("synchronization terminated: %w", err)
   100  	}
   101  }
   102  
   103  // synchronizerCommand is the synchronizer command.
   104  var synchronizerCommand = &cobra.Command{
   105  	Use:          agent.CommandSynchronizer,
   106  	Short:        "Run the agent in synchronizer mode",
   107  	Args:         cmd.DisallowArguments,
   108  	RunE:         synchronizerMain,
   109  	SilenceUsage: true,
   110  }
   111  
   112  // synchronizerConfiguration stores configuration for the synchronizer command.
   113  var synchronizerConfiguration struct {
   114  	// help indicates whether or not to show help information and exit.
   115  	help bool
   116  	// logLevel indicates the log level to use.
   117  	logLevel string
   118  }
   119  
   120  func init() {
   121  	// Grab a handle for the command line flags.
   122  	flags := synchronizerCommand.Flags()
   123  
   124  	// Disable alphabetical sorting of flags in help output.
   125  	flags.SortFlags = false
   126  
   127  	// Manually add a help flag to override the default message. Cobra will
   128  	// still implement its logic automatically.
   129  	flags.BoolVarP(&synchronizerConfiguration.help, "help", "h", false, "Show help information")
   130  
   131  	// Wire up logging flags.
   132  	flags.StringVar(&synchronizerConfiguration.logLevel, agent.FlagLogLevel, "", "Set the log level")
   133  }