Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/docker-riverproui.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ jobs:
with:
ref: ${{ inputs.ref || github.ref }}

- name: Build metadata
id: build_metadata
run: |
version=$(git describe --tags --always --match 'riverproui/v*')
echo "version=${version#riverproui/}" >> "$GITHUB_OUTPUT"
echo "revision=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
echo "commit_time=$(git show -s --format=%cI HEAD)" >> "$GITHUB_OUTPUT"

- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v5
with:
Expand Down Expand Up @@ -92,6 +100,10 @@ jobs:
with:
context: .
file: Dockerfile.pro
build-args: |
BUILD_VERSION=${{ steps.build_metadata.outputs.version }}
BUILD_REVISION=${{ steps.build_metadata.outputs.revision }}
BUILD_COMMIT_TIME=${{ steps.build_metadata.outputs.commit_time }}
pull: true
platforms: ${{ matrix.docker_platform }}
provenance: true
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/docker-riverui.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ jobs:
with:
ref: ${{ inputs.ref || github.ref }}

- name: Build metadata
id: build_metadata
run: |
echo "version=$(git describe --tags --always --match 'v*')" >> "$GITHUB_OUTPUT"
echo "revision=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
echo "commit_time=$(git show -s --format=%cI HEAD)" >> "$GITHUB_OUTPUT"

- name: Prepare
run: |
platform=${{ matrix.docker_platform }}
Expand Down Expand Up @@ -80,6 +87,10 @@ jobs:
uses: docker/build-push-action@v7
with:
context: .
build-args: |
BUILD_VERSION=${{ steps.build_metadata.outputs.version }}
BUILD_REVISION=${{ steps.build_metadata.outputs.revision }}
BUILD_COMMIT_TIME=${{ steps.build_metadata.outputs.commit_time }}
pull: true
platforms: ${{ matrix.docker_platform }}
labels: ${{ steps.meta.outputs.labels }}
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Show the running server's version, commit, commit date, and Go version on the settings page. [PR #685](https://github.com/riverqueue/riverui/pull/685).

## [v0.19.0] - 2026-08-25

### Changed
Expand Down
7 changes: 6 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ COPY uiendpoints/ uiendpoints/

COPY --from=build-ui /app/dist ./dist

RUN go build -trimpath -ldflags="-w -s -buildid=" -o /bin/riverui ./cmd/riverui
# Pass metadata from the original checkout; this partial copy is not a Git checkout.
ARG BUILD_VERSION
ARG BUILD_REVISION
ARG BUILD_COMMIT_TIME

RUN go build -trimpath -ldflags="-w -s -buildid= -X riverqueue.com/riverui/internal/buildinfo.version=${BUILD_VERSION} -X riverqueue.com/riverui/internal/buildinfo.revision=${BUILD_REVISION} -X riverqueue.com/riverui/internal/buildinfo.commitTime=${BUILD_COMMIT_TIME}" -o /bin/riverui ./cmd/riverui

FROM alpine:3.24.1@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b
ENV PATH_PREFIX="/"
Expand Down
7 changes: 6 additions & 1 deletion Dockerfile.pro
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ COPY --from=build-ui /app/dist ./dist

# Build the riverproui binary
WORKDIR /go/src/riverui/riverproui
RUN go build -trimpath -ldflags="-w -s -buildid=" -o /bin/riverproui ./cmd/riverproui
# Pass metadata from the original checkout; this partial copy is not a Git checkout.
ARG BUILD_VERSION
ARG BUILD_REVISION
ARG BUILD_COMMIT_TIME

RUN go build -trimpath -ldflags="-w -s -buildid= -X riverqueue.com/riverui/internal/buildinfo.version=${BUILD_VERSION} -X riverqueue.com/riverui/internal/buildinfo.revision=${BUILD_REVISION} -X riverqueue.com/riverui/internal/buildinfo.commitTime=${BUILD_COMMIT_TIME}" -o /bin/riverproui ./cmd/riverproui

FROM alpine:3.24.1@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b
ENV PATH_PREFIX="/"
Expand Down
1 change: 1 addition & 0 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ func (e *endpoints[TTx]) MountEndpoints(archetype *baseservice.Archetype, logger
apiendpoint.Mount(mux, newQueueResumeEndpoint(bundle), mountOpts),
apiendpoint.Mount(mux, newQueueUpdateEndpoint(bundle), mountOpts),
apiendpoint.Mount(mux, newStateAndCountGetEndpoint(bundle), mountOpts),
apiendpoint.Mount(mux, newVersionGetEndpoint(bundle), mountOpts),
}
}

Expand Down
42 changes: 42 additions & 0 deletions handler_api_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/riverqueue/river/rivertype"

"riverqueue.com/riverui/internal/apibundle"
"riverqueue.com/riverui/internal/buildinfo"
"riverqueue.com/riverui/internal/querycacher"
)

Expand Down Expand Up @@ -1091,3 +1092,44 @@ func riverQueuesToSerializableQueues(internal []*rivertype.Queue, counts []*rive
}
return listResponseFrom(queues)
}

//
// versionGetEndpoint
//

type versionGetEndpoint[TTx any] struct {
apibundle.APIBundle[TTx]
apiendpoint.Endpoint[versionGetRequest, versionGetResponse]
}

func newVersionGetEndpoint[TTx any](bundle apibundle.APIBundle[TTx]) *versionGetEndpoint[TTx] {
return &versionGetEndpoint[TTx]{APIBundle: bundle}
}

func (*versionGetEndpoint[TTx]) Meta() *apiendpoint.EndpointMeta {
return &apiendpoint.EndpointMeta{
Pattern: "GET /api/version",
StatusCode: http.StatusOK,
}
}

type versionGetRequest struct{}

type versionGetResponse struct {
Version string `json:"version"`
Revision string `json:"revision"`
Time string `json:"time"`
Modified bool `json:"modified"`
GoVersion string `json:"go_version"`
}

func (*versionGetEndpoint[TTx]) Execute(_ context.Context, _ *versionGetRequest) (*versionGetResponse, error) {
info := buildinfo.Get()
return &versionGetResponse{
Version: info.Version,
Revision: info.Revision,
Time: info.Time,
Modified: info.Modified,
GoVersion: info.GoVersion,
}, nil
}
25 changes: 25 additions & 0 deletions handler_api_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package riverui
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"net/http/httptest"
Expand All @@ -26,6 +27,7 @@ import (
"github.com/riverqueue/river/rivertype"

"riverqueue.com/riverui/internal/apibundle"
"riverqueue.com/riverui/internal/buildinfo"
"riverqueue.com/riverui/internal/riverinternaltest/testfactory"
"riverqueue.com/riverui/internal/uicommontest"
)
Expand Down Expand Up @@ -385,6 +387,29 @@ func TestAPIHandlerFeaturesGet(t *testing.T) {
})
}

func TestAPIHandlerVersionGet(t *testing.T) {
t.Parallel()

// Build metadata does not require a database connection.
endpoint := newVersionGetEndpoint(apibundle.APIBundle[pgx.Tx]{})
mux := http.NewServeMux()
apiendpoint.Mount(mux, endpoint, testMountOpts(t))

response := httptest.NewRecorder()
mux.ServeHTTP(response, httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/api/version", nil))

require.Equal(t, http.StatusOK, response.Code)
require.Contains(t, response.Header().Get("Content-Type"), "application/json")
info := buildinfo.Get()
require.JSONEq(t, fmt.Sprintf(`{
"version": %q,
"revision": %q,
"time": %q,
"modified": %t,
"go_version": %q
}`, info.Version, info.Revision, info.Time, info.Modified, info.GoVersion), response.Body.String())
}

func TestAPIHandlerHealthCheckGet(t *testing.T) {
t.Parallel()

Expand Down
1 change: 1 addition & 0 deletions handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ func TestNewHandlerIntegration(t *testing.T) {
},
}))
makeAPICall(t, "StateAndCountGet", http.MethodGet, makeURL("/api/states"), nil)
makeAPICall(t, "VersionGet", http.MethodGet, makeURL("/api/version"), nil)

//
// Static files
Expand Down
71 changes: 71 additions & 0 deletions internal/buildinfo/buildinfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Package buildinfo reports build and version information for the running
// binary, sourced from Go's embedded build metadata with optional overrides
// injected at link time:
//
// go build -ldflags "-X riverqueue.com/riverui/internal/buildinfo.version=v1.2.3 ..."
package buildinfo

import (
"runtime"
"runtime/debug"
)

// The variables below may be set at link time with -ldflags -X to override
// the values Go embeds automatically. They take precedence over the
// corresponding build settings when non-empty.
var (
version string
revision string //nolint:gochecknoglobals // Set by the Go linker with -X.
commitTime string //nolint:gochecknoglobals // Set by the Go linker with -X.
)

// Info describes the running binary's build.
type Info struct {
Version string
Revision string
Time string
Modified bool
GoVersion string
}

// Get returns build information for the running binary, preferring link-time
// overrides and falling back to the metadata Go embeds via
// runtime/debug.ReadBuildInfo (module version plus vcs.revision, vcs.time,
// and vcs.modified when built with VCS stamping). Time is the commit time,
// not the time the binary was built. When River UI is embedded, this describes
// the host executable.
func Get() *Info {
info := &Info{GoVersion: runtime.Version()}

if version != "" {
info.Version = version
}
if revision != "" {
info.Revision = revision
}
if commitTime != "" {
info.Time = commitTime
}

if buildInfo, ok := debug.ReadBuildInfo(); ok {
if info.Version == "" {
info.Version = buildInfo.Main.Version
}
for _, setting := range buildInfo.Settings {
switch setting.Key {
case "vcs.revision":
if info.Revision == "" {
info.Revision = setting.Value
}
case "vcs.time":
if info.Time == "" {
info.Time = setting.Value
}
case "vcs.modified":
info.Modified = setting.Value == "true"
}
}
}

return info
}
20 changes: 20 additions & 0 deletions internal/buildinfo/buildinfo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package buildinfo

import (
"runtime"
"testing"

"github.com/stretchr/testify/require"
)

func TestGet(t *testing.T) {
t.Parallel()

info := Get()

require.NotNil(t, info)
require.Equal(t, runtime.Version(), info.GoVersion)
// `go test` builds the test binary from the module, so the module path
// is always embedded; version may be "(devel)" when unbuilt from a tag.
require.NotEmpty(t, info.Version)
}
2 changes: 1 addition & 1 deletion riverproui/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,4 @@ retract (

// replace riverqueue.com/riverpro/driver/riverpropgxv5 => ../../riverpro/driver/riverpropgxv5

// replace riverqueue.com/riverui => ../
replace riverqueue.com/riverui => ../
Loading
Loading