2021-05-02 12:29:43 +00:00
package main
import (
"embed"
"flag"
"fmt"
"io/fs"
2022-02-08 14:13:45 +00:00
"io/ioutil"
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"
2022-02-08 14:13:45 +00:00
"path/filepath"
2021-05-02 19:33:37 +00:00
"runtime"
2021-05-02 12:29:43 +00:00
"time"
2022-02-08 14:13:45 +00:00
"github.com/cockroachdb/pebble"
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
2022-02-01 13:55:05 +00:00
maxBackups int
2022-02-01 11:35:34 +00:00
}
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 13:55:05 +00:00
maxBackups := flag . Int ( "max-backups" , 20 , "Maximum number of backups to keep, older ones will be deleted, set to 0 to keep all" )
2022-02-08 14:13:45 +00:00
driver := flag . String ( "driver" , "auto" , "Database driver to use (available: auto, badger, pebble). If 'auto' is specified with no database already in-place, the default driver (badger) will be used." )
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-02-02 17:10:34 +00:00
defer func ( ) {
_ = logger . Sync ( )
} ( )
2022-01-27 15:49:18 +00:00
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
2022-02-01 13:55:05 +00:00
options := dbOptions {
directory : * dbDir ,
restore : * restoreDB ,
backupDir : * backupDir ,
backupInterval : * backupInterval ,
maxBackups : * maxBackups ,
}
2022-02-08 14:13:45 +00:00
// If driver is not mentioned explicitly, run db detection
if * driver == "auto" {
file , err := ioutil . ReadFile ( filepath . Join ( options . directory , "stul-driver" ) )
if err != nil {
if err == os . ErrNotExist {
* driver = "badger"
} else {
failOnError ( err , "failed to open database driver file" )
}
} else {
* driver = string ( file )
}
}
logger . Info ( "opening database" , zap . String ( "driver" , * driver ) )
2022-02-01 11:35:34 +00:00
switch * driver {
case "badger" :
var db * badger . DB
2022-02-01 13:55:05 +00:00
db , hub , err = makeBadgerHub ( options )
2022-02-02 17:10:34 +00:00
if err != nil {
logger . Fatal ( "failed to open database" , zap . Error ( err ) )
}
2022-02-08 14:13:45 +00:00
defer badgerClose ( db )
case "pebble" :
var db * pebble . DB
db , hub , err = makePebbleHub ( options )
if err != nil {
logger . Fatal ( "failed to open database" , zap . Error ( err ) )
}
defer pebbleClose ( db )
2022-02-01 11:35:34 +00:00
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 ( ) )
}