From b5bad4defc6c75b9b969658229ce5fd2f3a46107 Mon Sep 17 00:00:00 2001 From: Ben Busby Date: Tue, 21 Jan 2025 13:46:29 -0700 Subject: Rewrite project, add daily update of services list The project was rewritten from Elixir to Go, primarily because: - I don't write Elixir anymore and don't want to maintain a project in a language I no longer write - I already write Go for other projects, including my day job, so it's a safer bet for a project that I want to maintain long term - Go allows me to build portable executables that will make it easier for others to run farside on their own machines The Go version of Farsside also has a built in task to fetch the latest services{-full}.json file from the repo and ingest it, which makes running a farside server a lot simpler. It also automatically fetches the latest instance state from https://farside.link unless configured as a primary farside node, which will allow others to use farside without increasing traffic to all instances that are queried by farside (just to the farside node itself). --- db/cron.go | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 db/cron.go (limited to 'db/cron.go') diff --git a/db/cron.go b/db/cron.go new file mode 100644 index 0000000..a7282a4 --- /dev/null +++ b/db/cron.go @@ -0,0 +1,139 @@ +package db + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "strings" + "time" + + "github.com/benbusby/farside/services" + "github.com/robfig/cron/v3" +) + +const defaultPrimary = "https://farside.link/state" +const defaultCFPrimary = "https://cf.farside.link/state" + +var LastUpdate time.Time + +func InitCronTasks() { + log.Println("Initializing cron tasks...") + + cronDisabled := os.Getenv("FARSIDE_CRON") + if len(cronDisabled) == 0 || cronDisabled == "1" { + c := cron.New() + c.AddFunc("@every 10m", queryServiceInstances) + c.AddFunc("@daily", updateServiceList) + c.Start() + } + + queryServiceInstances() +} + +func updateServiceList() { + fileName := services.GetServicesFileName() + _, _ = services.FetchServicesFile(fileName) + services.InitializeServices() +} + +func queryServiceInstances() { + log.Println("Starting instance queries...") + + isPrimary := os.Getenv("FARSIDE_PRIMARY") + if len(isPrimary) == 0 || isPrimary != "1" { + remoteServices, err := fetchInstancesFromPrimary() + if err != nil { + log.Println("Unable to fetch instances from primary", err) + } + + for _, service := range remoteServices { + SetInstances(service.Type, service.Instances) + } + + return + } + + for _, service := range services.ServiceList { + fmt.Printf("===== %s =====\n", service.Type) + var instances []string + for _, instance := range service.Instances { + testURL := strings.ReplaceAll( + service.TestURL, + "<%=query%>", + "current+weather") + available := queryServiceInstance( + instance, + testURL, + ) + + if available { + instances = append(instances, instance) + } + } + + SetInstances(service.Type, instances) + } + + LastUpdate = time.Now().UTC() +} + +func fetchInstancesFromPrimary() ([]services.Service, error) { + primaryURL := defaultPrimary + useCF := os.Getenv("FARSIDE_CF_ENABLED") + if len(useCF) > 0 && useCF == "1" { + primaryURL = defaultCFPrimary + } + + resp, err := http.Get(primaryURL) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var serviceList []services.Service + err = json.Unmarshal(bodyBytes, &serviceList) + return serviceList, err +} + +func queryServiceInstance(instance, testURL string) bool { + testMode := os.Getenv("FARSIDE_TEST") + if len(testMode) > 0 && testMode == "1" { + return true + } + + ua := "Mozilla/5.0 (compatible; Farside/1.0.0; +https://farside.link)" + url := instance + testURL + + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + fmt.Println(" [ERRO] Failed to create new http request!", err) + return false + } + + req.Header.Set("User-Agent", ua) + client := &http.Client{ + Timeout: 10 * time.Second, + } + resp, err := client.Do(req) + + if err != nil { + fmt.Println(" [ERRO] Error fetching instance:", err) + return false + } else if resp.StatusCode != http.StatusOK { + fmt.Printf(" [WARN] Received non-200 status for %s\n", url) + return false + } else { + fmt.Printf(" [INFO] Received 200 status for %s\n", url) + } + + return true +} -- cgit v1.2.3