github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/kbpagesd/main.go (about)

     1  // Copyright 2017 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"context"
     9  	"database/sql"
    10  	"flag"
    11  	"fmt"
    12  	"os"
    13  	"strings"
    14  	"time"
    15  
    16  	_ "github.com/go-sql-driver/mysql"
    17  
    18  	"github.com/keybase/client/go/kbfs/env"
    19  	"github.com/keybase/client/go/kbfs/libgit"
    20  	"github.com/keybase/client/go/kbfs/libkbfs"
    21  	"github.com/keybase/client/go/kbfs/libpages"
    22  	"github.com/keybase/client/go/kbfs/simplefs"
    23  	"github.com/keybase/client/go/kbfs/stderrutils"
    24  	"github.com/keybase/client/go/protocol/keybase1"
    25  	"github.com/keybase/go-framed-msgpack-rpc/rpc"
    26  	"go.uber.org/zap"
    27  	"go.uber.org/zap/zapcore"
    28  )
    29  
    30  var (
    31  	fProd          bool
    32  	fDiskCertCache bool
    33  	fKBFSLogFile   string
    34  	fStathatEZKey  string
    35  	fStathatPrefix string
    36  	fBlacklist     string
    37  	fMySQLDSN      string
    38  )
    39  
    40  func init() {
    41  	flag.BoolVar(&fProd, "prod", false, "disable development mode")
    42  	flag.BoolVar(&fDiskCertCache, "use-disk-cert-cache", false, "cache cert on disk")
    43  	flag.StringVar(&fKBFSLogFile, "kbfs-logfile", "kbp-kbfs.log",
    44  		"path to KBFS log file; empty means print to stdout")
    45  	flag.StringVar(&fStathatEZKey, "stathat-key", "",
    46  		"stathat EZ key for reporting stats to stathat; empty disables stathat")
    47  	flag.StringVar(&fStathatPrefix, "stathat-prefix", "kbp -",
    48  		"prefix to stathat statnames")
    49  	// TODO: hook up support in kbpagesd.
    50  	// TODO: when we make kbpagesd horizontally scalable, blacklist and
    51  	// whitelist should be dynamically configurable.
    52  	flag.StringVar(&fBlacklist, "blacklist", "",
    53  		"a comma-separated list of domains to block")
    54  	flag.StringVar(&fMySQLDSN, "mysql-dsn", "",
    55  		"enable MySQL based storage and use this as the DSN")
    56  }
    57  
    58  func newLogger(isCLI bool) (*zap.Logger, error) {
    59  	// In keybase/client/go/logger fd 2 is closed. To make sure our logger can
    60  	// log to stderr, duplicate the fd beforehand. Apparently it's important to
    61  	// call this function before any keybase/client/go/logger logging is set
    62  	// up.
    63  	stderr, err := stderrutils.DupStderr()
    64  	if err != nil {
    65  		panic(err)
    66  	}
    67  
    68  	// Zap loggers use os.Stderr by default. We could pass in stderr by making
    69  	// more boilerplate, but there's not much else we need from those. So
    70  	// override os.Stderr temporarily as a hack to inject stderr to the zap
    71  	// logger.
    72  	// TODO: replace this hack when we get logstash forwarding to work.
    73  	originalStderr := os.Stderr
    74  	os.Stderr = stderr
    75  	defer func() { os.Stderr = originalStderr }()
    76  
    77  	var loggerConfig zap.Config
    78  	if isCLI {
    79  		// The default development logger is suitable for console. Disable
    80  		// stacktrace here for less verbosity, and colorize loglevel for better
    81  		// readability.
    82  		loggerConfig = zap.NewDevelopmentConfig()
    83  		loggerConfig.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
    84  		loggerConfig.DisableStacktrace = true
    85  	} else {
    86  		// The default production logger simply logs a json object for each
    87  		// line. We override the time format to ISO8601 here to make it more
    88  		// readable and compatible.
    89  		loggerConfig = zap.NewProductionConfig()
    90  		loggerConfig.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
    91  		loggerConfig.EncoderConfig.TimeKey = "time"
    92  	}
    93  
    94  	return loggerConfig.Build()
    95  }
    96  
    97  func removeEmpty(strs []string) (ret []string) {
    98  	ret = make([]string, 0, len(strs))
    99  	for _, str := range strs {
   100  		if len(str) > 0 {
   101  			ret = append(ret, str)
   102  		}
   103  	}
   104  	return ret
   105  }
   106  
   107  func getStatsActivityStorerOrBust(
   108  	logger *zap.Logger) libpages.ActivityStatsStorer {
   109  	if len(fMySQLDSN) == 0 {
   110  		fileBasedStorer, err := libpages.NewFileBasedActivityStatsStorer(
   111  			activityStatsPath, logger)
   112  		if err != nil {
   113  			logger.Panic(
   114  				"libpages.NewFileBasedActivityStatsStorer", zap.Error(err))
   115  			return nil
   116  		}
   117  		return fileBasedStorer
   118  	}
   119  
   120  	db, err := sql.Open("mysql", fMySQLDSN)
   121  	if err != nil {
   122  		logger.Panic("open mysql", zap.Error(err))
   123  		return nil
   124  	}
   125  	mysqlStorer := libpages.NewMySQLActivityStatsStorer(db, logger)
   126  	return mysqlStorer
   127  }
   128  
   129  const activityStatsReportInterval = 5 * time.Minute
   130  const activityStatsPath = "./kbp-stats"
   131  
   132  func main() {
   133  	flag.Parse()
   134  
   135  	ctx, cancel := context.WithCancel(context.Background())
   136  
   137  	// TODO: make logstash forwarding work and use isCLI=false here if logstash
   138  	// forwarding address is set.
   139  	logger, err := newLogger(true)
   140  	if err != nil {
   141  		panic(err)
   142  	}
   143  
   144  	// Hack to make libkbfs.Init connect to prod {md,b}server all the time.
   145  	os.Setenv("KEYBASE_RUN_MODE", "prod")
   146  
   147  	kbCtx := env.NewContext()
   148  	params := libkbfs.DefaultInitParams(kbCtx)
   149  	params.EnableJournal = true
   150  	params.Debug = true
   151  	params.LogFileConfig.Path = fKBFSLogFile
   152  	params.LogFileConfig.MaxKeepFiles = 32
   153  	// Enable simpleFS in case we need to debug.
   154  	shutdownGit := func() {}
   155  	shutdownSimpleFS := func(_ context.Context) error { return nil }
   156  	createSimpleFS := func(
   157  		libkbfsCtx libkbfs.Context, config libkbfs.Config) (
   158  		rpc.Protocol, error) {
   159  		// Start autogit before the RPC connection to the service is
   160  		// fully initialized. Use a big cache since kbpages doesn't
   161  		// need memory for other stuff.
   162  		shutdownGit = libgit.StartAutogit(config, 1000)
   163  
   164  		var simplefsIface keybase1.SimpleFSInterface
   165  		simplefsIface, shutdownSimpleFS = simplefs.NewSimpleFS(
   166  			libkbfsCtx, config)
   167  		return keybase1.SimpleFSProtocol(simplefsIface), nil
   168  	}
   169  	defer func() {
   170  		err := shutdownSimpleFS(context.Background())
   171  		if err != nil {
   172  			fmt.Fprintf(os.Stderr, "Couldn't shut down SimpleFS: %+v\n", err)
   173  		}
   174  		shutdownGit()
   175  	}()
   176  
   177  	params.AdditionalProtocolCreators = []libkbfs.AdditionalProtocolCreator{
   178  		createSimpleFS,
   179  	}
   180  
   181  	kbfsLog, err := libkbfs.InitLog(params, kbCtx)
   182  	if err != nil {
   183  		logger.Panic("libkbfs.InitLog", zap.Error(err))
   184  	}
   185  	cancelWrapper := func() error {
   186  		cancel()
   187  		return nil
   188  	}
   189  	kbConfig, err := libkbfs.Init(
   190  		ctx, kbCtx, params, nil, cancelWrapper, kbfsLog)
   191  	if err != nil {
   192  		logger.Panic("libkbfs.Init", zap.Error(err))
   193  	}
   194  
   195  	var statsReporter libpages.StatsReporter
   196  	if len(fStathatEZKey) != 0 {
   197  		activityStorer := getStatsActivityStorerOrBust(logger)
   198  		enabler := &libpages.ActivityStatsEnabler{
   199  			Durations: []libpages.NameableDuration{
   200  				{
   201  					Duration: time.Hour, Name: "hourly"},
   202  				{
   203  					Duration: time.Hour * 24, Name: "daily"},
   204  				{
   205  					Duration: time.Hour * 24 * 7, Name: "weekly"},
   206  			},
   207  			Interval: activityStatsReportInterval,
   208  			Storer:   activityStorer,
   209  		}
   210  		statsReporter = libpages.NewStathatReporter(
   211  			logger, fStathatPrefix, fStathatEZKey, enabler)
   212  	}
   213  
   214  	certStore := libpages.NoCertStore
   215  	if fDiskCertCache {
   216  		certStore = libpages.DiskCertStore
   217  	}
   218  
   219  	serverConfig := &libpages.ServerConfig{
   220  		DomainBlacklist: removeEmpty(strings.Split(fBlacklist, ",")),
   221  		UseStaging:      !fProd,
   222  		Logger:          logger,
   223  		CertStore:       certStore,
   224  		StatsReporter:   statsReporter,
   225  	}
   226  
   227  	_ = libpages.ListenAndServe(ctx, serverConfig, kbConfig)
   228  }