@@ -174,6 +174,7 @@ func (b *Backend) VersionInfo() proto.VersionInfo {
return proto.VersionInfo{
Version: version.Version,
Commit: version.Commit,
+ BuildID: version.BuildID,
GoVersion: runtime.Version(),
Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
}
@@ -614,12 +614,15 @@ func restartIfStale(cmd *cobra.Command, hostURL *url.URL) (restarted bool, err e
if err != nil {
return false, err
}
- if vi.Version == version.Version {
+ if vi.Version == version.Version && vi.BuildID == version.BuildID {
return false, nil
}
- slog.Info("Server version mismatch, restarting",
- "server", vi.Version,
- "client", version.Version,
+ slog.Info(
+ "Server version mismatch, restarting",
+ "server_version", vi.Version,
+ "client_version", version.Version,
+ "server_build_id", vi.BuildID,
+ "client_build_id", version.BuildID,
)
_ = c.ShutdownServer(cmd.Context())
// Give the old process a moment to release the socket.
@@ -4,6 +4,7 @@ package proto
type VersionInfo struct {
Version string `json:"version"`
Commit string `json:"commit"`
+ BuildID string `json:"build_id"`
GoVersion string `json:"go_version"`
Platform string `json:"platform"`
}
@@ -1,12 +1,21 @@
package version
-import "runtime/debug"
+import (
+ "os"
+ "runtime/debug"
+ "strconv"
+)
// Build-time parameters set via -ldflags.
var (
Version = "devel"
Commit = "unknown"
+ // BuildID is a unique identifier for this build. For release builds it
+ // equals Commit; for development builds (go run / go build without
+ // ldflags) it is derived from the executable's modification time, which
+ // changes on every recompilation.
+ BuildID = ""
)
// A user may install crush using `go install github.com/charmbracelet/crush@latest`.
@@ -15,11 +24,30 @@ var (
// is only set for `go install` and not for `go build`).
func init() {
info, ok := debug.ReadBuildInfo()
- if !ok {
- return
+ if ok {
+ mainVersion := info.Main.Version
+ if mainVersion != "" && mainVersion != "(devel)" {
+ Version = mainVersion
+ }
+ }
+
+ // Derive BuildID when not set via ldflags.
+ if BuildID == "" {
+ BuildID = deriveBuildID()
+ }
+}
+
+// deriveBuildID uses the running executable's modification time as a unique
+// build fingerprint. This changes on every recompilation (including `go run`),
+// making it reliable for detecting stale servers during development.
+func deriveBuildID() string {
+ exe, err := os.Executable()
+ if err != nil {
+ return "unknown"
}
- mainVersion := info.Main.Version
- if mainVersion != "" && mainVersion != "(devel)" {
- Version = mainVersion
+ fi, err := os.Stat(exe)
+ if err != nil {
+ return "unknown"
}
+ return strconv.FormatInt(fi.ModTime().UnixNano(), 36)
}