latest
This commit is contained in:
parent
d33f093ab5
commit
1c7e2f11e0
@ -3,6 +3,7 @@ FROM golang:1.13 as builder
|
|||||||
WORKDIR /go/src/github.com/sneak/feta
|
WORKDIR /go/src/github.com/sneak/feta
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
#RUN make lint && make build
|
||||||
RUN make build
|
RUN make build
|
||||||
|
|
||||||
WORKDIR /go
|
WORKDIR /go
|
||||||
|
12
Makefile
12
Makefile
@ -25,10 +25,10 @@ ifneq ($(UNAME_S),Darwin)
|
|||||||
GOFLAGS = -ldflags "-linkmode external -extldflags -static $(GOLDFLAGS)"
|
GOFLAGS = -ldflags "-linkmode external -extldflags -static $(GOLDFLAGS)"
|
||||||
endif
|
endif
|
||||||
|
|
||||||
default: run
|
default: rundebug
|
||||||
|
|
||||||
rundebug: build
|
rundebug: build
|
||||||
DEBUG=1 ./$(FN)
|
GOTRACEBACK=all DEBUG=1 ./$(FN)
|
||||||
|
|
||||||
run: build
|
run: build
|
||||||
./$(FN)
|
./$(FN)
|
||||||
@ -38,9 +38,13 @@ clean:
|
|||||||
|
|
||||||
build: ./$(FN)
|
build: ./$(FN)
|
||||||
|
|
||||||
|
lint:
|
||||||
|
go get -u golang.org/x/lint/golint
|
||||||
|
go get -u github.com/GeertJohan/fgt
|
||||||
|
fgt golint
|
||||||
|
|
||||||
go-get:
|
go-get:
|
||||||
go get -v
|
go get -v
|
||||||
cd cmd/$(FN) && go get -v
|
|
||||||
|
|
||||||
./$(FN): *.go cmd/*/*.go go-get
|
./$(FN): *.go cmd/*/*.go go-get
|
||||||
cd cmd/$(FN) && go build -o ../../$(FN) $(GOFLAGS) .
|
cd cmd/$(FN) && go build -o ../../$(FN) $(GOFLAGS) .
|
||||||
@ -59,7 +63,7 @@ build-docker-image: clean
|
|||||||
build-docker-image-dist: is_uncommitted clean
|
build-docker-image-dist: is_uncommitted clean
|
||||||
docker build -t $(IMAGENAME):$(VERSION) -t $(IMAGENAME):latest -t $(IMAGENAME):$(BUILDTIMETAG) .
|
docker build -t $(IMAGENAME):$(VERSION) -t $(IMAGENAME):latest -t $(IMAGENAME):$(BUILDTIMETAG) .
|
||||||
|
|
||||||
dist: build-docker-image
|
dist: lint build-docker-image
|
||||||
-mkdir -p ./output
|
-mkdir -p ./output
|
||||||
docker run --rm --entrypoint cat $(IMAGENAME) /bin/$(FN) > output/$(FN)
|
docker run --rm --entrypoint cat $(IMAGENAME) /bin/$(FN) > output/$(FN)
|
||||||
docker save $(IMAGENAME) | bzip2 > output/$(BUILDTIMEFILENAME).$(FN).tbz2
|
docker save $(IMAGENAME) | bzip2 > output/$(BUILDTIMEFILENAME).$(FN).tbz2
|
||||||
|
76
apihandlers.go
Normal file
76
apihandlers.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package feta
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
import "net/http"
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
type hash map[string]interface{}
|
||||||
|
|
||||||
|
func (a *TootArchiverAPIServer) instances() []hash {
|
||||||
|
resp := make([]hash, 0)
|
||||||
|
now := time.Now()
|
||||||
|
for _, v := range a.archiver.manager.listInstances() {
|
||||||
|
i := make(hash)
|
||||||
|
// FIXME figure out why a very short lock here deadlocks
|
||||||
|
v.Lock()
|
||||||
|
i["hostname"] = v.hostname
|
||||||
|
i["nextCheck"] = v.nextFetch.UTC().Format(time.RFC3339)
|
||||||
|
i["nextCheckAfter"] = (-1 * now.Sub(v.nextFetch)).String()
|
||||||
|
i["successCount"] = v.successCount
|
||||||
|
i["errorCount"] = v.errorCount
|
||||||
|
i["identified"] = v.identified
|
||||||
|
i["status"] = v.status
|
||||||
|
i["software"] = "unknown"
|
||||||
|
i["version"] = "unknown"
|
||||||
|
if v.identified {
|
||||||
|
i["software"] = v.serverImplementationString
|
||||||
|
i["version"] = v.serverVersionString
|
||||||
|
}
|
||||||
|
v.Unlock()
|
||||||
|
resp = append(resp, i)
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TootArchiverAPIServer) getIndexHandler() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
index := &gin.H{
|
||||||
|
"page": "index",
|
||||||
|
"instances": a.instances(),
|
||||||
|
"status": "ok",
|
||||||
|
"now": time.Now().UTC().Format(time.RFC3339),
|
||||||
|
"uptime": a.archiver.Uptime().String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := json.Marshal(index)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(json)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TootArchiverAPIServer) getHealthCheckHandler() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
resp := &gin.H{
|
||||||
|
"status": "ok",
|
||||||
|
"now": time.Now().UTC().Format(time.RFC3339),
|
||||||
|
"uptime": a.archiver.Uptime().String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := json.Marshal(resp)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(json)
|
||||||
|
}
|
||||||
|
}
|
@ -43,30 +43,8 @@ func (a *TootArchiverAPIServer) getRouter() *gin.Engine {
|
|||||||
// attach logger middleware
|
// attach logger middleware
|
||||||
r.Use(ginzerolog.Logger("gin"))
|
r.Use(ginzerolog.Logger("gin"))
|
||||||
|
|
||||||
r.GET("/.well-known/healthcheck.json", func(c *gin.Context) {
|
r.GET("/.well-known/healthcheck.json", gin.WrapF(a.getHealthCheckHandler()))
|
||||||
c.JSON(200, gin.H{
|
r.GET("/", gin.WrapF(a.getIndexHandler()))
|
||||||
"status": "ok",
|
|
||||||
"now": time.Now().UTC().Format(time.RFC3339),
|
|
||||||
"uptime": a.archiver.Uptime().String(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
r.GET("/", func(c *gin.Context) {
|
|
||||||
ir := a.archiver.manager.instanceSummaryReport()
|
|
||||||
|
|
||||||
il := a.archiver.manager.instanceListForApi()
|
|
||||||
c.JSON(200, gin.H{
|
|
||||||
"status": "ok",
|
|
||||||
"now": time.Now().UTC().Format(time.RFC3339),
|
|
||||||
"uptime": a.archiver.Uptime().String(),
|
|
||||||
"instanceSummary": gin.H{
|
|
||||||
"total": ir.total,
|
|
||||||
"up": ir.up,
|
|
||||||
"identified": ir.identified,
|
|
||||||
},
|
|
||||||
"instanceList": il,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
@ -26,7 +26,6 @@ func (a *TootArchiver) RunForever() {
|
|||||||
newInstanceHostnameNotifications := make(chan InstanceHostname, 10000)
|
newInstanceHostnameNotifications := make(chan InstanceHostname, 10000)
|
||||||
|
|
||||||
a.locator = NewInstanceLocator()
|
a.locator = NewInstanceLocator()
|
||||||
|
|
||||||
a.manager = NewInstanceManager()
|
a.manager = NewInstanceManager()
|
||||||
|
|
||||||
a.locator.AddInstanceNotificationChannel(newInstanceHostnameNotifications)
|
a.locator.AddInstanceNotificationChannel(newInstanceHostnameNotifications)
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Version string
|
|
||||||
var Buildtime string
|
|
||||||
var Builduser string
|
|
||||||
var Buildarch string
|
|
||||||
|
|
||||||
type AppIdentity struct {
|
|
||||||
Version string
|
|
||||||
Buildtime string
|
|
||||||
Builduser string
|
|
||||||
Buildarch string
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAppIdentity() *AppIdentity {
|
|
||||||
i := new(AppIdentity)
|
|
||||||
i.Version = Version
|
|
||||||
i.Buildtime = Buildtime
|
|
||||||
i.Builduser = Builduser
|
|
||||||
i.Buildarch = Buildarch
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
func identify() {
|
|
||||||
i := GetAppIdentity()
|
|
||||||
log.Info().
|
|
||||||
Str("version", i.Version).
|
|
||||||
Str("buildarch", i.Buildarch).
|
|
||||||
Str("buildtime", i.Buildtime).
|
|
||||||
Str("builduser", i.Builduser).
|
|
||||||
Msg("starting")
|
|
||||||
}
|
|
@ -1,57 +1,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "os"
|
import "os"
|
||||||
import "sync"
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
import "github.com/rs/zerolog"
|
|
||||||
import "github.com/rs/zerolog/log"
|
|
||||||
import "golang.org/x/crypto/ssh/terminal"
|
|
||||||
|
|
||||||
import "github.com/sneak/feta"
|
import "github.com/sneak/feta"
|
||||||
|
|
||||||
|
// these are filled in at link-time by the build scripts
|
||||||
|
var Version string
|
||||||
|
var Buildtime string
|
||||||
|
var Builduser string
|
||||||
|
var Buildarch string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
os.Exit(app())
|
os.Exit(feta.CLIEntry(Version, Buildtime, Builduser, Buildarch))
|
||||||
}
|
|
||||||
|
|
||||||
func app() int {
|
|
||||||
log.Logger = log.With().Caller().Logger()
|
|
||||||
if terminal.IsTerminal(int(os.Stdout.Fd())) {
|
|
||||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
|
||||||
}
|
|
||||||
|
|
||||||
identify()
|
|
||||||
|
|
||||||
// always log in UTC
|
|
||||||
zerolog.TimestampFunc = func() time.Time {
|
|
||||||
return time.Now().UTC()
|
|
||||||
}
|
|
||||||
|
|
||||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
|
||||||
if os.Getenv("DEBUG") != "" {
|
|
||||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
archiver := feta.NewTootArchiver()
|
|
||||||
|
|
||||||
api := new(feta.TootArchiverAPIServer)
|
|
||||||
api.SetArchiver(archiver)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
// start api webserver goroutine
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
api.Serve()
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
archiver.RunForever()
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
91
feta.go
Normal file
91
feta.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package feta
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
//import "os/signal"
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
//import "syscall"
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
import "github.com/rs/zerolog"
|
||||||
|
import "github.com/rs/zerolog/log"
|
||||||
|
import "golang.org/x/crypto/ssh/terminal"
|
||||||
|
|
||||||
|
func CLIEntry(version string, buildtime string, buildarch string, builduser string) int {
|
||||||
|
f := new(FetaProcess)
|
||||||
|
f.version = version
|
||||||
|
f.buildtime = buildtime
|
||||||
|
f.buildarch = buildarch
|
||||||
|
f.builduser = builduser
|
||||||
|
f.setupLogging()
|
||||||
|
return f.runForever()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetaProcess is the main structure/process of this app
|
||||||
|
type FetaProcess struct {
|
||||||
|
version string
|
||||||
|
buildtime string
|
||||||
|
buildarch string
|
||||||
|
builduser string
|
||||||
|
archiver *TootArchiver
|
||||||
|
api *TootArchiverAPIServer
|
||||||
|
wg *sync.WaitGroup
|
||||||
|
//quit chan os.Signal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FetaProcess) identify() {
|
||||||
|
log.Info().
|
||||||
|
Str("version", f.version).
|
||||||
|
Str("buildtime", f.buildtime).
|
||||||
|
Str("buildarch", f.buildarch).
|
||||||
|
Str("builduser", f.builduser).
|
||||||
|
Msg("starting")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FetaProcess) setupLogging() {
|
||||||
|
log.Logger = log.With().Caller().Logger()
|
||||||
|
if terminal.IsTerminal(int(os.Stdout.Fd())) {
|
||||||
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||||
|
}
|
||||||
|
|
||||||
|
// always log in UTC
|
||||||
|
zerolog.TimestampFunc = func() time.Time {
|
||||||
|
return time.Now().UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||||
|
if os.Getenv("DEBUG") != "" {
|
||||||
|
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.identify()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FetaProcess) runForever() int {
|
||||||
|
f.archiver = NewTootArchiver()
|
||||||
|
|
||||||
|
f.api = new(TootArchiverAPIServer)
|
||||||
|
f.api.SetArchiver(f.archiver)
|
||||||
|
|
||||||
|
//FIXME(sneak) get this channel into places that need to be shut down
|
||||||
|
//f.quit = make(chan os.Signal)
|
||||||
|
//signal.Notify(f.quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
f.wg = new(sync.WaitGroup)
|
||||||
|
// start api webserver goroutine
|
||||||
|
f.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
f.api.Serve()
|
||||||
|
f.wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
f.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
f.archiver.RunForever()
|
||||||
|
f.wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
f.wg.Wait()
|
||||||
|
return 0
|
||||||
|
}
|
59
instance.go
59
instance.go
@ -14,6 +14,8 @@ import "github.com/rs/zerolog/log"
|
|||||||
|
|
||||||
const NodeInfoSchemaVersionTwoName = "http://nodeinfo.diaspora.software/ns/schema/2.0"
|
const NodeInfoSchemaVersionTwoName = "http://nodeinfo.diaspora.software/ns/schema/2.0"
|
||||||
|
|
||||||
|
const INSTANCE_NODEINFO_TIMEOUT = time.Second * 5
|
||||||
|
|
||||||
const INSTANCE_HTTP_TIMEOUT = time.Second * 60
|
const INSTANCE_HTTP_TIMEOUT = time.Second * 60
|
||||||
|
|
||||||
const INSTANCE_SPIDER_INTERVAL = time.Second * 60
|
const INSTANCE_SPIDER_INTERVAL = time.Second * 60
|
||||||
@ -40,25 +42,27 @@ const (
|
|||||||
|
|
||||||
type Instance struct {
|
type Instance struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
errorCount uint
|
errorCount uint
|
||||||
successCount uint
|
successCount uint
|
||||||
highestId int
|
highestId int
|
||||||
hostname string
|
hostname string
|
||||||
identified bool
|
identified bool
|
||||||
fetching bool
|
fetching bool
|
||||||
impl InstanceImplementation
|
implementation InstanceImplementation
|
||||||
backend *InstanceBackend
|
backend *InstanceBackend
|
||||||
status InstanceStatus
|
status InstanceStatus
|
||||||
nextFetch time.Time
|
nextFetch time.Time
|
||||||
nodeInfoUrl string
|
nodeInfoUrl string
|
||||||
serverVersion string
|
serverVersionString string
|
||||||
|
serverImplementationString string
|
||||||
|
fetchingLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInstance(hostname InstanceHostname) *Instance {
|
func NewInstance(hostname InstanceHostname) *Instance {
|
||||||
self := new(Instance)
|
self := new(Instance)
|
||||||
self.hostname = string(hostname)
|
self.hostname = string(hostname)
|
||||||
self.status = InstanceStatusUnknown
|
self.status = InstanceStatusUnknown
|
||||||
self.nextFetch = time.Now().Add(-1 * time.Second)
|
self.setNextFetchAfter(1 * time.Second)
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +79,9 @@ func (self *Instance) setNextFetchAfter(d time.Duration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *Instance) Fetch() {
|
func (self *Instance) Fetch() {
|
||||||
|
self.fetchingLock.Lock()
|
||||||
|
defer self.fetchingLock.Unlock()
|
||||||
|
|
||||||
err := self.detectNodeTypeIfNecessary()
|
err := self.detectNodeTypeIfNecessary()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
self.setNextFetchAfter(INSTANCE_ERROR_INTERVAL)
|
self.setNextFetchAfter(INSTANCE_ERROR_INTERVAL)
|
||||||
@ -85,8 +92,8 @@ func (self *Instance) Fetch() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//self.setNextFetchAfter(INSTANCE_SPIDER_INTERVAL)
|
self.setNextFetchAfter(INSTANCE_SPIDER_INTERVAL)
|
||||||
log.Info().Msgf("i (%s) should check for toots", self.hostname)
|
//log.Info().Msgf("i (%s) IS NOW READY FOR FETCH", self.hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Instance) dueForFetch() bool {
|
func (self *Instance) dueForFetch() bool {
|
||||||
@ -99,7 +106,7 @@ func (self *Instance) dueForFetch() bool {
|
|||||||
func (self *Instance) nodeIdentified() bool {
|
func (self *Instance) nodeIdentified() bool {
|
||||||
self.RLock()
|
self.RLock()
|
||||||
defer self.RUnlock()
|
defer self.RUnlock()
|
||||||
if self.impl > Unknown {
|
if self.implementation > Unknown {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -127,7 +134,7 @@ func (self *Instance) registerSuccess() {
|
|||||||
self.successCount++
|
self.successCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Instance) ApiReport() *gin.H {
|
func (self *Instance) APIReport() *gin.H {
|
||||||
r := gin.H{}
|
r := gin.H{}
|
||||||
return &r
|
return &r
|
||||||
}
|
}
|
||||||
@ -141,7 +148,7 @@ func (i *Instance) Up() bool {
|
|||||||
func (i *Instance) fetchNodeInfoURL() error {
|
func (i *Instance) fetchNodeInfoURL() error {
|
||||||
url := fmt.Sprintf("https://%s/.well-known/nodeinfo", i.hostname)
|
url := fmt.Sprintf("https://%s/.well-known/nodeinfo", i.hostname)
|
||||||
var c = &http.Client{
|
var c = &http.Client{
|
||||||
Timeout: INSTANCE_HTTP_TIMEOUT,
|
Timeout: INSTANCE_NODEINFO_TIMEOUT,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().
|
log.Debug().
|
||||||
@ -217,7 +224,7 @@ func (i *Instance) fetchNodeInfo() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var c = &http.Client{
|
var c = &http.Client{
|
||||||
Timeout: INSTANCE_HTTP_TIMEOUT,
|
Timeout: INSTANCE_NODEINFO_TIMEOUT,
|
||||||
}
|
}
|
||||||
|
|
||||||
//FIXME make sure the nodeinfourl is on the same domain as the instance
|
//FIXME make sure the nodeinfourl is on the same domain as the instance
|
||||||
@ -268,8 +275,8 @@ func (i *Instance) fetchNodeInfo() error {
|
|||||||
Msg("received nodeinfo from instance")
|
Msg("received nodeinfo from instance")
|
||||||
|
|
||||||
i.Lock()
|
i.Lock()
|
||||||
defer i.Unlock()
|
i.serverVersionString = ni.Software.Version
|
||||||
i.serverVersion = ni.Software.Version
|
i.serverImplementationString = ni.Software.Name
|
||||||
|
|
||||||
ni.Software.Name = strings.ToLower(ni.Software.Name)
|
ni.Software.Name = strings.ToLower(ni.Software.Name)
|
||||||
|
|
||||||
@ -278,22 +285,26 @@ func (i *Instance) fetchNodeInfo() error {
|
|||||||
Str("hostname", i.hostname).
|
Str("hostname", i.hostname).
|
||||||
Str("software", ni.Software.Name).
|
Str("software", ni.Software.Name).
|
||||||
Msg("detected server software")
|
Msg("detected server software")
|
||||||
i.registerSuccess()
|
|
||||||
i.identified = true
|
i.identified = true
|
||||||
i.impl = Pleroma
|
i.implementation = Pleroma
|
||||||
i.status = InstanceStatusIdentified
|
i.status = InstanceStatusIdentified
|
||||||
|
i.Unlock()
|
||||||
|
i.registerSuccess()
|
||||||
return nil
|
return nil
|
||||||
} else if ni.Software.Name == "mastodon" {
|
} else if ni.Software.Name == "mastodon" {
|
||||||
i.registerSuccess()
|
i.registerSuccess()
|
||||||
i.identified = true
|
i.identified = true
|
||||||
i.impl = Mastodon
|
i.implementation = Mastodon
|
||||||
i.status = InstanceStatusIdentified
|
i.status = InstanceStatusIdentified
|
||||||
|
i.Unlock()
|
||||||
|
i.registerSuccess()
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
log.Error().
|
log.Error().
|
||||||
Str("hostname", i.hostname).
|
Str("hostname", i.hostname).
|
||||||
Str("software", ni.Software.Name).
|
Str("software", ni.Software.Name).
|
||||||
Msg("FIXME unknown server implementation")
|
Msg("FIXME unknown server implementation")
|
||||||
|
i.Unlock()
|
||||||
i.registerError()
|
i.registerError()
|
||||||
return errors.New("FIXME unknown server implementation")
|
return errors.New("FIXME unknown server implementation")
|
||||||
}
|
}
|
||||||
|
33
locator.go
33
locator.go
@ -12,12 +12,16 @@ const INDEX_API_TIMEOUT = time.Second * 60
|
|||||||
|
|
||||||
var USER_AGENT = "https://github.com/sneak/feta indexer bot; sneak@sneak.berlin for feedback"
|
var USER_AGENT = "https://github.com/sneak/feta indexer bot; sneak@sneak.berlin for feedback"
|
||||||
|
|
||||||
// check with indices only hourly
|
// INDEX_CHECK_INTERVAL defines the interval for downloading new lists from
|
||||||
|
// the index APIs run by mastodon/pleroma (default: 1h)
|
||||||
var INDEX_CHECK_INTERVAL = time.Second * 60 * 60
|
var INDEX_CHECK_INTERVAL = time.Second * 60 * 60
|
||||||
|
|
||||||
// check with indices after 10 mins if they failed
|
// INDEX_ERROR_INTERVAL is used for when the index fetch/parse fails
|
||||||
|
// (default: 10m)
|
||||||
var INDEX_ERROR_INTERVAL = time.Second * 60 * 10
|
var INDEX_ERROR_INTERVAL = time.Second * 60 * 10
|
||||||
|
|
||||||
|
// LOG_REPORT_INTERVAL defines how long between logging internal
|
||||||
|
// stats/reporting for user supervision
|
||||||
var LOG_REPORT_INTERVAL = time.Second * 60
|
var LOG_REPORT_INTERVAL = time.Second * 60
|
||||||
|
|
||||||
const mastodonIndexUrl = "https://instances.social/list.json?q%5Busers%5D=&q%5Bsearch%5D=&strict=false"
|
const mastodonIndexUrl = "https://instances.social/list.json?q%5Busers%5D=&q%5Bsearch%5D=&strict=false"
|
||||||
@ -26,6 +30,8 @@ const pleromaIndexUrl = "https://distsn.org/cgi-bin/distsn-pleroma-instances-api
|
|||||||
type InstanceLocator struct {
|
type InstanceLocator struct {
|
||||||
pleromaIndexNextRefresh *time.Time
|
pleromaIndexNextRefresh *time.Time
|
||||||
mastodonIndexNextRefresh *time.Time
|
mastodonIndexNextRefresh *time.Time
|
||||||
|
mastodonIndexFetchLock sync.Mutex
|
||||||
|
pleromaIndexFetchLock sync.Mutex
|
||||||
reportInstanceVia chan InstanceHostname
|
reportInstanceVia chan InstanceHostname
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
}
|
}
|
||||||
@ -55,12 +61,20 @@ func (self *InstanceLocator) Locate() {
|
|||||||
x := time.Now()
|
x := time.Now()
|
||||||
for {
|
for {
|
||||||
log.Info().Msg("InstanceLocator tick")
|
log.Info().Msg("InstanceLocator tick")
|
||||||
if self.pleromaIndexNextRefresh.Before(time.Now()) {
|
go func() {
|
||||||
self.locatePleroma()
|
self.pleromaIndexFetchLock.Lock()
|
||||||
}
|
if self.pleromaIndexNextRefresh.Before(time.Now()) {
|
||||||
if self.mastodonIndexNextRefresh.Before(time.Now()) {
|
self.locatePleroma()
|
||||||
self.locateMastodon()
|
}
|
||||||
}
|
self.pleromaIndexFetchLock.Unlock()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
self.mastodonIndexFetchLock.Lock()
|
||||||
|
if self.mastodonIndexNextRefresh.Before(time.Now()) {
|
||||||
|
self.locateMastodon()
|
||||||
|
}
|
||||||
|
self.mastodonIndexFetchLock.Unlock()
|
||||||
|
}()
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
if time.Now().After(x.Add(LOG_REPORT_INTERVAL)) {
|
if time.Now().After(x.Add(LOG_REPORT_INTERVAL)) {
|
||||||
x = time.Now()
|
x = time.Now()
|
||||||
@ -95,6 +109,9 @@ func (self *InstanceLocator) locateMastodon() {
|
|||||||
self.mastodonIndexNextRefresh = &t
|
self.mastodonIndexNextRefresh = &t
|
||||||
self.Unlock()
|
self.Unlock()
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
log.Info().
|
||||||
|
Msg("fetched mastodon index")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
68
manager.go
68
manager.go
@ -5,7 +5,7 @@ import "time"
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
import "runtime"
|
import "runtime"
|
||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
//import "github.com/gin-gonic/gin"
|
||||||
import "github.com/rs/zerolog/log"
|
import "github.com/rs/zerolog/log"
|
||||||
|
|
||||||
type InstanceBackend interface {
|
type InstanceBackend interface {
|
||||||
@ -17,6 +17,7 @@ type InstanceManager struct {
|
|||||||
instances map[InstanceHostname]*Instance
|
instances map[InstanceHostname]*Instance
|
||||||
newInstanceNotifications chan InstanceHostname
|
newInstanceNotifications chan InstanceHostname
|
||||||
startup time.Time
|
startup time.Time
|
||||||
|
addLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInstanceManager() *InstanceManager {
|
func NewInstanceManager() *InstanceManager {
|
||||||
@ -50,14 +51,14 @@ func (self *InstanceManager) logCaller(msg string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *InstanceManager) Lock() {
|
func (self *InstanceManager) Lock() {
|
||||||
self.logCaller("instancemanager attempting to lock")
|
//self.logCaller("instancemanager attempting to lock")
|
||||||
self.mu.Lock()
|
self.mu.Lock()
|
||||||
self.logCaller("instancemanager locked")
|
//self.logCaller("instancemanager locked")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *InstanceManager) Unlock() {
|
func (self *InstanceManager) Unlock() {
|
||||||
self.mu.Unlock()
|
self.mu.Unlock()
|
||||||
self.logCaller("instancemanager unlocked")
|
//self.logCaller("instancemanager unlocked")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *InstanceManager) AddInstanceNotificationChannel(via chan InstanceHostname) {
|
func (self *InstanceManager) AddInstanceNotificationChannel(via chan InstanceHostname) {
|
||||||
@ -67,12 +68,10 @@ func (self *InstanceManager) AddInstanceNotificationChannel(via chan InstanceHos
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *InstanceManager) Manage() {
|
func (self *InstanceManager) Manage() {
|
||||||
self.managerInfiniteLoop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *InstanceManager) managerInfiniteLoop() {
|
|
||||||
log.Info().Msg("InstanceManager starting")
|
log.Info().Msg("InstanceManager starting")
|
||||||
go self.receiveNewInstanceHostnames()
|
go func() {
|
||||||
|
self.receiveNewInstanceHostnames()
|
||||||
|
}()
|
||||||
self.startup = time.Now()
|
self.startup = time.Now()
|
||||||
for {
|
for {
|
||||||
log.Info().Msg("InstanceManager tick")
|
log.Info().Msg("InstanceManager tick")
|
||||||
@ -83,16 +82,20 @@ func (self *InstanceManager) managerInfiniteLoop() {
|
|||||||
|
|
||||||
func (self *InstanceManager) managerLoop() {
|
func (self *InstanceManager) managerLoop() {
|
||||||
self.Lock()
|
self.Lock()
|
||||||
defer self.Unlock()
|
il := make([]*Instance, 0)
|
||||||
for _, v := range self.instances {
|
for _, v := range self.instances {
|
||||||
// wrap in a new goroutine because this needs to iterate
|
il = append(il, v)
|
||||||
// fast and unlock fast
|
|
||||||
go func() {
|
|
||||||
if v.dueForFetch() {
|
|
||||||
go v.Fetch()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
self.Unlock()
|
||||||
|
|
||||||
|
for _, v := range il {
|
||||||
|
if v.dueForFetch() {
|
||||||
|
go func(i *Instance) {
|
||||||
|
i.Fetch()
|
||||||
|
}(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *InstanceManager) hostnameExists(newhn InstanceHostname) bool {
|
func (self *InstanceManager) hostnameExists(newhn InstanceHostname) bool {
|
||||||
@ -107,11 +110,19 @@ func (self *InstanceManager) hostnameExists(newhn InstanceHostname) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *InstanceManager) addInstanceByHostname(newhn InstanceHostname) {
|
func (self *InstanceManager) addInstanceByHostname(newhn InstanceHostname) {
|
||||||
// only add it if we haven't seen the hostname before
|
// we do these one at a time
|
||||||
|
self.addLock.Lock()
|
||||||
|
defer self.addLock.Unlock()
|
||||||
|
|
||||||
if self.hostnameExists(newhn) {
|
if self.hostnameExists(newhn) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
i := NewInstance(newhn)
|
i := NewInstance(newhn)
|
||||||
|
// we do node detection under the addLock to avoid thundering
|
||||||
|
// on startup
|
||||||
|
i.detectNodeTypeIfNecessary()
|
||||||
|
|
||||||
self.Lock()
|
self.Lock()
|
||||||
defer self.Unlock()
|
defer self.Unlock()
|
||||||
self.instances[newhn] = i
|
self.instances[newhn] = i
|
||||||
@ -121,7 +132,11 @@ func (self *InstanceManager) receiveNewInstanceHostnames() {
|
|||||||
var newhn InstanceHostname
|
var newhn InstanceHostname
|
||||||
for {
|
for {
|
||||||
newhn = <-self.newInstanceNotifications
|
newhn = <-self.newInstanceNotifications
|
||||||
self.addInstanceByHostname(newhn)
|
// receive them fast out of the channel, let the adding function lock to add
|
||||||
|
// them one at a time
|
||||||
|
go func() {
|
||||||
|
self.addInstanceByHostname(newhn)
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,21 +181,6 @@ func (self *InstanceManager) listInstances() []*Instance {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *InstanceManager) instanceListForApi() []*gin.H {
|
|
||||||
var output []*gin.H
|
|
||||||
|
|
||||||
l := self.listInstances()
|
|
||||||
for _, v := range l {
|
|
||||||
id := &gin.H{
|
|
||||||
"hostname": v.hostname,
|
|
||||||
"up": v.Up(),
|
|
||||||
"nextFetch": string(time.Now().Sub(v.nextFetch)),
|
|
||||||
}
|
|
||||||
output = append(output, id)
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *InstanceManager) instanceSummaryReport() *InstanceSummaryReport {
|
func (self *InstanceManager) instanceSummaryReport() *InstanceSummaryReport {
|
||||||
self.Lock()
|
self.Lock()
|
||||||
defer self.Unlock()
|
defer self.Unlock()
|
||||||
|
Loading…
Reference in New Issue
Block a user