From d4cbfb960e7753ee492d8f16781a1a001e4ae097 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 19 May 2026 19:45:04 +0000 Subject: [PATCH] Move to setting version info explicitly in linker flags We don't have go built-in VCS information in a nix build because the git repository isn't present. After struggling to build an overlay that would provide it, I decided this path is easier of just injecting in the data that we need. Issue: #5 --- api/api.go | 2 +- api/event.go | 5 ++-- api/version.go | 16 +++++++++++ default.nix | 17 ++--------- flake.lock | 6 ++-- flake.nix | 28 +++++++++++++++++-- main.go | 70 ++++++++++++++++++++++++++++++++++++++++++++-- version/version.go | 54 ----------------------------------- 8 files changed, 119 insertions(+), 79 deletions(-) create mode 100644 api/version.go diff --git a/api/api.go b/api/api.go index fe056d2d..55301b05 100644 --- a/api/api.go +++ b/api/api.go @@ -322,7 +322,7 @@ type tegolaURLs struct { } func getRoot(ctx context.Context, r *http.Request, q resource.QueryParams) (*about, *nhttp.ErrorWithStatus) { - v := version.Get() + v := GetVersionInfo() return &about{ Environment: config.Environment, SentryDSN: config.SentryDSNFrontend, diff --git a/api/event.go b/api/event.go index 2d026c88..e94084b1 100644 --- a/api/event.go +++ b/api/event.go @@ -11,7 +11,6 @@ import ( "source.gleipnir.technology/Gleipnir/nidus-sync/lint" "source.gleipnir.technology/Gleipnir/nidus-sync/platform" "source.gleipnir.technology/Gleipnir/nidus-sync/platform/event" - "source.gleipnir.technology/Gleipnir/nidus-sync/version" ) var connectionsSSE map[*ConnectionSSE]bool = make(map[*ConnectionSSE]bool, 0) @@ -41,7 +40,7 @@ type Status struct { func (c *ConnectionSSE) SendEvent(w http.ResponseWriter, m platform.Event) error { if m.Type == event.EventTypeShutdown { - v := version.Get() + v := GetVersionInfo() return send(w, Status{ BuildTime: v.BuildTime, IsModified: v.IsModified, @@ -121,7 +120,7 @@ func streamEvents(w http.ResponseWriter, r *http.Request, u platform.User) { log.Debug().Int32("org", u.Organization.ID).Int("user", u.ID).Str("id", uid.String()).Msg("connected SSE client") // Send an initial connected event - v := version.Get() + v := GetVersionInfo() status := Status{ BuildTime: v.BuildTime, IsModified: v.IsModified, diff --git a/api/version.go b/api/version.go new file mode 100644 index 00000000..bbe64cba --- /dev/null +++ b/api/version.go @@ -0,0 +1,16 @@ +package api + +import ( + "source.gleipnir.technology/Gleipnir/nidus-sync/version" +) + +var ( + versionInfo version.VersionInfo +) + +func GetVersionInfo() version.VersionInfo { + return versionInfo +} +func SetVersionInfo(v version.VersionInfo) { + versionInfo = v +} diff --git a/default.nix b/default.nix index cb5d7e93..24a83339 100644 --- a/default.nix +++ b/default.nix @@ -1,4 +1,4 @@ -{ pkgs ? import { }, proj ? pkgs.proj }: +{ ldflags, version, pkgs ? import { }, proj ? pkgs.proj }: let # Get commit timestamp at eval time (pure) @@ -12,24 +12,12 @@ let else "0"; in pkgs.buildGoModule rec { + inherit ldflags version; pname = "nidus-sync"; - version = "0.0.12"; src = ./.; - gitRevision = - if builtins.pathExists ./.git - then pkgs.lib.commitIdFromGitRepo ./.git - else "unknown"; - buildTime = getCommitTime; - ldflags = [ - "-s" - "-w" - "-X main.Version=${version}" - "-X main.Commit=${gitRevision}" - "-X main.BuildTime=${buildTime}" - ]; meta = { description = "Nidus Sync"; homepage = "https://github.com/Gleipnir-Technology/nidus-sync"; @@ -74,4 +62,5 @@ pkgs.buildGoModule rec { cp -r vite/rmo/static/gen/rmo $out/share/frontend/ cp -r vite/rmo/static/gen/sync $out/share/frontend/ ''; + } diff --git a/flake.lock b/flake.lock index efbd7923..5fe5f258 100644 --- a/flake.lock +++ b/flake.lock @@ -75,11 +75,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1772542754, - "narHash": "sha256-WGV2hy+VIeQsYXpsLjdr4GvHv5eECMISX1zKLTedhdg=", + "lastModified": 1778869304, + "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8c809a146a140c5c8806f13399592dbcb1bb5dc4", + "rev": "d233902339c02a9c334e7e593de68855ad26c4cb", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index a7f0fa84..5b19d009 100644 --- a/flake.nix +++ b/flake.nix @@ -21,8 +21,32 @@ projPkg = proj.packages.${system}.default; package = pkgs.callPackage ./default.nix { - proj = projPkg; - }; + ldflags = ldflags; + proj = projPkg; + version = packageVersion; + }; + # Pull VCS metadata from the flake's own git source. + # These fields are available when the flake lives inside a git checkout. + gitRevision = self.sourceInfo.rev or "dirty"; + gitShortRev = self.sourceInfo.shortRev or "dirty"; + gitDateTime = self.sourceInfo.lastModifiedDate or "unknown"; + + # Derive a human-readable version from the git date + short rev. + # lastModifiedDate is YYYYMMDDHHMMSS; pull out just YYYY-MM-DD. + packageVersion = + let + y = builtins.substring 0 4 gitDateTime; + m = builtins.substring 4 2 gitDateTime; + d = builtins.substring 6 2 gitDateTime; + in "${y}-${m}-${d}.${gitShortRev}"; + + ldflags = [ + "-s" "-w" + "-X main.BuildTime=${gitDateTime}" + "-X main.Revision=${gitRevision}" + "-X main.Version=${packageVersion}" + ]; + in { packages.default = package; diff --git a/main.go b/main.go index bf8c8971..eb5255df 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "os" "os/signal" "runtime/debug" + //"strconv" "syscall" "time" @@ -33,6 +34,13 @@ import ( "source.gleipnir.technology/Gleipnir/nidus-sync/version" ) +// These will be set by ldflags at build time by Nix +var ( + BuildTime = "unknown" + Revision = "unknown" + Version = "dev" +) + func main() { err := config.Parse() if err != nil { @@ -46,8 +54,19 @@ func main() { log.Warn().Msg("Forcing production mode for testing templates") config.Environment = "PRODUCTION" } - v := version.Get() - log.Info().Str("environment", config.Environment).Bool("is-prod", config.IsProductionEnvironment()).Str("revision", v.Revision).Str("build_time", v.BuildTime.String()).Bool("is modified", v.IsModified).Msg("Starting") + v := GetVersionInfo() + log.Info(). + Str("environment", config.Environment). + Bool("is-prod", config.IsProductionEnvironment()). + Str("revision", v.Revision). + Str("build_time", v.BuildTime.String()). + Bool("is modified", v.IsModified). + Str("Version", Version). + Str("Revision", Revision). + Str("BuildDate", BuildTime). + Msg("Starting") + api.SetVersionInfo(v) + err = sentry.Init(sentry.ClientOptions{ Debug: false, //!config.IsProductionEnvironment(), Dsn: config.SentryDSN, @@ -285,3 +304,50 @@ func LoggerMiddleware(logger *zerolog.Logger) func(next http.Handler) http.Handl return http.HandlerFunc(fn) } } +func GetVersionInfo() (version.VersionInfo, error) { + // Try ldflags first (set by Nix build) + if Revision != "" && Revision != "unknown" { + build_time, err := parseNixTime(BuildTime) + if err != nil { + return version.VersionInfo{}, fmt.Errorf("parse nix time: %w", err) + } + return version.VersionInfo{ + BuildTime: build_time, + IsModified: Revision == "dirty", + Revision: Revision, + }, nil + } + + // Fallback to debug.ReadBuildInfo() for development + info, ok := debug.ReadBuildInfo() + if !ok { + return version.VersionInfo{}, fmt.Errorf("read build info not ok") + } + + var v version.VersionInfo + for _, setting := range info.Settings { + switch setting.Key { + case "vcs.modified": + v.IsModified = setting.Value == "true" + case "vcs.revision": + if len(setting.Value) > 7 { + v.Revision = setting.Value[:7] + } else { + v.Revision = setting.Value + } + case "vcs.time": + if t, err := time.Parse(time.RFC3339, setting.Value); err == nil { + v.BuildTime = t + } + } + } + return v, nil +} +func parseNixTime(timestamp string) (time.Time, error) { + layout := "20060102150405" + t, err := time.Parse(layout, timestamp) + if err != nil { + return time.Time{}, err + } + return t, nil +} diff --git a/version/version.go b/version/version.go index 4cc4729e..d2c917c6 100644 --- a/version/version.go +++ b/version/version.go @@ -1,65 +1,11 @@ package version import ( - "runtime/debug" - "strconv" "time" ) -// These will be set by ldflags at build time -var ( - Version = "dev" - Commit = "" - BuildTime = "0" - IsModified = "false" -) - type VersionInfo struct { BuildTime time.Time `json:"build_time"` IsModified bool `json:"is_modified"` Revision string `json:"revision"` } - -func Get() VersionInfo { - // Try ldflags first (set by Nix build) - if Commit != "" && Commit != "unknown" { - var buildTime time.Time - if timestamp, err := strconv.ParseInt(BuildTime, 10, 64); err == nil && timestamp > 0 { - buildTime = time.Unix(timestamp, 0) - } - - return VersionInfo{ - Revision: Commit, - BuildTime: buildTime, - IsModified: IsModified == "true", - } - } - - // Fallback to debug.ReadBuildInfo() for development - info, ok := debug.ReadBuildInfo() - if !ok { - return VersionInfo{ - Revision: "unknown", - } - } - - var version VersionInfo - for _, setting := range info.Settings { - switch setting.Key { - case "vcs.modified": - version.IsModified = setting.Value == "true" - case "vcs.revision": - if len(setting.Value) > 7 { - version.Revision = setting.Value[:7] - } else { - version.Revision = setting.Value - } - case "vcs.time": - if t, err := time.Parse(time.RFC3339, setting.Value); err == nil { - version.BuildTime = t - } - } - } - - return version -}