Stamp shared build metadata into banger binaries

Treat `banger`, `bangerd`, and `banger-vsock-agent` as one release by
stamping the same version, commit SHA, and build timestamp into every
binary through a shared ldflag-backed `internal/buildinfo` package.

Add `banger version`, extend daemon ping/status to report the running
daemon's build tuple, and keep the guest helper linked to the same build
metadata without adding a new public version surface for it.

Validate with `GOCACHE=/tmp/banger-gocache go test ./...`, `make build`,
`./build/bin/banger version`, and `./build/bin/banger daemon status`
after the daemon restarts onto the new binary.
This commit is contained in:
Thales Maciel 2026-03-22 17:14:06 -03:00
parent ea2db1e868
commit f798e1db33
No known key found for this signature in database
GPG key ID: 33112E6833C34679
9 changed files with 219 additions and 13 deletions

View file

@ -19,6 +19,7 @@ import (
"time"
"banger/internal/api"
"banger/internal/buildinfo"
"banger/internal/config"
"banger/internal/daemon"
"banger/internal/guest"
@ -70,6 +71,9 @@ var (
vmHealthFunc = func(ctx context.Context, socketPath, idOrName string) (api.VMHealthResult, error) {
return rpc.Call[api.VMHealthResult](ctx, socketPath, "vm.health", api.VMRefParams{IDOrName: idOrName})
}
daemonPingFunc = func(ctx context.Context, socketPath string) (api.PingResult, error) {
return rpc.Call[api.PingResult](ctx, socketPath, "ping", api.Empty{})
}
vmCreateBeginFunc = func(ctx context.Context, socketPath string, params api.VMCreateParams) (api.VMCreateBeginResult, error) {
return rpc.Call[api.VMCreateBeginResult](ctx, socketPath, "vm.create.begin", params)
}
@ -121,7 +125,7 @@ func NewBangerCommand() *cobra.Command {
RunE: helpNoArgs,
}
root.CompletionOptions.DisableDefaultCmd = true
root.AddCommand(newDaemonCommand(), newDoctorCommand(), newVMCommand(), newImageCommand(), newInternalCommand())
root.AddCommand(newDaemonCommand(), newDoctorCommand(), newImageCommand(), newInternalCommand(), newVersionCommand(), newVMCommand())
return root
}
@ -146,6 +150,18 @@ func newDoctorCommand() *cobra.Command {
}
}
func newVersionCommand() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "Show banger build information",
Args: noArgsUsage("usage: banger version"),
RunE: func(cmd *cobra.Command, args []string) error {
_, err := fmt.Fprint(cmd.OutOrStdout(), formatBuildInfoBlock(buildinfo.Current()))
return err
},
}
}
func newInternalCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "internal",
@ -342,7 +358,7 @@ func newDaemonCommand() *cobra.Command {
if err != nil {
return err
}
ping, pingErr := rpc.Call[api.PingResult](cmd.Context(), layout.SocketPath, "ping", api.Empty{})
ping, pingErr := daemonPingFunc(cmd.Context(), layout.SocketPath)
if pingErr != nil {
if strings.TrimSpace(cfg.WebListenAddr) != "" {
_, err = fmt.Fprintf(cmd.OutOrStdout(), "stopped\nsocket: %s\nlog: %s\ndns: %s\nweb: http://%s\n", layout.SocketPath, layout.DaemonLog, vmdns.DefaultListenAddr, cfg.WebListenAddr)
@ -351,11 +367,12 @@ func newDaemonCommand() *cobra.Command {
_, err = fmt.Fprintf(cmd.OutOrStdout(), "stopped\nsocket: %s\nlog: %s\ndns: %s\n", layout.SocketPath, layout.DaemonLog, vmdns.DefaultListenAddr)
return err
}
info := buildinfo.Normalize(ping.Version, ping.Commit, ping.BuiltAt)
if strings.TrimSpace(ping.WebURL) != "" {
_, err = fmt.Fprintf(cmd.OutOrStdout(), "running\npid: %d\nsocket: %s\nlog: %s\ndns: %s\nweb: %s\n", ping.PID, layout.SocketPath, layout.DaemonLog, vmdns.DefaultListenAddr, ping.WebURL)
_, err = fmt.Fprintf(cmd.OutOrStdout(), "running\npid: %d\n%ssocket: %s\nlog: %s\ndns: %s\nweb: %s\n", ping.PID, formatBuildInfoBlock(info), layout.SocketPath, layout.DaemonLog, vmdns.DefaultListenAddr, ping.WebURL)
return err
}
_, err = fmt.Fprintf(cmd.OutOrStdout(), "running\npid: %d\nsocket: %s\nlog: %s\ndns: %s\n", ping.PID, layout.SocketPath, layout.DaemonLog, vmdns.DefaultListenAddr)
_, err = fmt.Fprintf(cmd.OutOrStdout(), "running\npid: %d\n%ssocket: %s\nlog: %s\ndns: %s\n", ping.PID, formatBuildInfoBlock(info), layout.SocketPath, layout.DaemonLog, vmdns.DefaultListenAddr)
return err
},
},
@ -1141,7 +1158,7 @@ func ensureDaemon(ctx context.Context) (paths.Layout, model.DaemonConfig, error)
if err != nil {
return paths.Layout{}, model.DaemonConfig{}, err
}
if ping, err := rpc.Call[api.PingResult](ctx, layout.SocketPath, "ping", api.Empty{}); err == nil {
if ping, err := daemonPingFunc(ctx, layout.SocketPath); err == nil {
if daemonOutdated(ping.PID) {
if err := restartDaemon(ctx, layout, ping.PID); err != nil {
return paths.Layout{}, model.DaemonConfig{}, err
@ -2067,3 +2084,7 @@ func relativeTime(t time.Time) string {
return fmt.Sprintf("%d weeks ago", int(delta.Hours()/(24*7)))
}
}
func formatBuildInfoBlock(info buildinfo.Info) string {
return fmt.Sprintf("version: %s\ncommit: %s\nbuilt_at: %s\n", info.Version, info.Commit, info.BuiltAt)
}