// Copyright (C) 2019 Marius Schellenberger package cmd import ( "encoding/json" "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/server" "git.giftfish.de/ston1th/godrop/v2" "github.com/urfave/cli" stdlog "log" "net" "os" "os/signal" "syscall" "time" ) const ( defDataDir = "/var/lib/docstore" defListen = "127.0.0.1:8080" defLogFile = "docstore.log" defRunUser = "wiki" defRunGroup = "wiki" ) 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.DataDir); err != nil { return } if err = godrop.PledgePromises("stdio rpath wpath cpath inet fattr flock unveil"); err != nil { return } l, err := godrop.GetListener() if err != nil { return } log.InitLogger(cfg) srv := server.NewHTTPServer(cfg, l) 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 = "a simple wiki engine" app.Version = version app.Commands = []cli.Command{ { Name: "server", Usage: "start docstore server", Flags: defaultFlags(serverFlags()), Action: func(c *cli.Context) error { 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 articles in the data directory (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 to the data directory (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
:", 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.BoolTFlag{ Name: "admin, a", Usage: "use -a=false to disable the admin user", Destination: &config.Admin, }, cli.BoolFlag{ Name: "foreground, f", Usage: "do not fork into the background", Destination: &config.Foreground, }, cli.BoolFlag{ Name: "secure, s", Usage: "enable secure cookie", 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: "data, d", Value: defDataDir, Usage: "data directory", Destination: &config.DataDir, }, }, f...) }