2021-05-02 12:29:43 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"embed"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"io/fs"
|
2022-01-27 15:49:18 +00:00
|
|
|
"log"
|
2021-09-18 20:06:22 +00:00
|
|
|
"math/rand"
|
2022-01-02 10:45:09 +00:00
|
|
|
"os"
|
2021-05-02 19:33:37 +00:00
|
|
|
"runtime"
|
2021-05-02 12:29:43 +00:00
|
|
|
"time"
|
|
|
|
|
2022-02-01 11:35:34 +00:00
|
|
|
"github.com/dgraph-io/badger/v3"
|
|
|
|
"github.com/strimertul/strimertul/modules/database"
|
|
|
|
|
|
|
|
kv "github.com/strimertul/kilovolt/v8"
|
|
|
|
|
2022-01-27 15:49:18 +00:00
|
|
|
"go.uber.org/zap/zapcore"
|
|
|
|
|
2022-01-02 10:45:09 +00:00
|
|
|
jsoniter "github.com/json-iterator/go"
|
2022-01-27 15:49:18 +00:00
|
|
|
"go.uber.org/zap"
|
2022-01-02 10:45:09 +00:00
|
|
|
|
2021-05-02 12:29:43 +00:00
|
|
|
"github.com/strimertul/strimertul/modules"
|
2021-11-23 10:34:02 +00:00
|
|
|
"github.com/strimertul/strimertul/modules/http"
|
2021-05-02 12:29:43 +00:00
|
|
|
"github.com/strimertul/strimertul/modules/loyalty"
|
2021-05-14 14:37:54 +00:00
|
|
|
"github.com/strimertul/strimertul/modules/stulbe"
|
2021-05-14 11:15:38 +00:00
|
|
|
"github.com/strimertul/strimertul/modules/twitch"
|
2021-05-02 12:29:43 +00:00
|
|
|
|
2021-11-23 10:34:02 +00:00
|
|
|
"github.com/pkg/browser"
|
2021-06-05 23:18:31 +00:00
|
|
|
|
|
|
|
_ "net/http/pprof"
|
2021-05-02 12:29:43 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const AppHeader = `
|
|
|
|
_ _ _ O O _
|
|
|
|
__| |_ _ _(_)_ __ ___ _ _| |_ _ _| |
|
|
|
|
(_-< _| '_| | ' \/ -_) '_| _| || | |
|
|
|
|
/__/\__|_| |_|_|_|_\___|_| \__|\_,_|_| `
|
|
|
|
|
2021-12-06 13:47:38 +00:00
|
|
|
var appVersion = "v0.0.0-UNKNOWN"
|
|
|
|
|
2021-05-02 12:29:43 +00:00
|
|
|
//go:embed frontend/dist/*
|
|
|
|
var frontend embed.FS
|
|
|
|
|
2022-01-27 15:49:18 +00:00
|
|
|
var logger *zap.Logger
|
2021-05-02 12:29:43 +00:00
|
|
|
|
2021-12-09 10:45:10 +00:00
|
|
|
type ModuleConstructor = func(manager *modules.Manager) error
|
|
|
|
|
|
|
|
var moduleList = map[modules.ModuleID]ModuleConstructor{
|
|
|
|
modules.ModuleStulbe: stulbe.Register,
|
|
|
|
modules.ModuleLoyalty: loyalty.Register,
|
2022-01-12 11:02:54 +00:00
|
|
|
modules.ModuleTwitch: twitch.Register,
|
2021-12-09 10:45:10 +00:00
|
|
|
}
|
|
|
|
|
2022-02-01 11:35:34 +00:00
|
|
|
type dbOptions struct {
|
|
|
|
directory string
|
|
|
|
restore string
|
|
|
|
backupDir string
|
|
|
|
backupInterval int
|
|
|
|
}
|
|
|
|
|
2021-05-02 12:29:43 +00:00
|
|
|
func main() {
|
2021-05-02 19:33:37 +00:00
|
|
|
// Get cmd line parameters
|
2022-01-02 10:45:09 +00:00
|
|
|
noHeader := flag.Bool("no-header", false, "Do not print the app header")
|
|
|
|
dbDir := flag.String("database-dir", "data", "Path to strimertül database dir")
|
2022-01-27 15:49:18 +00:00
|
|
|
debug := flag.Bool("debug", false, "Start in debug mode (more logging)")
|
|
|
|
json := flag.Bool("json", false, "Print logging in JSON format")
|
2022-01-02 10:45:09 +00:00
|
|
|
exportDB := flag.Bool("export", false, "Export database as JSON")
|
|
|
|
importDB := flag.String("import", "", "Import database from JSON file")
|
|
|
|
restoreDB := flag.String("restore", "", "Restore database from backup file")
|
|
|
|
backupDir := flag.String("backup-dir", "backups", "Path to directory with database backups")
|
|
|
|
backupInterval := flag.Int("backup-interval", 60, "Backup database every X minutes, 0 to disable")
|
2022-02-01 11:35:34 +00:00
|
|
|
driver := flag.String("driver", "badger", "Database driver to use (available: badger,pebble)")
|
2021-05-02 12:29:43 +00:00
|
|
|
flag.Parse()
|
|
|
|
|
2021-09-18 20:06:22 +00:00
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
|
2022-01-27 15:49:18 +00:00
|
|
|
if *debug {
|
|
|
|
cfg := zap.NewDevelopmentConfig()
|
|
|
|
if *json {
|
|
|
|
cfg.Encoding = "json"
|
|
|
|
}
|
|
|
|
logger, _ = cfg.Build()
|
2021-12-07 00:22:45 +00:00
|
|
|
} else {
|
2022-01-27 15:49:18 +00:00
|
|
|
cfg := zap.NewProductionConfig()
|
|
|
|
if !*json {
|
|
|
|
cfg.Encoding = "console"
|
|
|
|
cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
|
|
|
cfg.EncoderConfig.CallerKey = zapcore.OmitKey
|
|
|
|
}
|
|
|
|
logger, _ = cfg.Build()
|
2021-05-02 19:33:37 +00:00
|
|
|
}
|
2022-01-27 15:49:18 +00:00
|
|
|
defer logger.Sync()
|
|
|
|
undo := zap.RedirectStdLog(logger)
|
|
|
|
defer undo()
|
2021-05-02 19:33:37 +00:00
|
|
|
|
2022-01-02 10:45:09 +00:00
|
|
|
if !*noHeader {
|
|
|
|
// Print the app header and version info
|
|
|
|
_, _ = fmt.Fprintf(os.Stderr, "%s\n\n %s - %s/%s (%s)\n\n", AppHeader, appVersion, runtime.GOOS, runtime.GOARCH, runtime.Version())
|
2021-11-23 10:34:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create module manager
|
2022-01-27 15:49:18 +00:00
|
|
|
manager := modules.NewManager(logger)
|
2021-05-02 12:29:43 +00:00
|
|
|
|
2022-02-01 11:35:34 +00:00
|
|
|
// Make KV hub
|
|
|
|
var hub *kv.Hub
|
|
|
|
var err error
|
|
|
|
logger.Info("opening database", zap.String("driver", *driver))
|
|
|
|
switch *driver {
|
|
|
|
case "badger":
|
|
|
|
var db *badger.DB
|
|
|
|
db, hub, err = makeBadgerHub(dbOptions{directory: *dbDir, backupDir: *backupDir, backupInterval: *backupInterval, restore: *restoreDB})
|
|
|
|
defer func() {
|
|
|
|
if err := badgerClose(db); err != nil {
|
|
|
|
logger.Fatal("Failed to close database", zap.Error(err))
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
default:
|
|
|
|
logger.Fatal("Unknown database driver", zap.String("driver", *driver))
|
2021-05-02 12:29:43 +00:00
|
|
|
}
|
|
|
|
|
2022-02-01 11:35:34 +00:00
|
|
|
go hub.Run()
|
|
|
|
|
|
|
|
db, err := database.NewDBModule(hub, manager)
|
|
|
|
failOnError(err, "Failed to initialize database module")
|
|
|
|
|
2022-01-02 10:45:09 +00:00
|
|
|
if *exportDB {
|
|
|
|
// Export database to stdout
|
|
|
|
data, err := db.GetAll("")
|
|
|
|
failOnError(err, "Could not export database")
|
|
|
|
failOnError(jsoniter.ConfigFastest.NewEncoder(os.Stdout).Encode(data), "Could not encode database")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if *importDB != "" {
|
|
|
|
file, err := os.Open(*importDB)
|
|
|
|
failOnError(err, "Could not open import file")
|
|
|
|
var entries map[string]string
|
|
|
|
err = jsoniter.ConfigFastest.NewDecoder(file).Decode(&entries)
|
|
|
|
failOnError(err, "Could not decode import file")
|
|
|
|
errors := 0
|
|
|
|
imported := 0
|
|
|
|
for key, value := range entries {
|
2022-02-01 11:35:34 +00:00
|
|
|
err = db.PutKey(key, value)
|
2022-01-02 10:45:09 +00:00
|
|
|
if err != nil {
|
2022-01-27 15:49:18 +00:00
|
|
|
logger.Error("Could not import entry", zap.String("key", key), zap.Error(err))
|
2022-01-02 10:45:09 +00:00
|
|
|
errors += 1
|
|
|
|
} else {
|
|
|
|
imported += 1
|
|
|
|
}
|
|
|
|
}
|
2022-01-27 15:49:18 +00:00
|
|
|
logger.Info("Imported database from file", zap.Int("imported", imported), zap.Int("errors", errors))
|
2022-01-02 10:45:09 +00:00
|
|
|
}
|
|
|
|
|
2021-12-09 10:45:10 +00:00
|
|
|
// Set meta keys
|
2022-02-01 11:35:34 +00:00
|
|
|
_ = db.PutKey("stul-meta/version", appVersion)
|
2021-05-02 12:29:43 +00:00
|
|
|
|
2021-12-09 10:45:10 +00:00
|
|
|
for module, constructor := range moduleList {
|
|
|
|
err := constructor(manager)
|
2021-11-19 18:37:42 +00:00
|
|
|
if err != nil {
|
2022-02-01 11:35:34 +00:00
|
|
|
logger.Error("Could not register module", zap.String("module", string(module)), zap.Error(err))
|
2021-11-19 18:37:42 +00:00
|
|
|
} else {
|
2021-12-09 10:45:10 +00:00
|
|
|
//goland:noinspection GoDeferInLoop
|
|
|
|
defer func() {
|
|
|
|
if err := manager.Modules[module].Close(); err != nil {
|
2022-01-27 15:49:18 +00:00
|
|
|
logger.Error("Could not close module", zap.String("module", string(module)), zap.Error(err))
|
2021-12-09 10:45:10 +00:00
|
|
|
}
|
|
|
|
}()
|
2021-05-02 12:29:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create logger and endpoints
|
2021-11-23 10:34:02 +00:00
|
|
|
httpServer, err := http.NewServer(manager)
|
2021-11-19 18:37:42 +00:00
|
|
|
failOnError(err, "Could not initialize http server")
|
2021-12-09 12:32:58 +00:00
|
|
|
defer func() {
|
|
|
|
if err := httpServer.Close(); err != nil {
|
2022-01-27 15:49:18 +00:00
|
|
|
logger.Error("Could not close DB", zap.Error(err))
|
2021-12-09 12:32:58 +00:00
|
|
|
}
|
|
|
|
}()
|
2021-05-02 12:29:43 +00:00
|
|
|
|
|
|
|
fedir, _ := fs.Sub(frontend, "frontend/dist")
|
2021-11-19 18:37:42 +00:00
|
|
|
httpServer.SetFrontend(fedir)
|
2021-05-02 12:29:43 +00:00
|
|
|
|
|
|
|
go func() {
|
|
|
|
time.Sleep(time.Second) // THIS IS STUPID
|
2021-11-19 18:37:42 +00:00
|
|
|
dashboardURL := fmt.Sprintf("http://%s/ui", httpServer.Config.Bind)
|
2021-10-28 09:01:52 +00:00
|
|
|
err := browser.OpenURL(dashboardURL)
|
|
|
|
if err != nil {
|
2022-01-27 15:49:18 +00:00
|
|
|
logger.Warn(fmt.Sprintf("could not open browser, dashboard URL available at: %s", dashboardURL), zap.Error(err))
|
2021-10-28 09:01:52 +00:00
|
|
|
}
|
2021-05-02 12:29:43 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
// Start HTTP server
|
2021-11-19 18:37:42 +00:00
|
|
|
failOnError(httpServer.Listen(), "HTTP server stopped")
|
2021-05-02 12:29:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func failOnError(err error, text string) {
|
|
|
|
if err != nil {
|
|
|
|
fatalError(err, text)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func fatalError(err error, text string) {
|
|
|
|
log.Fatalf("FATAL ERROR OCCURRED: %s\n\n%s", text, err.Error())
|
|
|
|
}
|