From a1f483ab99e718a9f5fd0decb89de4a1c388c334 Mon Sep 17 00:00:00 2001
From: WilliamK112 <164879897+WilliamK112@users.noreply.github.com>
Date: Sat, 15 Aug 2026 03:52:22 +0800
Subject: [PATCH 1/3] Show build information in web UI
Signed-off-by: WilliamK112 <164879897+WilliamK112@users.noreply.github.com>
---
cmd/proxy/main.go | 5 ++++-
internal/server/layout.go | 15 ++++++++++----
internal/server/server.go | 4 +++-
internal/server/server_test.go | 21 +++++++++++++++-----
internal/server/templates/layout/footer.html | 5 +++++
5 files changed, 39 insertions(+), 11 deletions(-)
diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go
index 15a71c0..02cb272 100644
--- a/cmd/proxy/main.go
+++ b/cmd/proxy/main.go
@@ -267,7 +267,10 @@ func runServe() {
logger := setupLogger(cfg.Log.Level, cfg.Log.Format)
// Create and start server
- srv, err := server.New(cfg, logger)
+ srv, err := server.New(cfg, logger, server.BuildInfo{
+ Version: Version,
+ Commit: Commit,
+ })
if err != nil {
logger.Error("failed to create server", "error", err)
os.Exit(1)
diff --git a/internal/server/layout.go b/internal/server/layout.go
index ef39858..d96f540 100644
--- a/internal/server/layout.go
+++ b/internal/server/layout.go
@@ -2,17 +2,24 @@ package server
import "net/http"
-// Layout carries per-request fields consumed by the shared base template
-// (canonical URL, og:url). It is embedded in every page data struct so that
-// templates can reference {{.UIBaseURL}} and {{.CanonicalPath}} alongside the
-// page's own fields.
+// BuildInfo identifies the running proxy binary.
+type BuildInfo struct {
+ Version string
+ Commit string
+}
+
+// Layout carries shared fields consumed by the base template. It is embedded
+// in every page data struct so templates can access canonical URL and build
+// information alongside the page's own fields.
type Layout struct {
+ BuildInfo
UIBaseURL string
CanonicalPath string
}
func (s *Server) layoutFor(r *http.Request) Layout {
return Layout{
+ BuildInfo: s.buildInfo,
UIBaseURL: s.cfg.UIBaseURL,
CanonicalPath: r.URL.Path,
}
diff --git a/internal/server/server.go b/internal/server/server.go
index e677bc9..e2966d6 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -91,6 +91,7 @@ type Server struct {
db *database.DB
storage storage.Storage
logger *slog.Logger
+ buildInfo BuildInfo
http *http.Server
templates *Templates
cancel context.CancelFunc
@@ -98,7 +99,7 @@ type Server struct {
}
// New creates a new Server with the given configuration.
-func New(cfg *config.Config, logger *slog.Logger) (*Server, error) {
+func New(cfg *config.Config, logger *slog.Logger, buildInfo BuildInfo) (*Server, error) {
// Initialize database
var db *database.DB
var err error
@@ -152,6 +153,7 @@ func New(cfg *config.Config, logger *slog.Logger) (*Server, error) {
db: db,
storage: store,
logger: logger,
+ buildInfo: buildInfo,
templates: &Templates{},
healthCache: hc,
}, nil
diff --git a/internal/server/server_test.go b/internal/server/server_test.go
index 98b58cc..3401654 100644
--- a/internal/server/server_test.go
+++ b/internal/server/server_test.go
@@ -100,10 +100,14 @@ func newTestServer(t *testing.T) *testServer {
// Create a minimal server struct for the handlers
s := &Server{
- cfg: cfg,
- db: db,
- storage: store,
- logger: logger,
+ cfg: cfg,
+ db: db,
+ storage: store,
+ logger: logger,
+ buildInfo: BuildInfo{
+ Version: "test-version",
+ Commit: "test-commit",
+ },
templates: &Templates{},
healthCache: hc,
}
@@ -313,6 +317,9 @@ func TestDashboard(t *testing.T) {
if !strings.Contains(body, "Cached Artifacts") {
t.Error("dashboard should contain stats")
}
+ if !strings.Contains(body, "proxy test-version (test-commit)") {
+ t.Error("dashboard footer should contain build information")
+ }
if !strings.Contains(body, "Popular Packages") {
t.Error("dashboard should contain popular packages section")
}
@@ -1327,10 +1334,14 @@ func TestNewServer_StorageConnectivityCheck(t *testing.T) {
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
- srv, err := New(cfg, logger)
+ buildInfo := BuildInfo{Version: "test-version", Commit: "test-commit"}
+ srv, err := New(cfg, logger, buildInfo)
if err != nil {
t.Fatalf("New() failed: %v", err)
}
+ if srv.buildInfo != buildInfo {
+ t.Errorf("build info = %#v, want %#v", srv.buildInfo, buildInfo)
+ }
// On Windows, OpenBucket normalises to file:///C:/path; on Unix the
// absolute path already starts with /, so file:// + /path == file:///path.
diff --git a/internal/server/templates/layout/footer.html b/internal/server/templates/layout/footer.html
index 5aa970d..d8b7acf 100644
--- a/internal/server/templates/layout/footer.html
+++ b/internal/server/templates/layout/footer.html
@@ -12,6 +12,11 @@
Aboutgithub.com/git-pkgs/proxy
+ {{if .Version}}
+
+ proxy {{.Version}}{{if .Commit}} ({{.Commit}}){{end}}
+
+ {{end}}
Resources
From a1842a6d7f8b3febe5a178ba96e9e9b041084131 Mon Sep 17 00:00:00 2001
From: WilliamK112 <164879897+WilliamK112@users.noreply.github.com>
Date: Thu, 20 Aug 2026 09:43:22 +0800
Subject: [PATCH 2/3] Fix footer build info shadowed by page Version fields
Shared footer templates were reading .Version and .Commit, which resolve
to package data on VersionShowData and BrowseSourceData. Point the footer
at Layout.BuildInfo and cover both pages so the proxy version stays visible.
Signed-off-by: WilliamK112 <164879897+WilliamK112@users.noreply.github.com>
Co-authored-by: Cursor
---
internal/server/browse_test.go | 4 ++
internal/server/server_test.go | 3 +
internal/server/templates/layout/footer.html | 4 +-
internal/server/templates_test.go | 58 ++++++++++++++++++++
4 files changed, 67 insertions(+), 2 deletions(-)
diff --git a/internal/server/browse_test.go b/internal/server/browse_test.go
index 3cc37c8..f4f2f9a 100644
--- a/internal/server/browse_test.go
+++ b/internal/server/browse_test.go
@@ -430,6 +430,10 @@ func TestHandleBrowseSourcePage(t *testing.T) {
}
}
+ if !strings.Contains(body, "proxy test-version (test-commit)") {
+ t.Error("browse source footer should contain proxy build information, not the package version")
+ }
+
// Check that the escapeHTML function is present for XSS protection
if !strings.Contains(body, "function escapeHTML(str)") {
t.Error("browse source page missing escapeHTML function for XSS protection")
diff --git a/internal/server/server_test.go b/internal/server/server_test.go
index 3401654..c995ea2 100644
--- a/internal/server/server_test.go
+++ b/internal/server/server_test.go
@@ -605,6 +605,9 @@ func TestVersionShowWithHitCount(t *testing.T) {
if !strings.Contains(body, "42 cache hits") {
t.Error("expected page to show hit count")
}
+ if !strings.Contains(body, "proxy test-version (test-commit)") {
+ t.Error("version show footer should contain proxy build information, not the package version")
+ }
}
func TestSearchWithNullValues(t *testing.T) {
diff --git a/internal/server/templates/layout/footer.html b/internal/server/templates/layout/footer.html
index d8b7acf..33daddf 100644
--- a/internal/server/templates/layout/footer.html
+++ b/internal/server/templates/layout/footer.html
@@ -12,9 +12,9 @@ Aboutgithub.com/git-pkgs/proxy
- {{if .Version}}
+ {{if .BuildInfo.Version}}
- proxy {{.Version}}{{if .Commit}} ({{.Commit}}){{end}}
+ proxy {{.BuildInfo.Version}}{{if .BuildInfo.Commit}} ({{.BuildInfo.Commit}}){{end}}
{{end}}
diff --git a/internal/server/templates_test.go b/internal/server/templates_test.go
index e9a4967..158278b 100644
--- a/internal/server/templates_test.go
+++ b/internal/server/templates_test.go
@@ -186,6 +186,64 @@ func TestRenderEmitsCanonicalAndOG(t *testing.T) {
}
}
+func TestFooterUsesBuildInfoWhenPageDefinesVersion(t *testing.T) {
+ templates := &Templates{}
+ buildInfo := BuildInfo{Version: "proxy-build-1.2.3", Commit: "abc123def"}
+ wantFooter := "proxy proxy-build-1.2.3 (abc123def)"
+
+ tests := []struct {
+ name string
+ page string
+ data any
+ shadow string
+ }{
+ {
+ name: "version show page",
+ page: "version_show",
+ data: VersionShowData{
+ Layout: Layout{BuildInfo: buildInfo},
+ Package: &database.Package{
+ PURL: "pkg:npm/lodash",
+ Ecosystem: "npm",
+ Name: "lodash",
+ },
+ Version: &database.Version{
+ PURL: "pkg:npm/lodash@9.9.9",
+ PackagePURL: "pkg:npm/lodash",
+ },
+ },
+ shadow: "9.9.9",
+ },
+ {
+ name: "browse source page",
+ page: "browse_source",
+ data: BrowseSourceData{
+ Layout: Layout{BuildInfo: buildInfo},
+ Ecosystem: "npm",
+ PackageName: "lodash",
+ Version: "9.9.9",
+ },
+ shadow: "9.9.9",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ w := httptest.NewRecorder()
+ if err := templates.Render(w, tt.page, tt.data); err != nil {
+ t.Fatalf("Render(%q) failed: %v", tt.page, err)
+ }
+ body := w.Body.String()
+ if !strings.Contains(body, wantFooter) {
+ t.Errorf("footer missing build info %q", wantFooter)
+ }
+ if strings.Contains(body, "proxy "+tt.shadow) {
+ t.Errorf("footer used page Version %q instead of BuildInfo", tt.shadow)
+ }
+ })
+ }
+}
+
func TestRenderOmitsCanonicalWhenUIBaseURLUnset(t *testing.T) {
templates := &Templates{}
From 02e8c3c0fe2878a376b5f56cd424cd19cb7c0ed0 Mon Sep 17 00:00:00 2001
From: WilliamK112 <164879897+WilliamK112@users.noreply.github.com>
Date: Thu, 20 Aug 2026 15:53:09 +0800
Subject: [PATCH 3/3] Fix Layout build info field promotion
---
internal/server/layout.go | 2 +-
internal/server/server_test.go | 13 +++++--------
2 files changed, 6 insertions(+), 9 deletions(-)
diff --git a/internal/server/layout.go b/internal/server/layout.go
index d96f540..2da9469 100644
--- a/internal/server/layout.go
+++ b/internal/server/layout.go
@@ -12,7 +12,7 @@ type BuildInfo struct {
// in every page data struct so templates can access canonical URL and build
// information alongside the page's own fields.
type Layout struct {
- BuildInfo
+ BuildInfo BuildInfo
UIBaseURL string
CanonicalPath string
}
diff --git a/internal/server/server_test.go b/internal/server/server_test.go
index fe0b3e5..03b253f 100644
--- a/internal/server/server_test.go
+++ b/internal/server/server_test.go
@@ -100,14 +100,11 @@ func newTestServer(t *testing.T) *testServer {
// Create a minimal server struct for the handlers
s := &Server{
- cfg: cfg,
- db: db,
- storage: store,
- logger: logger,
- buildInfo: BuildInfo{
- Version: "test-version",
- Commit: "test-commit",
- },
+ cfg: cfg,
+ db: db,
+ storage: store,
+ logger: logger,
+ buildInfo: BuildInfo{Version: "test-version", Commit: "test-commit"},
templates: &Templates{},
healthCache: hc,
}