docstore/pkg/cmd/cmd.go
2019-08-24 14:27:58 +02:00

319 lines
7.1 KiB
Go

// Copyright (C) 2019 Marius Schellenberger
package cmd
import (
"encoding/json"
"errors"
"git.giftfish.de/ston1th/docstore/pkg/core"
"git.giftfish.de/ston1th/docstore/pkg/db"
"git.giftfish.de/ston1th/docstore/pkg/log"
"git.giftfish.de/ston1th/docstore/pkg/scan"
"git.giftfish.de/ston1th/docstore/pkg/server"
"git.giftfish.de/ston1th/godrop/v2"
"github.com/urfave/cli"
stdlog "log"
"net"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"
)
const (
defRunDir = "/var/lib/docstore"
defListen = "127.0.0.1:8080"
defLogFile = "docstore.log"
defRunUser = "docstore"
defRunGroup = "docstore"
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
//}
dropCfg := godrop.Config{
User: cfg.User,
Group: cfg.Group,
Foreground: cfg.Foreground,
}
err = godrop.Drop(dropCfg, func() (net.Listener, error) {
return net.Listen("tcp", cfg.ListenAddr)
})
if 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 := godrop.GetListener()
if err != nil {
return
}
log.InitLogger(cfg)
cfg.DataDir = filepath.Join(cfg.RunDir, core.DataDir)
debugCfg := cfg
debugCfg.Secret = "***hidden***"
log.Debugf("docstore config: %+v", debugCfg)
scanner, err := scan.New(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(cfg, l, scanner)
err = srv.Start()
if err != nil {
return
}
log.Println("docstore " + cfg.Version + " started")
sigs := make(chan os.Signal)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigs
log.Println("signal: " + sig.String())
go func() {
time.Sleep(time.Second * 20)
log.Fatal("stop timed out: killing")
}()
err = srv.Stop()
if err != nil {
log.Println(err)
}
log.Println("docstore stopped")
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
}
return file.Close()
}
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 {
stdlog.Fatal("server: ", err)
}
config.Version = app.Version
if err := initServer(config); err != nil {
stdlog.Fatal("server: ", err)
}
return nil
},
},
{
Name: "dump",
Usage: "create a json dump of all users (docstore needs to be stopped)",
Flags: defaultFlags(dumpRestoreFlags()),
Action: func(c *cli.Context) error {
var (
file = os.Stdout
err error
)
if err = loadConfig(); err != nil {
stdlog.Fatal("dump: ", err)
}
if dumpFile != "-" {
file, err = os.OpenFile(dumpFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0640)
if err != nil {
stdlog.Fatal("dump: ", err)
}
}
DB, err := db.New(config)
if err != nil {
stdlog.Fatal("dump: ", err)
}
if err = DB.Dump(file); err != nil {
stdlog.Fatal("dump: ", err)
}
file.Close()
err = DB.Close()
if err != nil {
stdlog.Fatal("dump: ", err)
}
return nil
},
},
{
Name: "restore",
Usage: "restore a json dump (docstore needs to be stopped)",
Flags: defaultFlags(dumpRestoreFlags()),
Action: func(c *cli.Context) error {
var (
file = os.Stdin
err error
)
if err = loadConfig(); err != nil {
stdlog.Fatal("restore: ", err)
}
if dumpFile != "-" {
file, err = os.Open(dumpFile)
if err != nil {
stdlog.Fatal("restore: ", err)
}
}
DB, err := db.New(config)
if err != nil {
stdlog.Fatal("restore: ", err)
}
if err = DB.Restore(file); err != nil {
stdlog.Fatal("restore: ", err)
}
file.Close()
err = DB.Close()
if err != nil {
stdlog.Fatal("restore: ", err)
}
return nil
},
},
}
app.Run(os.Args)
}
func serverFlags() []cli.Flag {
return []cli.Flag{
cli.StringFlag{
Name: "user, u",
Value: defRunUser,
Usage: "drop privileges to user",
Destination: &config.User,
},
cli.StringFlag{
Name: "group, g",
Value: defRunGroup,
Usage: "drop privileges to group",
Destination: &config.Group,
},
cli.StringFlag{
Name: "listen, l",
Value: defListen,
Usage: "listening <address>:<port>",
Destination: &config.ListenAddr,
},
cli.StringFlag{
Name: "log",
Value: defLogFile,
Usage: "log file (use - for stdout, this only works in combination with -f)",
Destination: &config.LogFile,
},
cli.StringFlag{
Name: "secret",
Usage: "static hmac secret (at least 64 chars, used for JWT signing)",
Destination: &config.Secret,
},
cli.StringSliceFlag{
Name: "langs",
Usage: "languages for tesseract ocr (use --langs multiple times to add more languages)",
},
cli.IntFlag{
Name: "timeout, t",
Value: defTimeout,
Usage: "timeout in seconds for executed teseract and pdftotext commands",
Destination: &config.CMDTimeout,
},
cli.IntFlag{
Name: "threads",
Value: tesseractThreads,
Usage: "tesseract thread limit (1-4)",
Destination: &config.TesseractThreads,
},
cli.IntFlag{
Name: "oem",
Value: tesseractOEM,
Usage: "tesseract oem value (1-4)",
Destination: &config.TesseractOEM,
},
cli.BoolFlag{
Name: "foreground, f",
Usage: "do not fork into the background",
Destination: &config.Foreground,
},
cli.BoolFlag{
Name: "secure, s",
Usage: "enable secure cookie flag",
Destination: &config.SecureCookie,
},
cli.BoolFlag{
Name: "debug",
Usage: "enable debugging",
Destination: &config.Debug,
},
}
}
func dumpRestoreFlags() []cli.Flag {
return []cli.Flag{
cli.StringFlag{
Name: "file, f",
Value: "-",
Usage: "file to read/write dump (default stdin/stdout)",
Destination: &dumpFile,
},
}
}
func defaultFlags(f []cli.Flag) []cli.Flag {
return append([]cli.Flag{
cli.StringFlag{
Name: "config, c",
Usage: "config file",
Destination: &configFile,
},
cli.StringFlag{
Name: "run, r",
Value: defRunDir,
Usage: "run directory",
Destination: &config.RunDir,
},
}, f...)
}