From 76851f081689e2528f457000a5091d186d6871f5 Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Mon, 9 Mar 2026 09:43:13 +1100 Subject: [PATCH] show version in UI --- Dockerfile | 11 ++++++-- cmd/ingestd/buildinfo.go | 59 ++++++++++++++++++++++++++++++++++++++++ cmd/ingestd/web.go | 9 +++++- cmd/ingestd/web/app.js | 34 +++++++++++++++++++++-- 4 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 cmd/ingestd/buildinfo.go diff --git a/Dockerfile b/Dockerfile index 63b8827..ad5ff1e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,9 +12,16 @@ RUN go mod download # Copy the rest of the source COPY . . +ARG BUILD_TIME="" +ARG GIT_COMMIT="unknown" +ARG VERSION="dev" + # Build a static-ish binary (alpine musl) -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ - go build -trimpath -ldflags="-s -w" -o /out/ingestd ./cmd/ingestd +RUN BUILD_TIME="${BUILD_TIME:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}" && \ + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -trimpath \ + -ldflags="-s -w -X main.buildTime=${BUILD_TIME} -X main.gitCommit=${GIT_COMMIT} -X main.version=${VERSION}" \ + -o /out/ingestd ./cmd/ingestd # ---- runtime stage ---- FROM alpine:3.22 diff --git a/cmd/ingestd/buildinfo.go b/cmd/ingestd/buildinfo.go new file mode 100644 index 0000000..99eae08 --- /dev/null +++ b/cmd/ingestd/buildinfo.go @@ -0,0 +1,59 @@ +package main + +import ( + "runtime/debug" + "strings" +) + +var ( + version = "dev" + gitCommit = "unknown" + buildTime = "unknown" +) + +type binaryBuildInfo struct { + Version string `json:"version"` + GitCommit string `json:"git_commit"` + BuildTime string `json:"build_time"` +} + +func currentBuildInfo() binaryBuildInfo { + out := binaryBuildInfo{ + Version: version, + GitCommit: gitCommit, + BuildTime: buildTime, + } + + if bi, ok := debug.ReadBuildInfo(); ok { + if out.Version == "" || out.Version == "dev" { + if bi.Main.Version != "" && bi.Main.Version != "(devel)" { + out.Version = bi.Main.Version + } + } + for _, s := range bi.Settings { + if s.Key == "vcs.revision" && (out.GitCommit == "" || out.GitCommit == "unknown") { + if len(s.Value) > 12 { + out.GitCommit = s.Value[:12] + } else { + out.GitCommit = s.Value + } + } + if s.Key == "vcs.time" && (out.BuildTime == "" || out.BuildTime == "unknown") { + out.BuildTime = s.Value + } + } + } + + out.Version = normalizeBuildField(out.Version, "dev") + out.GitCommit = normalizeBuildField(out.GitCommit, "unknown") + out.BuildTime = normalizeBuildField(out.BuildTime, "unknown") + return out +} + +func normalizeBuildField(v string, fallback string) string { + v = strings.TrimSpace(v) + if v == "" { + return fallback + } + return v +} diff --git a/cmd/ingestd/web.go b/cmd/ingestd/web.go index 7aa2292..f54841f 100644 --- a/cmd/ingestd/web.go +++ b/cmd/ingestd/web.go @@ -21,12 +21,14 @@ type webServer struct { db *db.DB site providers.Site model string + build binaryBuildInfo } type dashboardResponse struct { GeneratedAt time.Time `json:"generated_at"` Site string `json:"site"` Model string `json:"model"` + Build binaryBuildInfo `json:"build"` RangeStart time.Time `json:"range_start"` RangeEnd time.Time `json:"range_end"` Observations []db.ObservationPoint `json:"observations"` @@ -46,6 +48,7 @@ func runWebServer(ctx context.Context, d *db.DB, site providers.Site, model, add db: d, site: site, model: model, + build: currentBuildInfo(), } mux := http.NewServeMux() @@ -53,7 +56,10 @@ func runWebServer(ctx context.Context, d *db.DB, site providers.Site, model, add mux.HandleFunc("/api/dashboard", ws.handleDashboard) mux.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"ok":true}`)) + _ = json.NewEncoder(w).Encode(map[string]any{ + "ok": true, + "build": ws.build, + }) }) mux.HandleFunc("/chart", func(w http.ResponseWriter, r *http.Request) { serveIndex(w, sub) @@ -193,6 +199,7 @@ func (s *webServer) handleDashboard(w http.ResponseWriter, r *http.Request) { GeneratedAt: time.Now().UTC(), Site: s.site.Name, Model: s.model, + Build: s.build, RangeStart: start, RangeEnd: end, Observations: observations, diff --git a/cmd/ingestd/web/app.js b/cmd/ingestd/web/app.js index 3e509b1..9988a73 100644 --- a/cmd/ingestd/web/app.js +++ b/cmd/ingestd/web/app.js @@ -615,7 +615,35 @@ function extendForecastTo(points, endTime) { return [...points, { ...last, ts: new Date(endTs).toISOString() }]; } -function updateSiteMeta(site, model, tzLabel) { +function formatBuildStamp(build) { + if (!build) { + return "build --"; + } + + const parts = []; + const rawBuildTime = build.build_time; + if (rawBuildTime && rawBuildTime !== "unknown") { + const dt = new Date(rawBuildTime); + if (Number.isNaN(dt.getTime())) { + parts.push(rawBuildTime); + } else { + parts.push(formatDateTime(dt)); + } + } else { + parts.push("--"); + } + + if (build.git_commit && build.git_commit !== "unknown") { + parts.push(`commit ${build.git_commit}`); + } + if (build.version && build.version !== "dev") { + parts.push(`version ${build.version}`); + } + + return `build ${parts.join(", ")}`; +} + +function updateSiteMeta(site, model, tzLabel, build) { const home = document.getElementById("site-home"); const suffix = document.getElementById("site-meta-suffix"); if (home) { @@ -623,7 +651,7 @@ function updateSiteMeta(site, model, tzLabel) { home.setAttribute("href", "/"); } if (suffix) { - suffix.textContent = ` | model ${model || "--"} | ${tzLabel}`; + suffix.textContent = ` | model ${model || "--"} | ${tzLabel} | ${formatBuildStamp(build)}`; } } @@ -686,7 +714,7 @@ function baseOptions(range) { function renderDashboard(data) { const latest = data.latest; const tzLabel = state.tz === "utc" ? "UTC" : "Local"; - updateSiteMeta(data.site, data.model, tzLabel); + updateSiteMeta(data.site, data.model, tzLabel, data.build); updateText("last-updated", `updated ${formatDateTime(data.generated_at)}`); const forecastMeta = data.forecast && data.forecast.points && data.forecast.points.length ? `forecast retrieved ${formatDateTime(data.forecast.retrieved_at)}`