Migration This is my new public git source viewer. Source coming soon. Please contact with concerns/bugs. Contact me

main.go

3a50036d96eb14462e96598fd169d1c79264ac4b · 3.2 KB · 123 lines raw

1 // Command gsg serves a modular git browser.
2 //
3 // gsg -root /srv/git -addr :8080 -name "rschanz.org"
4 // gsg -root /srv/git -templates ./templates # hot-reload the theme
5 //
6 // Per-repository settings live in each repo's own git config, cgit-style:
7 //
8 // git --git-dir=/srv/git/foo.git config gsg.title "foo"
9 // git --git-dir=/srv/git/foo.git config gsg.description "a thing"
10 // git --git-dir=/srv/git/foo.git config gsg.hide true
11 package main
12
13 import (
14 "embed"
15 "flag"
16 "fmt"
17 "io/fs"
18 "log"
19 "log/slog"
20 "net/http"
21 "os"
22 "path/filepath"
23 "sort"
24 "time"
25
26 "gsg/internal/core"
27 "gsg/internal/gitx"
28 _ "gsg/internal/modules" // registers the built-in modules
29 "gsg/internal/site"
30 )
31
32 //go:embed templates
33 var templateFS embed.FS
34
35 //go:embed static
36 var staticFS embed.FS
37
38 func main() {
39 var (
40 root = flag.String("root", "/srv/git", "directory containing bare repositories")
41 addr = flag.String("addr", ":8080", "listen address")
42 name = flag.String("name", "git", "site name")
43 base = flag.String("base", "", "base URL")
44 tmplDir = flag.String("templates", "", "serve templates from this directory, re-parsed per request")
45 cache = flag.Int("cache", 4096, "module cache entries")
46 timeout = flag.Duration("timeout", 10*time.Second, "per-request git budget")
47 )
48 flag.Parse()
49
50 if err := run(*root, *addr, *name, *base, *tmplDir, *cache, *timeout); err != nil {
51 log.Fatalf("gsg: %v", err)
52 }
53 }
54
55 func run(root, addr, name, base, tmplDir string, cacheSize int, timeout time.Duration) error {
56 siteCfg := &core.Site{Name: name, BaseURL: base, Root: root}
57
58 handles, err := discover(root)
59 if err != nil {
60 return err
61 }
62 if len(handles) == 0 {
63 return fmt.Errorf("no repositories found under %s", root)
64 }
65 siteCfg.Repos = handles
66
67 var tfs fs.FS
68 if tmplDir != "" {
69 tfs = os.DirFS(tmplDir)
70 } else if tfs, err = fs.Sub(templateFS, "templates"); err != nil {
71 return err
72 }
73
74 srv, err := core.NewServer(siteCfg, site.Routes(), tfs, cacheSize)
75 if err != nil {
76 return err
77 }
78 srv.TemplateDir = tmplDir
79 srv.Timeout = timeout
80
81 static, err := fs.Sub(staticFS, "static")
82 if err != nil {
83 return err
84 }
85
86 mux := http.NewServeMux()
87 mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(static))))
88 mux.Handle("/", srv.Handler())
89
90 slog.Info("gsg listening",
91 "addr", addr, "repos", len(handles),
92 "routes", len(site.Routes()), "modules", len(core.Names()))
93
94 hs := &http.Server{
95 Addr: addr,
96 Handler: mux,
97 ReadHeaderTimeout: 5 * time.Second,
98 WriteTimeout: timeout + 5*time.Second,
99 }
100 return hs.ListenAndServe()
101 }
102
103 // discover finds bare repositories one level below root.
104 // Extend to walk recursively if you nest by section.
105 func discover(root string) ([]core.Handle, error) {
106 entries, err := os.ReadDir(root)
107 if err != nil {
108 return nil, err
109 }
110 var handles []core.Handle
111 for _, e := range entries {
112 if !e.IsDir() {
113 continue
114 }
115 r, err := gitx.Open(filepath.Join(root, e.Name()))
116 if err != nil {
117 continue // not a repository
118 }
119 handles = append(handles, core.Handle{Repo: r, Info: core.LoadRepoInfo(r)})
120 }
121 sort.Slice(handles, func(i, j int) bool { return handles[i].Info.Slug < handles[j].Info.Slug })
122 return handles, nil
123 }