// Copyright (C) 2021 Marius Schellenberger package cmd import ( "encoding/json" "errors" "fmt" "net" "os" "os/signal" "path/filepath" "syscall" "git.giftfish.de/ston1th/docstore/pkg/core" "git.giftfish.de/ston1th/docstore/pkg/db" "git.giftfish.de/ston1th/docstore/pkg/scan" "git.giftfish.de/ston1th/docstore/pkg/server" "git.giftfish.de/ston1th/godrop/v2" "github.com/go-logr/logr" "github.com/urfave/cli" "k8s.io/klog/v2" "k8s.io/klog/v2/klogr" //"github.com/go-logr/zerologr" //"github.com/rs/zerolog" ) const ( defRunDir = "/var/lib/docstore" defListen = "127.0.0.1:8080" defLogFile = "docstore.log" defTimeout = 600 tesseractThreads = 1 tesseractOEM = 1 ) var ( config core.Config dumpFile string configFile string ) func initServer(cfg *core.Config) (err error) { // remove initial pledge due to violation with go1.12 // sysctl kern.somaxconn // if err = godrop.PledgePromises("stdio rpath wpath cpath inet fattr flock proc exec id unveil"); err != nil { // return // } if err = os.Chdir(cfg.RunDir); err != nil { return } if err = godrop.PledgePromises("stdio rpath wpath cpath inet fattr flock proc exec unveil"); err != nil { return } l, err := net.Listen("tcp", cfg.ListenAddr) if err != nil { return } ll, err := logFileLogger(cfg) if err != nil { return } log := ll.WithName("main") log.Info("starting docstore", "version", cfg.Version) cfg.DataDir = filepath.Join(cfg.RunDir, core.DataDir) debugCfg := new(core.Config) *debugCfg = *cfg debugCfg.Secret = "[redacted]" log.V(2).Info("docstore config", "cfg", debugCfg) scanner, err := scan.New(ll.WithName("scanner"), cfg) if err != nil { return errors.New("scanner: " + err.Error()) } err = godrop.Unveil(cfg.RunDir, "rwc") if err != nil { return } err = godrop.UnveilBlock() if err != nil { return } srv := server.NewHTTPServer(ll, cfg, l, scanner) err = srv.Start() if err != nil { return } sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) <-sigs log.Info("docstore shutdown initiated") err = srv.Stop() if err != nil { log.Error(err, "shutdown") } log.Info("docstore shutdown completed") return } func loadConfig() error { if configFile == "" { return nil } file, err := os.Open(configFile) if err != nil { return err } err = json.NewDecoder(file).Decode(&config) if err != nil { return err } if config.Debug { new(klog.Level).Set("2") //zerolog.SetGlobalLevel(zerolog.TraceLevel) } if config.RunDir == "" { config.RunDir = defRunDir } if config.ListenAddr == "" { config.ListenAddr = defListen } if config.LogFile == "" { config.LogFile = defLogFile } if config.CMDTimeout == 0 { config.CMDTimeout = defTimeout } if config.TesseractThreads == 0 { config.TesseractThreads = tesseractThreads } if config.TesseractOEM == 0 { config.TesseractOEM = tesseractOEM } return file.Close() } func fatal(err error) { log.Error(err, "init failed") os.Exit(1) } var ( log logr.Logger //zl zerolog.Logger ) func logFileLogger(cfg *core.Config) (l logr.Logger, err error) { f, err := os.OpenFile(filepath.Join(cfg.DataDir, cfg.LogFile), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o640) if err != nil { return } klog.LogToStderr(false) klog.SetOutput(f) l = klogr.New() //zln := zl.Output(f) //l = zerologr.New(&zln) return } func init() { //zerolog.CallerMarshalFunc = func(pc uintptr, file string, line int) string { // short := file // for i := len(file) - 1; i > 0; i-- { // if file[i] == '/' { // short = file[i+1:] // break // } // } // file = short // return file + ":" + strconv.Itoa(line) //} //zerologr.VerbosityFieldName = "" //zl = zerolog.New(os.Stderr) //zl = zl.With().Caller().Timestamp().Logger() //log = zerologr.New(&zl) log = klogr.New() } func Run(version string) { app := cli.NewApp() app.Name = "docstore" app.Usage = "file server and indexing engine" app.Version = version app.Commands = []cli.Command{ { Name: "server", Usage: "start docstore server", Flags: defaultFlags(serverFlags()), Action: func(c *cli.Context) error { config.Langs = c.StringSlice("langs") if err := loadConfig(); err != nil { fatal(err) } config.Version = app.Version if err := initServer(&config); err != nil { fatal(err) } return nil }, }, { Name: "reset", Usage: "reset the users password and TOTP token (docstore must be stopped)", Flags: defaultFlags(nil), Action: func(c *cli.Context) error { if err := loadConfig(); err != nil { fatal(err) } DB, err := db.NewPlain(log.WithName("db"), &config) if err != nil { fatal(err) } u, err := DB.GetUser() if err != nil { fatal(err) } fmt.Println("username:", u.Username) pw, err := password() if err != nil { fatal(err) } if err = DB.UpdateUserPassword(pw); err != nil { fatal(err) } fmt.Println("password:", pw) if err = DB.UpdateUserSecret(""); err != nil { fatal(err) } if err = DB.Close(); err != nil { fatal(err) } return nil }, }, { Name: "dump", Usage: "create a json dump of all users (docstore must be stopped)", Flags: defaultFlags(dumpRestoreFlags()), Action: func(c *cli.Context) error { var ( file = os.Stdout err error ) if err = loadConfig(); err != nil { fatal(err) } if dumpFile != "-" { file, err = os.OpenFile(dumpFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o640) if err != nil { fatal(err) } } DB, err := db.NewPlain(log.WithName("db"), &config) if err != nil { fatal(err) } if err = DB.Dump(file); err != nil { fatal(err) } file.Close() err = DB.Close() if err != nil { fatal(err) } return nil }, }, { Name: "restore", Usage: "restore a json dump (docstore must be stopped)", Flags: defaultFlags(dumpRestoreFlags()), Action: func(c *cli.Context) error { var ( file = os.Stdin err error ) if err = loadConfig(); err != nil { fatal(err) } if dumpFile != "-" { file, err = os.Open(dumpFile) if err != nil { fatal(err) } } DB, err := db.NewPlain(log.WithName("db"), &config) if err != nil { fatal(err) } if err = DB.Restore(file); err != nil { fatal(err) } file.Close() err = DB.Close() if err != nil { fatal(err) } return nil }, }, } app.Run(os.Args) } func serverFlags() []cli.Flag { return []cli.Flag{ cli.StringFlag{ Name: "listen, l", Value: defListen, Usage: "listening
: