package serve import ( "html/template" "log" "os" "runtime" "strings" "time" "gitler.moe/suwako/gitlin/pages" "gitler.moe/suwako/gitlin/utils" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/compress" "github.com/gofiber/fiber/v2/middleware/limiter" // For debugging purposes // "github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/recover" "github.com/gofiber/template/html" ) func Serve(port string) { utils.LoadCnf() isdebug, ok := os.LookupEnv("GITLIN_DEBUG") engine := html.New("/var/gitlin/views", ".html") if !ok { isdebug = "0" } if isdebug != "0" { engine = html.New("./views", ".html") } engine.AddFunc( // Add unescape function. This is needed to render HTML from Markdown. "unescape", func(s string) template.HTML { return template.HTML(s) }, ) app := fiber.New(fiber.Config{ Views: engine, Prefork: false, AppName: "Gitlin", // kind of screwed up way to fix rate limits EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0/0"}, ProxyHeader: fiber.HeaderXForwardedFor, ErrorHandler: func(ctx *fiber.Ctx, err error) error { code := fiber.StatusInternalServerError if e, ok := err.(*fiber.Error); ok { code = e.Code } link := strings.TrimPrefix(err.Error(), "Cannot GET ") err = ctx.Status(code).Render("error", fiber.Map{ "error": err, "ver": utils.Ver, "ves": utils.Ves, "link": link, }) if err != nil { return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error") } return nil }, }) // For debugging purposes // app.Use(logger.New(logger.Config{ // Format: "[${ip}]:${port} ${status} - ${method} ${path} ${queryParams}\n", // })) app.Use(compress.New(compress.Config{ Level: compress.LevelBestSpeed, // 1 })) app.Use(recover.New()) ratelimiter := limiter.New(limiter.Config{ Max: 5, Expiration: 5 * time.Minute, LimitReached: func(c *fiber.Ctx) error { return c.Status(429).Render("ratelimit_gt", fiber.Map{ "Title": "Rate limit exceeded", "Ver": utils.Ver, "Ves": utils.Ves, }) }, }) staticConfig := fiber.Static{ Compress: true, // Cache-Control: max-age=31536000 MaxAge: 31536000, } proxying, ok := os.LookupEnv("GITLIN_PROXYING_ENABLED") if !ok { proxying = "true" } // add global headers app.Use(func(c *fiber.Ctx) error { c.Set("X-Frame-Options", "SAMEORIGIN") c.Set("X-XSS-Protection", "1; mode=block") c.Set("X-Content-Type-Options", "nosniff") c.Set("Referrer-Policy", "no-referrer") if proxying == "true" { c.Set("Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; script-src 'self'; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; connect-src 'self';") } else { c.Set("Content-Security-Policy", "default-src 'self' https://camo.githubusercontent.com https://avatars.githubusercontent.com https://github.com https://raw.githubusercontent.com https://gist.github.com; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://camo.githubusercontent.com https://avatars.githubusercontent.com https://gist.github.com; font-src 'self'; connect-src 'self' https://camo.githubusercontent.com https://avatars.githubusercontent.com https://github.com https://raw.githubusercontent.com https://gist.github.com; frame-ancestors 'self'; form-action 'self'; base-uri 'self'") } c.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload") return c.Next() }) app.Get("/", pages.HandleIndex) if isdebug == "0" { app.Static("/css", "/var/gitlin/public/css", staticConfig) app.Static("/robots.txt", "/var/gitlin/public/robots.txt", staticConfig) app.Static("/favicon.ico", "/var/gitlin/public/assets/favicon.ico", staticConfig) app.Static("/logo.png", "/var/gitlin/public/assets/logo.png", staticConfig) app.Static("/git.png", "/var/gitlin/public/assets/git.png", staticConfig) } else { app.Static("/css", "./public/css", staticConfig) app.Static("/robots.txt", "./public/robots.txt", staticConfig) app.Static("/favicon.ico", "./public/assets/favicon.ico", staticConfig) app.Static("/logo.png", "./public/assets/logo.png", staticConfig) app.Static("/git.png", "./public/assets/git.png", staticConfig) } app.Get("/about", pages.HandleAbout) app.Get("/explore", ratelimiter, pages.HandleExplore) app.Get("/:user", pages.HandleUser) app.Get("/avatar/:id", func(c *fiber.Ctx) error { if proxying == "true" { utils.ProxyRequest(c, "https://avatars.githubusercontent.com/u/"+c.Params("id")+"?v=4") return nil } else { c.Redirect("https://avatars.githubusercontent.com/u/" + c.Params("id") + "?v=4") return nil } }) app.Get("/:user/:repo", pages.HandleRepo) app.Get("/:user/:repo/info/+", func(c *fiber.Ctx) error { // This is needed but it looks like git doesn't send it. Or maybe i just shouldn't program at midnight param := "?service=git-upload-pack" if proxying == "true" { utils.ProxyRequest(c, "https://github.com/"+c.Params("user")+"/"+c.Params("repo")+".git/info/"+c.Params("+")+param) return nil } else { c.Redirect("https://github.com/" + c.Params("user") + "/" + c.Params("repo") + ".git/info/" + c.Params("+") + param) return nil } }) app.Post("/:user/:repo/git-upload-pack", func(c *fiber.Ctx) error { if proxying == "true" { utils.ProxyRequest(c, "https://github.com/"+c.Params("user")+"/"+c.Params("repo")+".git/git-upload-pack") return nil } else { c.Redirect("https://github.com/" + c.Params("user") + "/" + c.Params("repo") + ".git/git-upload-pack") return nil } }) app.Get("/:user/:repo/blob/:branch/+", pages.FileView) app.Get("/:user/:repo/tree/:branch/+", pages.DirView) app.Get("/:user/:repo/tree/:branch", pages.HandleRepo) app.Get("/:user/:repo/archive/:branch", func(c *fiber.Ctx) error { if proxying == "true" { utils.ProxyRequest(c, "https://github.com/"+c.Params("user")+"/"+c.Params("repo")+"/archive/"+c.Params("branch")) return nil } else { c.Redirect("https://github.com/" + c.Params("user") + "/" + c.Params("repo") + "/archive/" + c.Params("branch")) return nil } }) app.Get("/raw/:user/:repo/:branch/+", func(c *fiber.Ctx) error { if proxying == "true" { utils.ProxyRequest(c, "https://raw.githubusercontent.com/"+c.Params("user")+"/"+c.Params("repo")+"/"+c.Params("branch")+"/"+c.Params("+")) return nil } else { c.Redirect("https://raw.githubusercontent.com/" + c.Params("user") + "/" + c.Params("repo") + "/" + c.Params("branch") + "/" + c.Params("+")) return nil } }) app.Get("/camo/:p1/:p2", func(c *fiber.Ctx) error { if proxying == "true" { utils.ProxyRequest(c, "https://camo.githubusercontent.com/"+c.Params("p1")+"/"+c.Params("p2")) return nil } else { c.Redirect("https://camo.githubusercontent.com/" + c.Params("p1") + "/" + c.Params("p2")) return nil } }) app.Get("/gist/:user/:gistID", pages.HandleGist) app.Get("/download/gist/:user/:gistID/:revision", func(c *fiber.Ctx) error { if proxying == "true" { utils.ProxyRequest(c, "https://gist.github.com/"+c.Params("user")+"/"+c.Params("gistID")+"/archive/"+c.Params("revision")+".tar.gz") return nil } else { c.Redirect("https://gist.github.com/" + c.Params("user") + "/" + c.Params("gistID") + "/archive/" + c.Params("revision") + ".tar.gz") return nil } }) app.Get("/:user/:repo/commits/:branch?", pages.HandleCommits) app.Get("/:user/:repo/branches/:cat?", pages.HandleBranches) api := app.Group("/api") v1 := api.Group("/v1") v1.Get("/version", func(c *fiber.Ctx) error { return c.JSON(fiber.Map{ "version": utils.Ver, "fiberversion": fiber.Version, "goversion": runtime.Version(), }) }) val, ok := os.LookupEnv("GITLIN_PORT") if !ok { val = "9715" } if port != "" { val = port } log.Fatal(app.Listen(":" + val)) }