A CLI-first git forge.

cli forge git self-hosted

https://gitbay.org

Commit a5b8bd2151

a5b8bd215107b78649c253f3b52a1e6422157194

parent: 2baa586b20

Verified · cmc

cmc <hello@cleberg.net> · 2026-08-24T16:38:06Z

web: identity — wordmark, favicon, footer

Header wordmark pairs an inline-SVG wave glyph with the site name.
/favicon.svg serves an embedded SVG (route word added to the reserved
username list). Footer carries a gitbay link and the binary's short
VCS revision via runtime/debug buildinfo. Footer sits at the viewport
bottom on short pages.
internal/httpd/routes.go +1
@@ -28,6 +28,7 @@ func (s *Server) Routes() []Route {
2828 routes = append(routes,
2929 Route{Method: "GET", Pattern: "/{$}", Handler: s.index},
3030 Route{Method: "GET", Pattern: "/static/style.css", Handler: s.stylesheet},
31 Route{Method: "GET", Pattern: "/favicon.svg", Handler: s.favicon},
3132 Route{Method: "GET", Pattern: "/{owner}", Handler: s.ownerPage},
3233 Route{Method: "GET", Pattern: "/{owner}/{repo}", Handler: s.repoHome},
3334 Route{Method: "GET", Pattern: "/{owner}/{repo}/tree/{ref}/{path...}", Handler: s.tree},
internal/httpd/web.go +5
@@ -49,6 +49,11 @@ func (s *Server) stylesheet(w http.ResponseWriter, r *http.Request) {
4949 w.Write(web.StyleCSS)
5050 }
5151
52func (s *Server) favicon(w http.ResponseWriter, r *http.Request) {
53 w.Header().Set("Content-Type", "image/svg+xml")
54 w.Write(web.FaviconSVG)
55}
56
5257 // describedRepo pairs a repo with its description for listings.
5358 type describedRepo struct {
5459 store.Repo
internal/policy/names.go +12 −11
@@ -11,17 +11,18 @@ import (
1111 // the httpd mux's top-level routes must be reflected here; the httpd package
1212 // asserts this in its tests.
1313 var reservedNames = map[string]bool{
14 "admin": true,
15 "api": true,
16 "archive": true,
17 "explore": true,
18 "login": true,
19 "logout": true,
20 "new": true,
21 "raw": true,
22 "register": true,
23 "settings": true,
24 "static": true,
14 "admin": true,
15 "api": true,
16 "archive": true,
17 "explore": true,
18 "favicon.svg": true,
19 "login": true,
20 "logout": true,
21 "new": true,
22 "raw": true,
23 "register": true,
24 "settings": true,
25 "static": true,
2526 }
2627
2728 // namePat matches valid user, org, and repo names: lowercase alphanumerics,
internal/web/static/favicon.svg added +1
@@ -0,0 +1 @@
1<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect x="1" y="1" width="22" height="22" rx="6" fill="#0b6c80"/><path d="M5 9.5c2-2.5 4-2.5 6 0s4 2.5 6 0" stroke="#ffffff" stroke-width="2.2" fill="none" stroke-linecap="round"/><path d="M5 15c2-2.5 4-2.5 6 0s4 2.5 6 0" stroke="#ffffff" stroke-width="2.2" fill="none" stroke-linecap="round"/></svg>
internal/web/static/style.css +26 −1
@@ -70,7 +70,11 @@ body {
7070 background: var(--bg);
7171 color: var(--fg);
7272 font: var(--fs-3)/1.6 var(--sans);
73 min-height: 100vh;
74 display: flex;
75 flex-direction: column;
7376 }
77main { flex: 1; }
7478
7579 a { color: var(--accent); text-decoration: none; }
7680 a:hover { text-decoration: underline; }
@@ -91,8 +95,16 @@ header {
9195 border-bottom: 1px solid var(--faint);
9296 padding: var(--sp-3) var(--sp-5);
9397 }
94a.site { font-weight: 650; color: var(--fg); letter-spacing: -0.01em; }
98a.site {
99 font-weight: 650;
100 color: var(--fg);
101 letter-spacing: -0.01em;
102 display: inline-flex;
103 align-items: center;
104 gap: var(--sp-2);
105}
95106 a.site:hover { text-decoration: none; color: var(--accent); }
107a.site svg.mark { display: block; }
96108 main {
97109 max-width: 64rem;
98110 margin: 0 auto;
@@ -250,6 +262,19 @@ button {
250262 }
251263 button:hover { filter: brightness(1.08); }
252264
265/* footer */
266footer {
267 max-width: 64rem;
268 margin: 0 auto;
269 padding: var(--sp-4) var(--sp-5);
270 border-top: 1px solid var(--faint);
271 color: var(--muted);
272 font-size: var(--fs-1);
273}
274footer p { margin: 0; }
275footer a { color: var(--muted); text-decoration: underline; }
276footer a:hover { color: var(--accent); }
277
253278 /* mobile */
254279 @media (max-width: 40rem) {
255280 header { padding: var(--sp-3) var(--sp-4); }
internal/web/templates/layout.html +5 −1
@@ -5,14 +5,18 @@
55 <meta name="viewport" content="width=device-width, initial-scale=1">
66 <title>{{template "title" .}}</title>
77 <link rel="stylesheet" href="/static/style.css">
8<link rel="icon" href="/favicon.svg" type="image/svg+xml">
89 </head>
910 <body>
1011 <header>
11 <nav><a class="site" href="/">{{.Site}}</a></nav>
12 <nav><a class="site" href="/"><svg class="mark" width="20" height="20" viewBox="0 0 24 24" aria-hidden="true"><rect x="1" y="1" width="22" height="22" rx="6" fill="var(--accent)"/><path d="M5 9.5c2-2.5 4-2.5 6 0s4 2.5 6 0" stroke="var(--accent-fg)" stroke-width="2.2" fill="none" stroke-linecap="round"/><path d="M5 15c2-2.5 4-2.5 6 0s4 2.5 6 0" stroke="var(--accent-fg)" stroke-width="2.2" fill="none" stroke-linecap="round"/></svg>{{.Site}}</a></nav>
1213 </header>
1314 <main>
1415 {{template "content" .}}
1516 </main>
17<footer>
18 <p>powered by <a href="https://gitbay.org/krz/gitbay">gitbay</a>{{with gitbayVersion}} · <code>{{.}}</code>{{end}}</p>
19</footer>
1620 </body>
1721 </html>{{end}}
1822
internal/web/web.go +23 −1
@@ -6,6 +6,8 @@ import (
66 "embed"
77 "html/template"
88 "io"
9 "runtime/debug"
10 "sync"
911 )
1012
1113 //go:embed templates/*.html
@@ -14,10 +16,30 @@ var templateFS embed.FS
1416 //go:embed static/style.css
1517 var StyleCSS []byte
1618
19//go:embed static/favicon.svg
20var FaviconSVG []byte
21
22// version returns the short VCS revision baked into the binary, or "" when
23// built outside a checkout. Used by the layout footer.
24var version = sync.OnceValue(func() string {
25 info, ok := debug.ReadBuildInfo()
26 if !ok {
27 return ""
28 }
29 for _, s := range info.Settings {
30 if s.Key == "vcs.revision" && len(s.Value) >= 10 {
31 return s.Value[:10]
32 }
33 }
34 return ""
35})
36
37var funcs = template.FuncMap{"gitbayVersion": func() string { return version() }}
38
1739 // Render executes the named page template with the shared layout.
1840 func Render(w io.Writer, page string, data any) error {
1941 t, err := template.Must(
20 template.ParseFS(templateFS, "templates/layout.html"),
42 template.New("layout.html").Funcs(funcs).ParseFS(templateFS, "templates/layout.html"),
2143 ).ParseFS(templateFS, "templates/"+page)
2244 if err != nil {
2345 return err