diff options
author | Ben Morrison <ben@gbmor.dev> | 2019-06-05 15:36:23 -0400 |
---|---|---|
committer | Ben Morrison <ben@gbmor.dev> | 2019-06-05 15:36:23 -0400 |
commit | fd43c61bd128ad77b22db0537a9a4eb58490b0b5 (patch) | |
tree | 4c5fa7b33fadbf7c3e14e69b7d68ce280bc3810a /svc/init.go | |
parent | 4658fe82be3e9d95e93fa5c7c7ca64a15cf2f1a1 (diff) | |
download | getwtxt-fd43c61bd128ad77b22db0537a9a4eb58490b0b5.tar.gz |
moved bulk of code to its own package to clean up source tree
Diffstat (limited to 'svc/init.go')
-rw-r--r-- | svc/init.go | 600 |
1 files changed, 600 insertions, 0 deletions
diff --git a/svc/init.go b/svc/init.go new file mode 100644 index 0000000..18fe0b6 --- /dev/null +++ b/svc/init.go @@ -0,0 +1,600 @@ +package svc // import "github.com/getwtxt/getwtxt/svc" + +import ( + "database/sql" + "fmt" + "html/template" + "log" + "os" + "os/signal" + "path/filepath" + "strings" + "time" + + "github.com/fsnotify/fsnotify" + "github.com/getwtxt/registry" + "github.com/spf13/pflag" + "github.com/spf13/viper" + "github.com/syndtr/goleveldb/leveldb" +) + +const getwtxt = "0.2.2" + +var ( + flagVersion *bool = pflag.BoolP("version", "v", false, "Display version information, then exit.") + flagHelp *bool = pflag.BoolP("help", "h", false, "Display the quick-help screen.") + flagMan *bool = pflag.BoolP("manual", "m", false, "Display the configuration manual.") + flagConfFile *string = pflag.StringP("config", "c", "", "The name/path of the configuration file you wish to use.") + flagAssets *string = pflag.StringP("assets", "a", "", "The location of the getwtxt assets directory") + flagDBPath *string = pflag.StringP("db", "d", "", "Path to the getwtxt database") + flagDBType *string = pflag.StringP("dbtype", "t", "", "Type of database being used") +) + +var confObj = &Configuration{} + +// signals to close the log file +var closeLog = make(chan bool, 1) + +// used to transmit database pointer after +// initialization +var dbChan = make(chan dbase, 1) + +var tmpls *template.Template + +var twtxtCache = registry.NewIndex() + +var remoteRegistries = &RemoteRegistries{} + +var staticCache = &struct { + index []byte + indexMod time.Time + css []byte + cssMod time.Time +}{ + index: nil, + indexMod: time.Time{}, + css: nil, + cssMod: time.Time{}, +} + +func init() { + checkFlags() + titleScreen() + initConfig() + initLogging() + tmpls = initTemplates() + initDatabase() + go cacheAndPush() + watchForInterrupt() +} + +func checkFlags() { + pflag.Parse() + if *flagVersion { + titleScreen() + os.Exit(0) + } + if *flagHelp { + titleScreen() + helpScreen() + os.Exit(0) + } + if *flagMan { + titleScreen() + helpScreen() + manualScreen() + os.Exit(0) + } +} + +func initConfig() { + + if *flagConfFile == "" { + viper.SetConfigName("getwtxt") + viper.SetConfigType("yml") + viper.AddConfigPath(".") + viper.AddConfigPath("/usr/local/getwtxt") + viper.AddConfigPath("/etc") + viper.AddConfigPath("/usr/local/etc") + + } else { + path, file := filepath.Split(*flagConfFile) + if path == "" { + path = "." + } + if file == "" { + file = *flagConfFile + } + filename := strings.Split(file, ".") + viper.SetConfigName(filename[0]) + viper.SetConfigType(filename[1]) + viper.AddConfigPath(path) + } + + log.Printf("Loading configuration ...\n") + if err := viper.ReadInConfig(); err != nil { + log.Printf("%v\n", err.Error()) + log.Printf("Using defaults ...\n") + } else { + viper.WatchConfig() + viper.OnConfigChange(func(e fsnotify.Event) { + log.Printf("Config file change detected. Reloading...\n") + rebindConfig() + }) + } + + viper.SetDefault("ListenPort", 9001) + viper.SetDefault("LogFile", "getwtxt.log") + viper.SetDefault("DatabasePath", "getwtxt.db") + viper.SetDefault("AssetsDirectory", "assets") + viper.SetDefault("DatabaseType", "leveldb") + viper.SetDefault("StdoutLogging", false) + viper.SetDefault("ReCacheInterval", "1h") + viper.SetDefault("DatabasePushInterval", "5m") + + viper.SetDefault("Instance.SiteName", "getwtxt") + viper.SetDefault("Instance.OwnerName", "Anonymous Microblogger") + viper.SetDefault("Instance.Email", "nobody@knows") + viper.SetDefault("Instance.URL", "https://twtxt.example.com") + viper.SetDefault("Instance.Description", "A fast, resilient twtxt registry server written in Go!") + + confObj.Mu.Lock() + + confObj.Port = viper.GetInt("ListenPort") + confObj.LogFile = viper.GetString("LogFile") + + if *flagDBType == "" { + confObj.DBType = strings.ToLower(viper.GetString("DatabaseType")) + } else { + confObj.DBType = *flagDBType + } + + if *flagDBPath == "" { + confObj.DBPath = viper.GetString("DatabasePath") + } else { + confObj.DBPath = *flagDBPath + } + log.Printf("Using %v database: %v\n", confObj.DBType, confObj.DBPath) + + if *flagAssets == "" { + confObj.AssetsDir = viper.GetString("AssetsDirectory") + } else { + confObj.AssetsDir = *flagAssets + } + + confObj.StdoutLogging = viper.GetBool("StdoutLogging") + if confObj.StdoutLogging { + log.Printf("Logging to stdout\n") + } else { + log.Printf("Logging to %v\n", confObj.LogFile) + } + + confObj.CacheInterval = viper.GetDuration("StatusFetchInterval") + log.Printf("User status fetch interval: %v\n", confObj.CacheInterval) + + confObj.DBInterval = viper.GetDuration("DatabasePushInterval") + log.Printf("Database push interval: %v\n", confObj.DBInterval) + + confObj.LastCache = time.Now() + confObj.LastPush = time.Now() + confObj.Version = getwtxt + + confObj.Instance.Vers = getwtxt + confObj.Instance.Name = viper.GetString("Instance.SiteName") + confObj.Instance.URL = viper.GetString("Instance.URL") + confObj.Instance.Owner = viper.GetString("Instance.OwnerName") + confObj.Instance.Mail = viper.GetString("Instance.Email") + confObj.Instance.Desc = viper.GetString("Instance.Description") + + confObj.Mu.Unlock() + +} + +func initLogging() { + + confObj.Mu.RLock() + + if confObj.StdoutLogging { + log.SetOutput(os.Stdout) + + } else { + + logfile, err := os.OpenFile(confObj.LogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + log.Printf("Could not open log file: %v\n", err.Error()) + } + + // Listen for the signal to close the log file + // in a separate thread. Passing it as an argument + // to prevent race conditions when the config is + // reloaded. + go func(logfile *os.File) { + + <-closeLog + log.Printf("Closing log file ...\n") + + err = logfile.Close() + if err != nil { + log.Printf("Couldn't close log file: %v\n", err.Error()) + } + }(logfile) + + log.SetOutput(logfile) + } + + confObj.Mu.RUnlock() +} + +func rebindConfig() { + + confObj.Mu.RLock() + if !confObj.StdoutLogging { + closeLog <- true + } + confObj.Mu.RUnlock() + + confObj.Mu.Lock() + + confObj.LogFile = viper.GetString("LogFile") + confObj.DBType = strings.ToLower(viper.GetString("DatabaseType")) + confObj.DBPath = viper.GetString("DatabasePath") + confObj.StdoutLogging = viper.GetBool("StdoutLogging") + confObj.CacheInterval = viper.GetDuration("StatusFetchInterval") + confObj.DBInterval = viper.GetDuration("DatabasePushInterval") + + confObj.Instance.Name = viper.GetString("Instance.SiteName") + confObj.Instance.URL = viper.GetString("Instance.URL") + confObj.Instance.Owner = viper.GetString("Instance.OwnerName") + confObj.Instance.Mail = viper.GetString("Instance.Email") + confObj.Instance.Desc = viper.GetString("Instance.Description") + + confObj.Mu.Unlock() + + initLogging() +} + +func initTemplates() *template.Template { + confObj.Mu.RLock() + assetsDir := confObj.AssetsDir + confObj.Mu.RUnlock() + + return template.Must(template.ParseFiles(assetsDir + "/tmpl/index.html")) +} + +// Pull DB data into cache, if available. +func initDatabase() { + var db dbase + var err error + + confObj.Mu.RLock() + switch confObj.DBType { + + case "leveldb": + var lvl *leveldb.DB + lvl, err = leveldb.OpenFile(confObj.DBPath, nil) + db = &dbLevel{db: lvl} + + case "sqlite": + var lite *sql.DB + db = &dbSqlite{db: lite} + + } + confObj.Mu.RUnlock() + + if err != nil { + log.Fatalf("%v\n", err.Error()) + } + + dbChan <- db + + pullDatabase() +} + +// Watch for SIGINT aka ^C +// Close the log file then exit +func watchForInterrupt() { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + + go func() { + for sigint := range c { + + log.Printf("\n\nCaught %v. Cleaning up ...\n", sigint) + confObj.Mu.RLock() + log.Printf("Closing database connection to %v...\n", confObj.DBPath) + + db := <-dbChan + + switch dbType := db.(type) { + + case *dbLevel: + lvl := dbType + if err := lvl.db.Close(); err != nil { + log.Printf("%v\n", err.Error()) + } + + } + + if !confObj.StdoutLogging { + closeLog <- true + } + + confObj.Mu.RUnlock() + close(dbChan) + close(closeLog) + + // Let everything catch up + time.Sleep(100 * time.Millisecond) + os.Exit(0) + } + }() +} + +func titleScreen() { + fmt.Printf(` + + _ _ _ + __ _ ___| |___ _| |___ _| |_ + / _ |/ _ \ __\ \ /\ / / __\ \/ / __| + | (_| | __/ |_ \ V V /| |_ > <| |_ + \__, |\___|\__| \_/\_/ \__/_/\_\\__| + |___/ + version ` + getwtxt + ` + github.com/getwtxt/getwtxt + GPL v3 + +`) +} + +func helpScreen() { + fmt.Printf(` + getwtxt Help + + + :: Command Line Options :: + + Command line options are used to explicitly override defaults, + or what has been specified in the configuration file. + + -h [--help] Print this help screen. + -m [--manual] Print the manual. + -v [--version] Print the version information and quit. + -c [--config] Path to an alternate configuration file + to use. May be relative or absolute. + -a [--assets] Path to the assets directory, containing + style.css and tmpl/index.html + -d [--db] Path getwtxt should use for the database. + -t [--dbtype] Type of database to use. + Options: leveldb + +`) +} +func manualScreen() { + fmt.Printf(` + :: Sections :: + + >> Configuration File + Covers syntax and location of default configuration, + passing a specific configuration file to getwtxt, + and acceptable formats for configuration files. + + >> Customizing the Landing Page + Covers the location of the landing page template, + format of the template, and optional preprocessor + tags available to use when creating a new landing + page template. + + >> Interacting With the Registry + Explains all API endpoints, their parameters, + and expected output. + + + :: Configuration File :: + + The default configuration file is in YAML format, chosen for + its clarity and its support of commenting (unlike JSON). It may + be placed in any of the following locations by default: + + The same directory as the getwtxt executable + /usr/local/getwtxt/ + /etc/ + /usr/local/etc/ + + The paths are searched in that order. The first configuration + file found is used by getwtxt, while the locations further down + are ignored. + + Multiple configuration files may be used, however, with the + '-c' command line flag. The path passed to getwtxt via '-c' may + be relative or absolute. For example, both of the following are + allowed: + + ./getwtxt -c myconfig.json + ./getwtxt -c /etc/ExtraConfigsDir/mysecondconfig.toml + + The supported configuration types are: + YAML, TOML, JSON, HCL + + The configuration file contains several options used to + customize your instance of getwtxt. None are required, they will + simply use their default value unless otherwise specified. + + Listen class="w"> case *dbLevel: lvl := dbType err := lvl.db.Close() errLog("", err) } if !confObj.StdoutLogging { closeLog <- true } confObj.Mu.RUnlock() close(dbChan) close(closeLog) // Let everything catch up time.Sleep(100 * time.Millisecond) os.Exit(0) } }() } |