diff --git a/cmd/gothub/root.go b/cmd/gothub/root.go new file mode 100644 index 0000000..0af80ee --- /dev/null +++ b/cmd/gothub/root.go @@ -0,0 +1,24 @@ +package gothub + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "gothub", + Short: "An alternative front-end for GitHub.", + Long: `An alternative front-end for GitHub, written by Odyssey346 and Midou360.`, + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintf(os.Stderr, "Error running GotHub's CLI: %v", err) + os.Exit(1) + } +} diff --git a/cmd/gothub/serve.go b/cmd/gothub/serve.go new file mode 100644 index 0000000..0477e06 --- /dev/null +++ b/cmd/gothub/serve.go @@ -0,0 +1,20 @@ +package gothub + +import ( + "github.com/spf13/cobra" + + "codeberg.org/gothub/gothub/serve" +) + +var serveCmd = &cobra.Command{ + Use: "serve", + Short: "Start the web server.", + Long: `Start the web server.`, + Run: func(cmd *cobra.Command, args []string) { + serve.Serve() + }, +} + +func init() { + rootCmd.AddCommand(serveCmd) +} diff --git a/go.mod b/go.mod index 4577f6f..533234c 100644 --- a/go.mod +++ b/go.mod @@ -15,11 +15,14 @@ require ( github.com/andybalholm/brotli v1.0.4 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/gorilla/css v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/klauspost/compress v1.15.12 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/rivo/uniseg v0.4.3 // indirect + github.com/spf13/cobra v1.6.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect diff --git a/go.sum b/go.sum index 6091c12..e9c9623 100644 --- a/go.sum +++ b/go.sum @@ -100,6 +100,7 @@ github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWH github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -246,6 +247,9 @@ github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -342,7 +346,10 @@ github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY52 github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -815,6 +822,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go index a179862..083dd6c 100644 --- a/main.go +++ b/main.go @@ -1,155 +1,8 @@ package main -import ( - "context" - "log" - - "os" - - "github.com/gofiber/fiber/v2" - - "github.com/gofiber/template/html" - - "codeberg.org/gothub/gothub/pages" - - "github.com/gofiber/fiber/v2/middleware/cache" - - "time" - - "github.com/gofiber/fiber/v2/middleware/proxy" - - "github.com/gofiber/fiber/v2/middleware/compress" - - "github.com/gofiber/fiber/v2/middleware/recover" - - "github.com/gofiber/fiber/v2/middleware/limiter" - - "html/template" - - "github.com/carlmjohnson/requests" -) +import "codeberg.org/gothub/gothub/cmd/gothub" func main() { - engine := html.New("./views", ".html") - - engine.AddFunc( - // add unescape function - "unescape", func(s string) template.HTML { - return template.HTML(s) - }, - ) - - app := fiber.New(fiber.Config{ - Views: engine, - Prefork: false, - AppName: "GotHub", - // fucked 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 - } - - err = ctx.Status(code).Render("error", fiber.Map{ - "error": err, - }) - if err != nil { - return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error") - } - - return nil - }, - }) - - app.Use(cache.New(cache.Config{ - Expiration: 5 * time.Minute, - })) - - app.Get("/", func(c *fiber.Ctx) error { - return c.Render("index", fiber.Map{ - "host": c.Hostname(), - }) - }) - - 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", - }) - }, - }) - - app.Static("/css", "./public/css") - app.Static("/fonts", "./public/fonts") - app.Static("/robots.txt", "./public/robots.txt") - app.Static("/favicon.ico", "./public/assets/favicon.ico") - app.Static("/logo.svg", "./public/assets/logo.svg") - app.Get("/explore", ratelimiter, pages.HandleExplore) - app.Get("/:user", ratelimiter, pages.HandleUser) - app.Get("/:user/:repo", ratelimiter, pages.HandleRepo) - app.Get("/file/:user/:repo/:branch/:file", func(c *fiber.Ctx) error { - var file string - url := "https://raw.githubusercontent.com/" + c.Params("user") + "/" + c.Params("repo") + "/" + c.Params("branch") + "/" + c.Params("file") - err := requests. - URL(url). - ToString(&file). - Fetch(context.Background()) - if err != nil { - return c.Status(404).Render("error", fiber.Map{ - "error": err, - }) - } - return c.Render("file", fiber.Map{ - "file": file, - "fullname": c.Params("user") + "/" + c.Params("repo"), - "name": c.Params("file"), - "branch": c.Params("branch"), - }) - }) - app.Get("/avatar/:id", func(c *fiber.Ctx) error { - url := "https://avatars.githubusercontent.com/u/" + c.Params("id") + "?v=4" - if err := proxy.Do(c, url); err != nil { - return err - } - // Remove Server header from response - c.Response().Header.Del(fiber.HeaderServer) - return nil - }) - app.Get("/download/:user/:repo/:branch", ratelimiter, func(c *fiber.Ctx) error { - url := "https://github.com/" + c.Params("user") + "/" + c.Params("repo") + "/archive/" + c.Params("branch") + ".zip" - if err := proxy.Do(c, url); err != nil { - return err - } - // Remove Server header from response - c.Response().Header.Del(fiber.HeaderServer) - return nil - }) - app.Get("/raw/:user/:repo/:branch/:file", ratelimiter, func(c *fiber.Ctx) error { - url := "https://raw.githubusercontent.com/" + c.Params("user") + "/" + c.Params("repo") + "/" + c.Params("branch") + "/" + c.Params("file") - if err := proxy.Do(c, url); err != nil { - return err - } - // Remove Server header from response - c.Response().Header.Del(fiber.HeaderServer) - return nil - }) - val, ok := os.LookupEnv("GOTHUB_PORT") - if !ok { - val = "3000" - } else { - log.Println("Using port from env variable") - } - log.Fatal(app.Listen(":" + val)) + gothub.Execute() + // Looking for the Fiber server? It's in serve/serve.go. } diff --git a/serve/serve.go b/serve/serve.go new file mode 100644 index 0000000..bd31ef4 --- /dev/null +++ b/serve/serve.go @@ -0,0 +1,144 @@ +package serve + +import ( + "context" + "html/template" + "log" + "os" + "time" + + "codeberg.org/gothub/gothub/pages" + "github.com/carlmjohnson/requests" + "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/limiter" + "github.com/gofiber/fiber/v2/middleware/proxy" + "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/gofiber/template/html" +) + +func Serve() { + engine := html.New("./views", ".html") + + engine.AddFunc( + // add unescape function + "unescape", func(s string) template.HTML { + return template.HTML(s) + }, + ) + + app := fiber.New(fiber.Config{ + Views: engine, + Prefork: false, + AppName: "GotHub", + // fucked 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 + } + + err = ctx.Status(code).Render("error", fiber.Map{ + "error": err, + }) + if err != nil { + return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error") + } + + return nil + }, + }) + + app.Use(cache.New(cache.Config{ + Expiration: 5 * time.Minute, + })) + + app.Get("/", func(c *fiber.Ctx) error { + return c.Render("index", fiber.Map{ + "host": c.Hostname(), + }) + }) + + 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", + }) + }, + }) + + app.Static("/css", "./public/css") + app.Static("/fonts", "./public/fonts") + app.Static("/robots.txt", "./public/robots.txt") + app.Static("/favicon.ico", "./public/assets/favicon.ico") + app.Static("/logo.svg", "./public/assets/logo.svg") + app.Get("/explore", ratelimiter, pages.HandleExplore) + app.Get("/:user", ratelimiter, pages.HandleUser) + app.Get("/avatar/:id", func(c *fiber.Ctx) error { + url := "https://avatars.githubusercontent.com/u/" + c.Params("id") + "?v=4" + if err := proxy.Do(c, url); err != nil { + return err + } + // Remove Server header from response + c.Response().Header.Del(fiber.HeaderServer) + return nil + }) + app.Get("/:user/:repo", ratelimiter, pages.HandleRepo) + app.Get("/file/:user/:repo/:branch/:file", func(c *fiber.Ctx) error { + var file string + url := "https://raw.githubusercontent.com/" + c.Params("user") + "/" + c.Params("repo") + "/" + c.Params("branch") + "/" + c.Params("file") + err := requests. + URL(url). + ToString(&file). + Fetch(context.Background()) + if err != nil { + return c.Status(404).Render("error", fiber.Map{ + "error": err, + }) + } + return c.Render("file", fiber.Map{ + "file": file, + "fullname": c.Params("user") + "/" + c.Params("repo"), + "name": c.Params("file"), + "branch": c.Params("branch"), + }) + }) + app.Get("/download/:user/:repo/:branch", ratelimiter, func(c *fiber.Ctx) error { + url := "https://github.com/" + c.Params("user") + "/" + c.Params("repo") + "/archive/" + c.Params("branch") + ".zip" + if err := proxy.Do(c, url); err != nil { + return err + } + // Remove Server header from response + c.Response().Header.Del(fiber.HeaderServer) + return nil + }) + app.Get("/raw/:user/:repo/:branch/:file", ratelimiter, func(c *fiber.Ctx) error { + url := "https://raw.githubusercontent.com/" + c.Params("user") + "/" + c.Params("repo") + "/" + c.Params("branch") + "/" + c.Params("file") + if err := proxy.Do(c, url); err != nil { + return err + } + // Remove Server header from response + c.Response().Header.Del(fiber.HeaderServer) + return nil + }) + val, ok := os.LookupEnv("GOTHUB_PORT") + if !ok { + val = "3000" + } else { + log.Println("Using port from env variable") + } + log.Fatal(app.Listen(":" + val)) +}