web frontend for git repositories, written in Go git.pocka.jp/legit.git
3

Configure Feed

Select the types of activity you want to include in your feed.

Embed static files into the binary

https://tangled.org/pocka.jp/legit/issues/3

Having to copy the "static/" directories is PITA, and distribution
burden.

Shota FUJI (Jul 13, 2026, 5:41 PM +0900) 0e496be9 208876a9

+48 -16
+6 -2
config.yaml
··· 28 28 # <https://pkg.go.dev/html/template> 29 29 templates: ./templates 30 30 31 - # Path to a directory containing static assets. 32 - # User can access files in this directory by accessing "/static/*". 31 + # [optional] 32 + # Path to a directory containing custom static assets. 33 + # If you omit this, legit will use its default static asset directory 34 + # ("static" directory next to this file) embedded in the binary. 35 + # 36 + # Visitors can access files in this directory by accessing "/static/*". 33 37 static: ./static 34 38 35 39 meta:
+6 -2
config/config.go
··· 58 58 if c.Dirs.Templates, err = resolvePath(c.Dirs.Templates, f); err != nil { 59 59 return nil, err 60 60 } 61 - if c.Dirs.Static, err = resolvePath(c.Dirs.Static, f); err != nil { 62 - return nil, err 61 + 62 + // Override static dir 63 + if c.Dirs.Static != "" { 64 + if c.Dirs.Static, err = resolvePath(c.Dirs.Static, f); err != nil { 65 + return nil, err 66 + } 63 67 } 64 68 65 69 return &c, nil
-1
demo/config.yaml
··· 30 30 31 31 dirs: 32 32 templates: ../templates 33 - static: ../static 34 33 35 34 meta: 36 35 title: legit Demo
+1 -1
flake.nix
··· 31 31 root = ./.; 32 32 fileset = pkgs.lib.fileset.unions [ 33 33 ./config.yaml 34 - ./static 35 34 ./templates 36 35 ]; 37 36 }; ··· 47 46 fileset = unions [ 48 47 ./go.mod 49 48 ./go.sum 49 + ./static 50 50 (fileFilter (file: file.hasExt "go") ./.) 51 51 ]; 52 52 };
+23 -1
main.go
··· 1 1 package main 2 2 3 3 import ( 4 + "embed" 4 5 "flag" 5 6 "fmt" 7 + "io/fs" 6 8 "log" 7 9 "math" 8 10 "net/http" 11 + "os" 9 12 10 13 "github.com/pocka/legit/config" 11 14 "github.com/pocka/legit/routes" 12 15 ) 16 + 17 + //go:embed static/* 18 + var defaultStaticDir embed.FS 13 19 14 20 func main() { 15 21 var cfg string ··· 46 52 log.Fatalf("unveil: %s", err) 47 53 } 48 54 49 - mux := routes.Handlers(c) 55 + var staticDir fs.FS 56 + if c.Dirs.Static != "" { 57 + root, err := os.OpenRoot(c.Dirs.Static) 58 + if err != nil { 59 + log.Fatalf("Unable to open static dir: %s", err) 60 + } 61 + defer root.Close() 62 + 63 + staticDir = root.FS() 64 + } else { 65 + staticDir, err = fs.Sub(defaultStaticDir, "static") 66 + if err != nil { 67 + log.Fatalf("Unable to open default static dir: %s", err) 68 + } 69 + } 70 + 71 + mux := routes.Handlers(c, staticDir) 50 72 addr := fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port) 51 73 log.Println("starting server on", addr) 52 74 log.Fatal(http.ListenAndServe(addr, mux))
+3 -1
routes/handler.go
··· 1 1 package routes 2 2 3 3 import ( 4 + "io/fs" 4 5 "net/http" 5 6 6 7 "github.com/microcosm-cc/bluemonday" ··· 29 30 } 30 31 } 31 32 32 - func Handlers(c *config.Config) *http.ServeMux { 33 + func Handlers(c *config.Config, staticDir fs.FS) *http.ServeMux { 33 34 mux := http.NewServeMux() 34 35 d := deps{ 35 36 c: c, 37 + staticDir: staticDir, 36 38 ugcPolicy: bluemonday.UGCPolicy(), 37 39 } 38 40
+9 -5
routes/routes.go
··· 5 5 "compress/gzip" 6 6 "fmt" 7 7 "html/template" 8 + "io/fs" 8 9 "log" 9 10 "net/http" 10 11 "net/url" ··· 24 25 25 26 type deps struct { 26 27 c *config.Config 28 + 29 + // staticDir should be path traversal attack resilient FS, such as the one 30 + // returned by "os.Root.FS". 31 + staticDir fs.FS 27 32 28 33 // ugcPolicy is a bluemonday policy for user generated content. 29 34 ugcPolicy *bluemonday.Policy ··· 636 641 } 637 642 638 643 func (d *deps) ServeStatic(w http.ResponseWriter, r *http.Request) { 639 - f := r.PathValue("file") 640 - f = filepath.Clean(f) 641 - f, err := securejoin.SecureJoin(d.c.Dirs.Static, f) 642 - if err != nil { 644 + name := r.PathValue("file") 645 + name = filepath.Clean(name) 646 + if !filepath.IsLocal(name) { 643 647 d.Write404(w) 644 648 return 645 649 } 646 650 647 - http.ServeFile(w, r, f) 651 + http.ServeFileFS(w, r, d.staticDir, name) 648 652 }
-1
tests/k6/config.yaml
··· 10 10 11 11 dirs: 12 12 templates: ../../templates 13 - static: ../../static 14 13 15 14 meta: 16 15 title: Legit Load Testing
-2
tests/k6/repos.nix
··· 15 15 fileset = lib.fileset.unions [ 16 16 ./create_repos.bash 17 17 ./config.yaml 18 - ../../static 19 18 ../../templates 20 19 ]; 21 20 }; ··· 25 24 cd $out/tests/k6/repos 26 25 bash $src/tests/k6/create_repos.bash 27 26 ln -s $src/tests/k6/config.yaml $out/tests/k6/config.yaml 28 - ln -s $src/static $out/static 29 27 ln -s $src/templates $out/templates 30 28 ''; 31 29