2019-05-11 08:07:15 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2019-06-04 20:13:27 +00:00
|
|
|
"database/sql"
|
2019-05-12 08:34:06 +00:00
|
|
|
"fmt"
|
2019-05-13 21:42:32 +00:00
|
|
|
"html/template"
|
2019-05-11 08:07:15 +00:00
|
|
|
"log"
|
|
|
|
"os"
|
2019-05-13 04:42:48 +00:00
|
|
|
"os/signal"
|
2019-05-28 04:55:07 +00:00
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2019-05-13 04:42:48 +00:00
|
|
|
"time"
|
2019-05-12 07:59:29 +00:00
|
|
|
|
|
|
|
"github.com/fsnotify/fsnotify"
|
2019-05-20 04:53:07 +00:00
|
|
|
"github.com/getwtxt/registry"
|
2019-05-12 07:59:29 +00:00
|
|
|
"github.com/spf13/pflag"
|
|
|
|
"github.com/spf13/viper"
|
2019-05-21 03:01:13 +00:00
|
|
|
"github.com/syndtr/goleveldb/leveldb"
|
2019-05-12 07:59:29 +00:00
|
|
|
)
|
|
|
|
|
2019-06-04 22:19:54 +00:00
|
|
|
const getwtxt = "0.3.0"
|
2019-05-21 03:01:13 +00:00
|
|
|
|
2019-05-12 07:59:29 +00:00
|
|
|
var (
|
2019-05-27 06:00:50 +00:00
|
|
|
flagVersion *bool = pflag.BoolP("version", "v", false, "Display version information, then exit.")
|
2019-05-28 04:55:07 +00:00
|
|
|
flagHelp *bool = pflag.BoolP("help", "h", false, "Display the quick-help screen.")
|
2019-06-04 07:20:40 +00:00
|
|
|
flagMan *bool = pflag.BoolP("manual", "m", false, "Display the configuration manual.")
|
2019-05-28 04:55:07 +00:00
|
|
|
flagConfFile *string = pflag.StringP("config", "c", "", "The name/path of the configuration file you wish to use.")
|
2019-06-05 02:56:28 +00:00
|
|
|
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")
|
2019-05-11 08:07:15 +00:00
|
|
|
)
|
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
var confObj = &Configuration{}
|
2019-05-12 07:59:29 +00:00
|
|
|
|
|
|
|
// signals to close the log file
|
2019-05-21 20:50:54 +00:00
|
|
|
var closeLog = make(chan bool, 1)
|
2019-05-12 07:59:29 +00:00
|
|
|
|
2019-05-21 03:01:13 +00:00
|
|
|
// used to transmit database pointer after
|
|
|
|
// initialization
|
2019-06-04 20:13:27 +00:00
|
|
|
var dbChan = make(chan dbase, 1)
|
2019-05-21 03:01:13 +00:00
|
|
|
|
2019-05-13 21:42:32 +00:00
|
|
|
var tmpls *template.Template
|
|
|
|
|
2019-05-20 04:53:07 +00:00
|
|
|
var twtxtCache = registry.NewIndex()
|
|
|
|
|
2019-05-21 08:13:21 +00:00
|
|
|
var remoteRegistries = &RemoteRegistries{}
|
2019-05-21 02:52:50 +00:00
|
|
|
|
2019-05-23 06:16:41 +00:00
|
|
|
var staticCache = &struct {
|
|
|
|
index []byte
|
|
|
|
indexMod time.Time
|
|
|
|
css []byte
|
|
|
|
cssMod time.Time
|
|
|
|
}{
|
|
|
|
index: nil,
|
|
|
|
indexMod: time.Time{},
|
|
|
|
css: nil,
|
|
|
|
cssMod: time.Time{},
|
|
|
|
}
|
|
|
|
|
2019-06-05 02:56:28 +00:00
|
|
|
func init() {
|
2019-05-13 07:07:32 +00:00
|
|
|
checkFlags()
|
2019-05-12 08:34:06 +00:00
|
|
|
titleScreen()
|
2019-05-12 07:59:29 +00:00
|
|
|
initConfig()
|
|
|
|
initLogging()
|
2019-05-13 21:42:32 +00:00
|
|
|
tmpls = initTemplates()
|
2019-05-21 08:13:21 +00:00
|
|
|
initDatabase()
|
2019-05-13 04:42:48 +00:00
|
|
|
watchForInterrupt()
|
2019-05-12 07:59:29 +00:00
|
|
|
}
|
|
|
|
|
2019-05-13 07:07:32 +00:00
|
|
|
func checkFlags() {
|
2019-05-12 08:34:06 +00:00
|
|
|
pflag.Parse()
|
2019-05-13 07:07:32 +00:00
|
|
|
if *flagVersion {
|
|
|
|
titleScreen()
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
if *flagHelp {
|
2019-05-21 03:01:13 +00:00
|
|
|
titleScreen()
|
|
|
|
helpScreen()
|
2019-05-13 07:07:32 +00:00
|
|
|
os.Exit(0)
|
2019-05-13 06:52:32 +00:00
|
|
|
}
|
2019-05-28 04:55:07 +00:00
|
|
|
if *flagMan {
|
|
|
|
titleScreen()
|
|
|
|
helpScreen()
|
|
|
|
manualScreen()
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
2019-05-13 07:07:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func initConfig() {
|
2019-05-12 07:59:29 +00:00
|
|
|
|
2019-05-28 04:55:07 +00:00
|
|
|
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)
|
|
|
|
}
|
2019-05-12 07:59:29 +00:00
|
|
|
|
2019-05-12 08:34:06 +00:00
|
|
|
log.Printf("Loading configuration ...\n")
|
2019-05-12 07:59:29 +00:00
|
|
|
if err := viper.ReadInConfig(); err != nil {
|
2019-05-31 08:36:31 +00:00
|
|
|
log.Printf("%v\n", err.Error())
|
2019-05-12 07:59:29 +00:00
|
|
|
log.Printf("Using defaults ...\n")
|
2019-05-27 06:00:50 +00:00
|
|
|
} else {
|
|
|
|
viper.WatchConfig()
|
|
|
|
viper.OnConfigChange(func(e fsnotify.Event) {
|
|
|
|
log.Printf("Config file change detected. Reloading...\n")
|
|
|
|
rebindConfig()
|
|
|
|
})
|
2019-05-12 07:59:29 +00:00
|
|
|
}
|
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
viper.SetDefault("ListenPort", 9001)
|
|
|
|
viper.SetDefault("LogFile", "getwtxt.log")
|
|
|
|
viper.SetDefault("DatabasePath", "getwtxt.db")
|
2019-06-05 02:56:28 +00:00
|
|
|
viper.SetDefault("DatabaseType", "leveldb")
|
2019-05-23 04:21:53 +00:00
|
|
|
viper.SetDefault("StdoutLogging", false)
|
|
|
|
viper.SetDefault("ReCacheInterval", "1h")
|
|
|
|
viper.SetDefault("DatabasePushInterval", "5m")
|
|
|
|
|
2019-05-27 06:00:50 +00:00
|
|
|
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!")
|
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.Mu.Lock()
|
2019-05-27 06:00:50 +00:00
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.Port = viper.GetInt("ListenPort")
|
|
|
|
confObj.LogFile = viper.GetString("LogFile")
|
2019-05-27 06:00:50 +00:00
|
|
|
|
2019-06-05 02:56:28 +00:00
|
|
|
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 = "assets"
|
|
|
|
} else {
|
|
|
|
confObj.AssetsDir = *flagAssets
|
|
|
|
}
|
2019-05-27 06:00:50 +00:00
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.StdoutLogging = viper.GetBool("StdoutLogging")
|
|
|
|
if confObj.StdoutLogging {
|
2019-05-22 02:04:19 +00:00
|
|
|
log.Printf("Logging to stdout\n")
|
|
|
|
} else {
|
2019-05-23 04:21:53 +00:00
|
|
|
log.Printf("Logging to %v\n", confObj.LogFile)
|
2019-05-22 02:04:19 +00:00
|
|
|
}
|
2019-05-27 06:00:50 +00:00
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.CacheInterval = viper.GetDuration("StatusFetchInterval")
|
|
|
|
log.Printf("User status fetch interval: %v\n", confObj.CacheInterval)
|
2019-05-27 06:00:50 +00:00
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.DBInterval = viper.GetDuration("DatabasePushInterval")
|
|
|
|
log.Printf("Database push interval: %v\n", confObj.DBInterval)
|
2019-05-27 06:00:50 +00:00
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.LastCache = time.Now()
|
|
|
|
confObj.LastPush = time.Now()
|
|
|
|
confObj.Version = getwtxt
|
2019-05-27 06:00:50 +00:00
|
|
|
|
2019-06-04 00:43:32 +00:00
|
|
|
confObj.Instance.Vers = getwtxt
|
2019-05-23 04:21:53 +00:00
|
|
|
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")
|
2019-05-27 06:00:50 +00:00
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.Mu.Unlock()
|
|
|
|
|
2019-05-12 07:59:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func initLogging() {
|
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.Mu.RLock()
|
2019-05-21 03:01:13 +00:00
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
if confObj.StdoutLogging {
|
2019-05-13 03:37:11 +00:00
|
|
|
log.SetOutput(os.Stdout)
|
|
|
|
|
|
|
|
} else {
|
2019-05-11 08:07:15 +00:00
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
logfile, err := os.OpenFile(confObj.LogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
|
2019-05-11 08:07:15 +00:00
|
|
|
if err != nil {
|
2019-05-31 08:36:31 +00:00
|
|
|
log.Printf("Could not open log file: %v\n", err.Error())
|
2019-05-11 08:07:15 +00:00
|
|
|
}
|
|
|
|
|
2019-05-12 08:34:06 +00:00
|
|
|
// Listen for the signal to close the log file
|
2019-05-13 03:37:11 +00:00
|
|
|
// in a separate thread. Passing it as an argument
|
|
|
|
// to prevent race conditions when the config is
|
|
|
|
// reloaded.
|
|
|
|
go func(logfile *os.File) {
|
2019-05-21 03:01:13 +00:00
|
|
|
|
2019-05-21 20:50:54 +00:00
|
|
|
<-closeLog
|
2019-05-12 08:34:06 +00:00
|
|
|
log.Printf("Closing log file ...\n")
|
2019-05-21 03:01:13 +00:00
|
|
|
|
2019-05-12 08:34:06 +00:00
|
|
|
err = logfile.Close()
|
|
|
|
if err != nil {
|
2019-05-31 08:36:31 +00:00
|
|
|
log.Printf("Couldn't close log file: %v\n", err.Error())
|
2019-05-12 08:34:06 +00:00
|
|
|
}
|
2019-05-13 03:37:11 +00:00
|
|
|
}(logfile)
|
2019-05-12 08:34:06 +00:00
|
|
|
|
|
|
|
log.SetOutput(logfile)
|
|
|
|
}
|
2019-05-27 22:00:00 +00:00
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.Mu.RUnlock()
|
2019-05-12 07:59:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func rebindConfig() {
|
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.Mu.RLock()
|
|
|
|
if !confObj.StdoutLogging {
|
2019-05-21 20:50:54 +00:00
|
|
|
closeLog <- true
|
2019-05-12 08:34:06 +00:00
|
|
|
}
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.Mu.RUnlock()
|
2019-05-12 08:34:06 +00:00
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.Mu.Lock()
|
2019-05-27 06:00:50 +00:00
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.LogFile = viper.GetString("LogFile")
|
2019-06-04 20:13:27 +00:00
|
|
|
confObj.DBType = strings.ToLower(viper.GetString("DatabaseType"))
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.DBPath = viper.GetString("DatabasePath")
|
|
|
|
confObj.StdoutLogging = viper.GetBool("StdoutLogging")
|
|
|
|
confObj.CacheInterval = viper.GetDuration("StatusFetchInterval")
|
|
|
|
confObj.DBInterval = viper.GetDuration("DatabasePushInterval")
|
2019-05-27 06:00:50 +00:00
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
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")
|
2019-05-27 06:00:50 +00:00
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.Mu.Unlock()
|
2019-05-11 08:07:15 +00:00
|
|
|
|
2019-05-12 07:59:29 +00:00
|
|
|
initLogging()
|
2019-05-11 08:07:15 +00:00
|
|
|
}
|
2019-05-12 08:34:06 +00:00
|
|
|
|
2019-05-13 21:42:32 +00:00
|
|
|
func initTemplates() *template.Template {
|
2019-06-05 02:56:28 +00:00
|
|
|
confObj.Mu.RLock()
|
|
|
|
assetsDir := confObj.AssetsDir
|
|
|
|
confObj.Mu.RUnlock()
|
|
|
|
|
|
|
|
return template.Must(template.ParseFiles(assetsDir + "/tmpl/index.html"))
|
2019-05-13 21:42:32 +00:00
|
|
|
}
|
|
|
|
|
2019-05-21 20:04:10 +00:00
|
|
|
// Pull DB data into cache, if available.
|
2019-05-21 03:01:13 +00:00
|
|
|
func initDatabase() {
|
2019-06-04 20:13:27 +00:00
|
|
|
var db dbase
|
|
|
|
var err error
|
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.Mu.RLock()
|
2019-06-04 20:13:27 +00:00
|
|
|
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}
|
|
|
|
|
|
|
|
}
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.Mu.RUnlock()
|
2019-06-04 20:13:27 +00:00
|
|
|
|
2019-05-21 03:01:13 +00:00
|
|
|
if err != nil {
|
2019-05-31 08:36:31 +00:00
|
|
|
log.Fatalf("%v\n", err.Error())
|
2019-05-21 03:01:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dbChan <- db
|
2019-05-21 08:13:21 +00:00
|
|
|
|
|
|
|
pullDatabase()
|
2019-05-21 20:04:10 +00:00
|
|
|
go cacheAndPush()
|
2019-05-21 03:01:13 +00:00
|
|
|
}
|
|
|
|
|
2019-05-13 04:42:48 +00:00
|
|
|
// 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 {
|
|
|
|
|
2019-05-21 08:13:21 +00:00
|
|
|
log.Printf("\n\nCaught %v. Cleaning up ...\n", sigint)
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.Mu.RLock()
|
|
|
|
log.Printf("Closing database connection to %v...\n", confObj.DBPath)
|
2019-06-04 20:13:27 +00:00
|
|
|
|
2019-05-21 20:04:10 +00:00
|
|
|
db := <-dbChan
|
2019-06-04 20:13:27 +00:00
|
|
|
|
|
|
|
switch dbType := db.(type) {
|
|
|
|
|
|
|
|
case *dbLevel:
|
|
|
|
lvl := dbType
|
|
|
|
if err := lvl.db.Close(); err != nil {
|
|
|
|
log.Printf("%v\n", err.Error())
|
|
|
|
}
|
|
|
|
|
2019-05-21 08:13:21 +00:00
|
|
|
}
|
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
if !confObj.StdoutLogging {
|
2019-05-21 20:50:54 +00:00
|
|
|
closeLog <- true
|
2019-05-13 04:42:48 +00:00
|
|
|
}
|
|
|
|
|
2019-05-23 04:21:53 +00:00
|
|
|
confObj.Mu.RUnlock()
|
2019-05-21 20:04:10 +00:00
|
|
|
close(dbChan)
|
2019-05-21 20:50:54 +00:00
|
|
|
close(closeLog)
|
2019-05-20 04:53:07 +00:00
|
|
|
|
|
|
|
// Let everything catch up
|
2019-05-21 08:13:21 +00:00
|
|
|
time.Sleep(100 * time.Millisecond)
|
2019-05-13 04:42:48 +00:00
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2019-05-12 08:34:06 +00:00
|
|
|
func titleScreen() {
|
|
|
|
fmt.Printf(`
|
2019-05-28 04:55:07 +00:00
|
|
|
|
2019-06-04 05:41:56 +00:00
|
|
|
_ _ _
|
|
|
|
__ _ ___| |___ _| |___ _| |_
|
|
|
|
/ _ |/ _ \ __\ \ /\ / / __\ \/ / __|
|
|
|
|
| (_| | __/ |_ \ V V /| |_ > <| |_
|
|
|
|
\__, |\___|\__| \_/\_/ \__/_/\_\\__|
|
|
|
|
|___/
|
|
|
|
version ` + getwtxt + `
|
|
|
|
github.com/getwtxt/getwtxt
|
|
|
|
GPL v3
|
|
|
|
|
2019-05-21 03:01:13 +00:00
|
|
|
`)
|
|
|
|
}
|
|
|
|
|
|
|
|
func helpScreen() {
|
|
|
|
fmt.Printf(`
|
2019-06-04 05:41:56 +00:00
|
|
|
getwtxt Help
|
|
|
|
|
|
|
|
|
|
|
|
:: Command Line Options ::
|
2019-05-27 06:00:50 +00:00
|
|
|
|
2019-06-04 07:20:40 +00:00
|
|
|
Command Line Options:
|
|
|
|
-h [--help] Print this help screen.
|
|
|
|
-m [--manual] Print the manual.
|
|
|
|
-v [--version] Print the version information and quit.
|
2019-05-28 04:55:07 +00:00
|
|
|
-c [--config] Path to an alternate configuration file
|
|
|
|
to use. May be relative or absolute.
|
2019-05-28 06:02:31 +00:00
|
|
|
|
2019-05-28 04:55:07 +00:00
|
|
|
`)
|
|
|
|
}
|
|
|
|
func manualScreen() {
|
|
|
|
fmt.Printf(`
|
2019-06-04 05:41:56 +00:00
|
|
|
:: 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 ::
|
|
|
|
|
2019-05-28 04:55:07 +00:00
|
|
|
The default configuration file is in YAML format, chosen for
|
2019-06-04 05:41:56 +00:00
|
|
|
its clarity and its support of commenting (unlike JSON). It may
|
|
|
|
be placed in any of the following locations by default:
|
2019-05-28 04:55:07 +00:00
|
|
|
|
|
|
|
The same directory as the getwtxt executable
|
|
|
|
/usr/local/getwtxt/
|
|
|
|
/etc/
|
|
|
|
/usr/local/etc/
|
|
|
|
|
|
|
|
The paths are searched in that order. The first configuration
|
2019-06-04 05:41:56 +00:00
|
|
|
file found is used by getwtxt, while the locations further down
|
|
|
|
are ignored.
|
2019-05-28 04:55:07 +00:00
|
|
|
|
|
|
|
Multiple configuration files may be used, however, with the
|
2019-06-04 05:41:56 +00:00
|
|
|
'-c' command line flag. The path passed to getwtxt via '-c' may
|
|
|
|
be relative or absolute. For example, both of the following are
|
|
|
|
allowed:
|
2019-05-28 04:55:07 +00:00
|
|
|
|
|
|
|
./getwtxt -c myconfig.json
|
|
|
|
./getwtxt -c /etc/ExtraConfigsDir/mysecondconfig.toml
|
|
|
|
|
2019-06-04 05:41:56 +00:00
|
|
|
The supported configuration types are:
|
2019-05-28 04:55:07 +00:00
|
|
|
YAML, TOML, JSON, HCL
|
|
|
|
|
|
|
|
The configuration file contains several options used to
|
2019-06-04 05:41:56 +00:00
|
|
|
customize your instance of getwtxt. None are required, they will
|
|
|
|
simply use their default value unless otherwise specified.
|
2019-05-28 04:55:07 +00:00
|
|
|
|
|
|
|
ListenPort: Defines the port getwtxt should bind to.
|
|
|
|
Default: 9001
|
|
|
|
|
|
|
|
DatabasePath: The location of the LevelDB structure
|
|
|
|
used by getwtxt to back up registry data. This
|
|
|
|
can be a relative or absolute path.
|
2019-06-04 05:41:56 +00:00
|
|
|
Default: getwtxt.db
|
2019-05-28 04:55:07 +00:00
|
|
|
|
|
|
|
StdoutLogging: Boolean used to determine whether
|
|
|
|
getwtxt should send logging output to stdout.
|
|
|
|
This is useful for debugging, but you should
|
|
|
|
probably save your logs once your instance
|
|
|
|
is running.
|
|
|
|
Default: false
|
|
|
|
|
|
|
|
LogFile: The location of getwtxt's log file. This,
|
|
|
|
like DatabasePath, can be relative or absolute.
|
|
|
|
Default: getwtxt.log
|
|
|
|
|
|
|
|
DatabasePushInterval: The interval on which getwtxt
|
|
|
|
will push registry data from the in-memory cache
|
|
|
|
to the on-disk LevelDB database. The following
|
|
|
|
time suffixes may be used:
|
|
|
|
ns, us, ms, s, m, h
|
|
|
|
Default: 5m
|
|
|
|
|
|
|
|
StatusFetchInterval: The interval on which getwtxt
|
|
|
|
will crawl all users' twtxt files to retrieve
|
|
|
|
new statuses. The same time suffixes as
|
|
|
|
DatabasePushInterval may be used.
|
|
|
|
Default: 1h
|
|
|
|
|
|
|
|
Instance: Signifies the start of instance-specific
|
|
|
|
meta information. The following are used only
|
|
|
|
for the summary and use information displayed
|
|
|
|
by the default web page for getwtxt. If desired,
|
|
|
|
the assets/tmpl/index.html file may be
|
|
|
|
customized. Keep in mind that in YAML, the
|
|
|
|
following options must be preceded by two spaces
|
|
|
|
so that they are interpreted as sub-options.
|
|
|
|
|
|
|
|
SiteName: The name of your getwtxt instance.
|
|
|
|
Default: getwtxt
|
|
|
|
|
|
|
|
URL: The publicly-accessible URL of your
|
|
|
|
getwtxt instance.
|
|
|
|
Default: https://twtxt.example.com
|
|
|
|
|
|
|
|
OwnerName: Your name.
|
|
|
|
Default: Anonymous Microblogger
|
|
|
|
|
|
|
|
Email: Your email address.
|
|
|
|
Default: nobody@knows
|
|
|
|
|
|
|
|
Description: A short description of your getwtxt
|
|
|
|
instance or your site. As this likely includes
|
|
|
|
whitespace, it should be in double-quotes.
|
|
|
|
This can include XHTML or HTML line breaks if
|
|
|
|
desired:
|
|
|
|
<br />
|
|
|
|
<br>
|
|
|
|
Default: "A fast, resilient twtxt registry
|
|
|
|
server written in Go!"
|
2019-06-04 05:41:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
:: Customizing the Landing Page ::
|
|
|
|
|
|
|
|
If you like, feel free to customize the landing page
|
|
|
|
template provided at
|
|
|
|
|
|
|
|
assets/tmpl/index.html
|
|
|
|
|
|
|
|
It must be standard HTML or XHTML. There are a few special
|
|
|
|
tags available to use that will be replaced with specific values
|
|
|
|
when the template is parsed by getwtxt.
|
|
|
|
|
|
|
|
Values are derived from the "Instance" section of the
|
|
|
|
configuration file, except for the version of getwtxt used. The
|
|
|
|
following will be in the form of:
|
|
|
|
|
|
|
|
{{.TemplateTag}} What it will be replaced with when
|
|
|
|
the template is processed and the landing page is
|
|
|
|
served to a visitor.
|
|
|
|
|
|
|
|
The surrounding double braces and prefixed period are required
|
|
|
|
if you choose to use these tags in your modified landing page. The
|
|
|
|
tags themselves are not required; access to them is provided simply
|
|
|
|
for convenience.
|
|
|
|
|
|
|
|
{{.Vers}} The version of getwtxt used. Does not include
|
|
|
|
the preceding 'v'. Ex: 0.2.0
|
|
|
|
|
|
|
|
{{.Name}} The name of the instance.
|
|
|
|
|
|
|
|
{{.Owner}} The instance owner's name.
|
|
|
|
|
|
|
|
{{.Mail}} Email address used for contacting the instance
|
|
|
|
owner if the need arises.
|
|
|
|
|
|
|
|
{{.Desc}} Short description placed in the configuration
|
|
|
|
file. This is why HTML tags are allowed.
|
|
|
|
|
|
|
|
{{.URL}} The publicly-accessible URL of your instance. In
|
|
|
|
the default landing page, example API calls are shown
|
|
|
|
using this URL for the convenience of the user.
|
|
|
|
|
|
|
|
|
|
|
|
:: Interacting with the Registry ::
|
|
|
|
|
|
|
|
The registry API is rather simple, and can be interacted with
|
|
|
|
via the command line using cURL. Example output of the calls will
|
|
|
|
not be provided.
|
|
|
|
|
|
|
|
Pseudo line-breaks will be represented with a backslash.
|
|
|
|
Examples with line-breaks are not syntactically correct and will
|
|
|
|
be rejected by cURL. Please concatenate the example calls without
|
|
|
|
the backslash. This is only present to maintain consistent
|
|
|
|
formatting for this manual text.
|
|
|
|
|
|
|
|
Ex:
|
|
|
|
/api/plain/users\
|
|
|
|
?q=FOO
|
|
|
|
Should be:
|
|
|
|
/api/plain/users?q=FOO
|
|
|
|
|
|
|
|
All queries (every call except adding users) accept the
|
|
|
|
?page=N parameter, where N > 0. The output is provided in groups
|
|
|
|
of 20 results. For example, indexed at 1, ?page=2 (or &page=2 if
|
|
|
|
it is not the first parameter) appended to any query will return
|
|
|
|
results 21 through 40. If the page requested will exceed the
|
|
|
|
bounds of the query output, the last 20 query results are returned.
|
|
|
|
|
|
|
|
Adding a user:
|
|
|
|
curl -X POST 'http://localhost:9001/api/plain/users\
|
|
|
|
?url=https://gbmor.dev/twtxt.txt&nickname=gbmor'
|
|
|
|
|
|
|
|
Retrieve user list:
|
|
|
|
curl 'http://localhost:9001/api/plain/users'
|
|
|
|
|
|
|
|
Retrieve all statuses:
|
|
|
|
curl 'http://localhost:9001/api/plain/tweets'
|
|
|
|
|
|
|
|
Retrieve all statuses with mentions:
|
|
|
|
curl 'http://localhost:9001/api/plain/mentions'
|
|
|
|
|
|
|
|
Retrieve all statuses with tags:
|
|
|
|
curl 'http://localhost:9001/api/plain/tags'
|
|
|
|
|
|
|
|
Query for users by keyword:
|
|
|
|
curl 'http://localhost:9001/api/plain/users?q=FOO'
|
|
|
|
|
|
|
|
Query for users by URL:
|
|
|
|
curl 'http://localhost:9001/api/plain/users\
|
|
|
|
?url=https://gbmor.dev/twtxt.txt'
|
|
|
|
|
|
|
|
Query for statuses by substring:
|
|
|
|
curl 'http://localhost:9001/api/plain/tweets\
|
|
|
|
?q=SUBSTRING'
|
|
|
|
|
|
|
|
Query for statuses mentioning a user:
|
|
|
|
curl 'http://localhost:9001/api/plain/mentions\
|
|
|
|
?url=https://gbmor.dev/twtxt.txt'
|
|
|
|
|
|
|
|
Query for statuses with a given tag:
|
|
|
|
curl 'http://localhost:9001/api/plain/tags/myTagHere'
|
|
|
|
|
2019-05-12 08:34:06 +00:00
|
|
|
`)
|
|
|
|
}
|