on main help sign in
fix: detect stale server during development with BuildID
9e126c2Tai Groot
internal/backend/backend.go+1
@@ -174,6 +174,7 @@ func (b *Backend) VersionInfo() proto.VersionInfo {
174 return proto.VersionInfo{
174 return proto.VersionInfo{
175 Version: version.Version,
175 Version: version.Version,
176 Commit: version.Commit,
176 Commit: version.Commit,
177+ BuildID: version.BuildID,
177 GoVersion: runtime.Version(),
178 GoVersion: runtime.Version(),
178 Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
179 Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
179 }
180 }
internal/cmd/root.go+7−4
@@ -614,12 +614,15 @@ func restartIfStale(cmd *cobra.Command, hostURL *url.URL) (restarted bool, err e
614 if err != nil {
614 if err != nil {
615 return false, err
615 return false, err
616 }
616 }
617− if vi.Version == version.Version {
617+ if vi.Version == version.Version && vi.BuildID == version.BuildID {
618 return false, nil
618 return false, nil
619 }
619 }
620+ slog.Info(
620− slog.Info("Server version mismatch, restarting",
621+ "Server version mismatch, restarting",
621− "server", vi.Version,
622+ "server_version", vi.Version,
622− "client", version.Version,
623+ "client_version", version.Version,
624+ "server_build_id", vi.BuildID,
625+ "client_build_id", version.BuildID,
623 )
626 )
624 _ = c.ShutdownServer(cmd.Context())
627 _ = c.ShutdownServer(cmd.Context())
625 // Give the old process a moment to release the socket.
628 // Give the old process a moment to release the socket.
internal/proto/version.go+1
@@ -4,6 +4,7 @@ package proto
4type VersionInfo struct {
4type VersionInfo struct {
5 Version string `json:"version"`
5 Version string `json:"version"`
6 Commit string `json:"commit"`
6 Commit string `json:"commit"`
7+ BuildID string `json:"build_id"`
7 GoVersion string `json:"go_version"`
8 GoVersion string `json:"go_version"`
8 Platform string `json:"platform"`
9 Platform string `json:"platform"`
9}
10}
internal/version/version.go+34−6
@@ -1,12 +1,21 @@
1package version
1package version
2
2
3+import (
4+ "os"
3−import "runtime/debug"
5+ "runtime/debug"
6+ "strconv"
7+)
4
8
5// Build-time parameters set via -ldflags.
9// Build-time parameters set via -ldflags.
6
10
7var (
11var (
8 Version = "devel"
12 Version = "devel"
9 Commit = "unknown"
13 Commit = "unknown"
14+ // BuildID is a unique identifier for this build. For release builds it
15+ // equals Commit; for development builds (go run / go build without
16+ // ldflags) it is derived from the executable's modification time, which
17+ // changes on every recompilation.
18+ BuildID = ""
10)
19)
11
20
12// A user may install crush using `go install github.com/charmbracelet/crush@latest`.
21// A user may install crush using `go install github.com/charmbracelet/crush@latest`.
@@ -15,11 +24,30 @@ var (
15// is only set for `go install` and not for `go build`).
24// is only set for `go install` and not for `go build`).
16func init() {
25func init() {
17 info, ok := debug.ReadBuildInfo()
26 info, ok := debug.ReadBuildInfo()
18− if !ok {
27+ if ok {
28+ mainVersion := info.Main.Version
29+ if mainVersion != "" && mainVersion != "(devel)" {
30+ Version = mainVersion
31+ }
32+ }
33+
34+ // Derive BuildID when not set via ldflags.
35+ if BuildID == "" {
36+ BuildID = deriveBuildID()
37+ }
38+}
39+
40+// deriveBuildID uses the running executable's modification time as a unique
41+// build fingerprint. This changes on every recompilation (including `go run`),
42+// making it reliable for detecting stale servers during development.
43+func deriveBuildID() string {
44+ exe, err := os.Executable()
45+ if err != nil {
19− return
46+ return "unknown"
20 }
47 }
21− mainVersion := info.Main.Version
48+ fi, err := os.Stat(exe)
22− if mainVersion != "" && mainVersion != "(devel)" {
49+ if err != nil {
23− Version = mainVersion
50+ return "unknown"
24 }
51 }
52+ return strconv.FormatInt(fi.ModTime().UnixNano(), 36)
25}
53}