package main import ( "embed" "errors" "fmt" "io" "log" "net/http" "os" "time" cfg "website/internal/config" "github.com/ansrivas/fiberprometheus/v2" "github.com/ardanlabs/conf/v3" "github.com/getsentry/sentry-go" "github.com/gofiber/contrib/fibersentry" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cache" "github.com/gofiber/fiber/v2/middleware/compress" "github.com/gofiber/fiber/v2/middleware/etag" "github.com/gofiber/fiber/v2/middleware/filesystem" "github.com/gofiber/fiber/v2/middleware/healthcheck" "github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/recover" "github.com/gofiber/fiber/v2/middleware/skip" "github.com/shengyanli1982/law" ) type Config struct { Production bool `conf:"default:false"` Port uint16 `conf:"default:3000,short:p"` BaseURL cfg.URL `conf:"default:http://localhost:3000,short:b"` RedirectOtherHostnames bool `conf:"default:false"` } // TODO purge CSS // TODO HTTP2 https://github.com/dgrr/http2 type Host struct { Fiber *fiber.App } //go:embed all:public/* var fs embed.FS var Commit string func main() { runtimeConfig := Config{} if help, err := conf.Parse("", &runtimeConfig); err != nil { if errors.Is(err, conf.ErrHelpWanted) { fmt.Println(help) os.Exit(1) } log.Panicf("parsing runtime configuration: %v", err) } config, err := cfg.GetConfig() if err != nil { log.Panicf("parsing configuration file: %v", err) } err = sentry.Init(sentry.ClientOptions{ Dsn: os.Getenv("SENTRY_DSN"), Release: os.Getenv("FLY_MACHINE_VERSION"), Environment: os.Getenv("ENV"), }) if err != nil { log.Panic("could not set up sentry") } defer sentry.Flush(2 * time.Second) metricServer := fiber.New(fiber.Config{ GETOnly: true, StrictRouting: true, DisableDefaultDate: true, DisableHeaderNormalizing: true, DisableStartupMessage: true, }) prometheus := fiberprometheus.New("homestead") prometheus.RegisterAt(metricServer, "/metrics") hosts := map[string]*Host{} internal := fiber.New(fiber.Config{ GETOnly: true, StrictRouting: true, }) internal.Use(healthcheck.New(healthcheck.Config{})) hosts["fly-internal"] = &Host{internal} website := fiber.New(fiber.Config{ EnableTrustedProxyCheck: true, TrustedProxies: []string{"172.16.0.0/16"}, ProxyHeader: "Fly-Client-IP", GETOnly: true, ReadTimeout: 5 * time.Minute, WriteTimeout: 5 * time.Minute, StrictRouting: true, UnescapePath: true, }) website.Use(prometheus.Middleware) website.Use(fibersentry.New(fibersentry.Config{})) website.Use(func(c *fiber.Ctx) error { for k, v := range config.Extra.Headers { c.Set(k, v) } if c.Secure() { c.Set("Strict-Transport-Security", "max-age=31536000; includeSubdomains; preload") } return c.Next() }) website.Use(compress.New()) website.Use(cache.New(cache.Config{ CacheControl: true, Expiration: 24 * time.Hour, StoreResponseHeaders: true, })) // must be after compress to be encoding-independent website.Use(etag.New(etag.Config{ Weak: true, })) website.Use(recover.New(recover.Config{})) files := http.FS(fs) notFoundHandler := func(c *fiber.Ctx) error { c.Status(fiber.StatusNotFound).Type("html", "utf-8") content, err := files.Open("public/404.html") if err != nil { c.Type("txt") return c.SendString("404 Not Found") } return c.SendStream(content) } website.Get("/404.html", notFoundHandler) website.Use("/", filesystem.New(filesystem.Config{ Root: files, PathPrefix: "public", ContentTypeCharset: "utf-8", MaxAge: int((24 * time.Hour).Seconds()), })) website.Use(notFoundHandler) hosts[runtimeConfig.BaseURL.Host] = &Host{website} toplevel := fiber.New(fiber.Config{ DisableStartupMessage: runtimeConfig.Production, ServerHeader: fmt.Sprintf("website (%s)", Commit), }) toplevel.Get("/health", func(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) }) var logWriter io.Writer if runtimeConfig.Production { logWriter = law.NewWriteAsyncer(os.Stdout, nil) } else { logWriter = os.Stdout } toplevel.Use(skip.New(logger.New(logger.Config{ Output: logWriter, Format: "${protocol} ${method} ${status} ${host} ${url} ${respHeader:Location}\n", }), func(c *fiber.Ctx) bool { return c.Hostname() == "fly-internal" })) toplevel.Use(func(c *fiber.Ctx) error { host := hosts[c.Hostname()] if host == nil { if runtimeConfig.RedirectOtherHostnames { return c.Redirect(runtimeConfig.BaseURL.JoinPath(c.OriginalURL()).String()) } else { hosts[runtimeConfig.BaseURL.Host].Fiber.Handler()(c.Context()) return nil } } else { host.Fiber.Handler()(c.Context()) return nil } }) go func() { err := metricServer.Listen(":9091") log.Printf("failed to start metrics server: %v", err) }() log.Fatal(toplevel.Listen(fmt.Sprintf("%s:%d", "", runtimeConfig.Port))) }