package main import ( "context" "embed" "io/fs" "log" "net" "net/http" "os" os_signal "os/signal" "syscall" "time" "git.exozy.me/exozyme/status/core" "git.exozy.me/exozyme/status/scanner" "github.com/cbroglie/mustache" ) //go:embed all:public var staticAssets embed.FS //go:embed all:templates var templateAssets embed.FS func parseAndRenderTemplate(templateName string, data interface{}) (string, error) { rawTemplate, err := templateAssets.ReadFile(templateName) if err != nil { return "", err } tmpl, err := mustache.ParseString(string(rawTemplate)) if err != nil { log.Println("Failed to parse template:", err) return "", err } res, err := tmpl.Render(data) if err != nil { log.Println("Failed to render template:", err) return "", err } return res, nil } func rootPage(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { return } config_new, err := core.LoadConfig() if err != nil { log.Println("[WARN] failed to load config:", err) } else { config = config_new } // get service status' timeout := time.Second ctx, cancel := context.WithTimeout(context.Background(), timeout) data := scanner.CheckServiceBatch(ctx, config.Service) cancel() // don't uncomment this in production. verbose // log.Printf("%v", data) parsedTemplate, err := parseAndRenderTemplate("templates/index.html", data) if err != nil { http.Error(w, err.Error(), 500) } w.Write([]byte(parsedTemplate)) } func removeFile(path string) { err := os.Remove(path) if err != nil && !os.IsNotExist(err) { log.Panic(err) } } var config *core.Config var dbm core.Database func cleanup() { log.Println("Cleaning up") _ = dbm.StoreSelfStatus(core.EventSelfDown, time.Now()) } func main() { var err error config, err = core.LoadConfig() if err != nil { log.Panic(err) } dbm, err = core.OpenDatabase(config.Database) if err != nil { log.Panic(err) } http.HandleFunc("/", rootPage) // serve public folder from embedfs htmlContent, err := fs.Sub(fs.FS(staticAssets), "public") if err != nil { log.Panic(err) } fs := http.FileServer(http.FS(htmlContent)) http.Handle("/public/", http.StripPrefix("/public/", fs)) protocol := "tcp" addr := "localhost:3333" var unix_socket_path string var is_unix bool if config.UnixSocket != "" { // get unix socket path from the config else unix_socket_path = config.UnixSocket is_unix = true } else { // fallback unix_socket_path, is_unix = os.LookupEnv("PORT") } if is_unix { protocol = "unix" addr = unix_socket_path // VnPower: is this unsafe? removeFile(addr) // ideally this should be run at program exit. sadly Go's defer doesn't care about signals or panic } srv := &http.Server{Addr: addr, Handler: nil} ln, err := net.Listen(protocol, addr) if err != nil { log.Panic(err) } if is_unix { err = os.Chmod(addr, 0660) if err != nil { log.Panic(err) } log.Printf("Listening on %v\n", addr) } else { log.Printf("Listening on http://%v/\n", addr) } // log startup and register cleanup (on SIGTERM or panic) ch_sigterm := make(chan os.Signal) os_signal.Notify(ch_sigterm, syscall.SIGTERM) log.Print("Starting up") err = dbm.StoreSelfStatus(core.EventSelfUp, time.Now()) if err != nil { log.Panic(err) } defer cleanup() go func(){ <- ch_sigterm err := srv.Shutdown(context.Background()) log.Printf("Error when shutting down: %v", err) }() // serve http err = srv.Serve(ln) if err != http.ErrServerClosed { log.Panic(err) } }