diff --git a/CLAUDE.md b/CLAUDE.md index 785e0c9..1dbf28e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -90,6 +90,10 @@ Follow semantic versioning: - **Minor** (0.x.0): Non-breaking changes adding features - **Major** (x.0.0): Breaking changes +Update the `const version` in `main.go`. The Nix build extracts the version from there automatically. + +**When to bump**: If any Go code has changed, bump the version before committing. Do this automatically when asked to commit. On feature branches, only bump once per branch (check if version has already been bumped compared to master). + ## Key Design Decisions - Go chosen for mature Prometheus client library and static binary output diff --git a/README.md b/README.md index ba85057..0099014 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ services.prometheus.exporters.nixos = { |--------|------|--------|-------------| | `nixos_flake_input_age_seconds` | Gauge | `input` | Age of flake input in seconds | | `nixos_flake_input_info` | Gauge | `input`, `rev`, `type` | Info gauge with revision and type labels | +| `nixos_flake_info` | Gauge | `current_rev`, `remote_rev` | Info gauge with current and remote flake revisions | | `nixos_flake_revision_behind` | Gauge | | 1 if current system revision differs from remote latest | ## Example Prometheus Alerts @@ -129,7 +130,7 @@ groups: ## Known Limitations -- The `nixos_flake_revision_behind` metric relies on parsing the git hash from `/run/current-system/nixos-version`. The format of this file varies depending on NixOS configuration: +- The `nixos_flake_info` and `nixos_flake_revision_behind` metrics rely on parsing the git hash from `/run/current-system/nixos-version`. The format of this file varies depending on NixOS configuration: - Standard format: `25.11.20260203.e576e3c` - Custom format: `1994-294a625` diff --git a/collector/flake.go b/collector/flake.go index 55a7df8..825f277 100644 --- a/collector/flake.go +++ b/collector/flake.go @@ -27,6 +27,7 @@ type FlakeCollector struct { inputAge *prometheus.Desc inputInfo *prometheus.Desc + flakeInfo *prometheus.Desc revisionBehind *prometheus.Desc mu sync.RWMutex @@ -75,6 +76,11 @@ func NewFlakeCollector(flakeURL string, checkInterval time.Duration) *FlakeColle "Info gauge with revision and type labels", []string{"input", "rev", "type"}, nil, ), + flakeInfo: prometheus.NewDesc( + "nixos_flake_info", + "Info gauge with current and remote flake revisions", + []string{"current_rev", "remote_rev"}, nil, + ), revisionBehind: prometheus.NewDesc( "nixos_flake_revision_behind", "1 if current system revision differs from remote latest, 0 if match", @@ -86,6 +92,7 @@ func NewFlakeCollector(flakeURL string, checkInterval time.Duration) *FlakeColle func (c *FlakeCollector) Describe(ch chan<- *prometheus.Desc) { ch <- c.inputAge ch <- c.inputInfo + ch <- c.flakeInfo ch <- c.revisionBehind } @@ -168,14 +175,17 @@ func (c *FlakeCollector) collectRevisionBehind(ch chan<- prometheus.Metric, data return } + remoteRev := data.Revision + if len(remoteRev) > 7 { + remoteRev = remoteRev[:7] + } + + // Emit flake info metric with revisions + ch <- prometheus.MustNewConstMetric(c.flakeInfo, prometheus.GaugeValue, 1, currentRev, remoteRev) + behind := 0.0 if currentRev != "" && data.Revision != "" { - // Compare short hashes - remoteShort := data.Revision - if len(remoteShort) > 7 { - remoteShort = remoteShort[:7] - } - if currentRev != remoteShort && !strings.HasPrefix(data.Revision, currentRev) { + if currentRev != remoteRev && !strings.HasPrefix(data.Revision, currentRev) { behind = 1.0 } } diff --git a/flake.nix b/flake.nix index 864fcdf..47c9696 100644 --- a/flake.nix +++ b/flake.nix @@ -9,6 +9,12 @@ let supportedSystems = [ "x86_64-linux" "aarch64-linux" ]; forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + + # Extract version from main.go + mainGo = builtins.readFile ./main.go; + lines = builtins.filter (x: builtins.isString x) (builtins.split "\n" mainGo); + versionLine = builtins.head (builtins.filter (line: builtins.match ".*const version = .*" line != null) lines); + version = builtins.head (builtins.match ".*\"([0-9.]+)\".*" versionLine); in { packages = forAllSystems (system: @@ -18,7 +24,7 @@ { default = pkgs.buildGoModule { pname = "nixos-exporter"; - version = "0.1.0"; + inherit version; src = ./.; vendorHash = "sha256-NnvB20rORPS5QF5enbb5KpWaKZ70ybSgfd7wjk21/Cg="; diff --git a/main.go b/main.go index edafe92..97c9b7a 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,8 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" ) +const version = "0.2.0" + func main() { cfg, err := config.Parse() if err != nil { @@ -59,7 +61,7 @@ func main() { defer stop() go func() { - slog.Info("Starting server", "addr", cfg.ListenAddr) + slog.Info("Starting server", "version", version, "addr", cfg.ListenAddr) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { slog.Error("Server error", "error", err) os.Exit(1)