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 }