From 8a49a3d04ebed3c7b0d60e9ac3ffc87abd61a4b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 02:03:49 +0000 Subject: [PATCH 1/4] Initial plan From deb66c9cb7818a91ae3dc07a7ee67c5b04b69cd2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 02:06:13 +0000 Subject: [PATCH 2/4] Replace CLI-based version detection with Nix store path extraction via systemctl show Agent-Logs-Url: https://github.com/naturallaw777/staging_alpha/sessions/d75fe7da-369a-40e9-913e-7dba45de21c3 Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com> --- app/sovran_systemsos_web/server.py | 82 ++++++++++++------------------ 1 file changed, 32 insertions(+), 50 deletions(-) diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index d3f7237..1495f12 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -1503,52 +1503,32 @@ _btc_version_cache: tuple[float, dict | None] = (0.0, None) _BTC_VERSION_CACHE_TTL = 60 # seconds — version doesn't change at runtime -# ── Generic service version detection ──────────────────────────── +# ── Generic service version detection (NixOS store path) ───────── -# Map service unit names to CLI commands that print a version string. -# Only include services where a reliable --version flag exists. -_SERVICE_VERSION_COMMANDS: dict[str, list[str]] = { - "electrs.service": ["electrs", "--version"], - "lnd.service": ["lnd", "--version"], - "caddy.service": ["caddy", "version"], - "tor.service": ["tor", "--version"], - "livekit.service": ["livekit-server", "--version"], - "vaultwarden.service": ["vaultwarden", "--version"], - "btcpayserver.service": ["btcpay-server", "--version"], - "matrix-synapse.service": ["python3", "-c", "import synapse; print(synapse.__version__)"], - "gnome-remote-desktop.service": ["grdctl", "--version"], -} +# Regex to extract the version from a Nix store ExecStart path. +# Pattern: /nix/store/<32-char-hash>--/... +# The name segments consist of alphabetic-starting words separated by hyphens. +# The version is the first hyphen-delimited token that starts with a digit. +_NIX_STORE_VERSION_RE = re.compile( + r"/nix/store/[a-z0-9]{32}-" # hash prefix + r"(?:[a-zA-Z][a-zA-Z0-9_]*(?:-[a-zA-Z][a-zA-Z0-9_]*)*)+" # package name + r"-(\d+\.\d+[a-zA-Z0-9._+-]*)/" # version (group 1) +) # Cache: unit → (monotonic_timestamp, version_str | None) _svc_version_cache: dict[str, tuple[float, str | None]] = {} _SVC_VERSION_CACHE_TTL = 300 # 5 minutes — versions only change on system update -def _parse_version_from_output(output: str) -> str | None: - """Extract the first semver-like version number from command output. - - Handles patterns such as: - 'electrs 0.10.5' - 'lnd version 0.18.4-beta commit=v0.18.4-beta' - 'Tor version 0.4.8.12.' - 'v2.7.6 h1:...' - Returns a string starting with 'v', e.g. 'v0.10.5', or None. - """ - m = re.search(r"v?(\d+\.\d+(?:\.\d+(?:\.\d+)?)?(?:[+-][a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*)?)", output) - if m: - ver = m.group(0) - if not ver.startswith("v"): - ver = "v" + ver - return ver - return None - - def _get_service_version(unit: str) -> str | None: - """Return a version string for *unit*, using a CLI command when available. + """Extract the version of a service from its Nix store ExecStart path. + + Runs ``systemctl show --property=ExecStart --value`` and parses + the Nix store path embedded in the output to obtain the package version. Results are cached for _SVC_VERSION_CACHE_TTL seconds so that repeated - /api/services polls don't re-exec binaries on every request. Returns - None if no version command is configured or if the command fails. + /api/services polls don't spawn extra processes on every request. + Returns None if the version cannot be determined. """ now = time.monotonic() cached = _svc_version_cache.get(unit) @@ -1558,20 +1538,22 @@ def _get_service_version(unit: str) -> str | None: return cached_val version: str | None = None - cmd = _SERVICE_VERSION_COMMANDS.get(unit) - if cmd: - try: - result = subprocess.run( - cmd, - capture_output=True, - text=True, - timeout=5, - ) - output_raw = result.stdout.strip() or result.stderr.strip() - output = output_raw.splitlines()[0] if output_raw else "" - version = _parse_version_from_output(output) - except Exception: - pass + try: + result = subprocess.run( + ["systemctl", "show", unit, "--property=ExecStart", "--value"], + capture_output=True, + text=True, + timeout=5, + ) + if result.returncode == 0 and result.stdout.strip(): + m = _NIX_STORE_VERSION_RE.search(result.stdout) + if m: + ver = m.group(1).rstrip(".") + # Skip Nix environment/wrapper suffixes that are not real versions + if not re.search(r"-(?:env|wrapper|wrapped|script|hook|setup|compat)$", ver): + version = ver if ver.startswith("v") else f"v{ver}" + except Exception: + pass _svc_version_cache[unit] = (now, version) return version From 8240b9af3ccf423437cea6c500876a72e9a28383 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 02:08:23 +0000 Subject: [PATCH 3/4] Address review feedback: module-level wrapper suffix regex, allow digit-starting name segments Agent-Logs-Url: https://github.com/naturallaw777/staging_alpha/sessions/d75fe7da-369a-40e9-913e-7dba45de21c3 Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com> --- app/sovran_systemsos_web/server.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index 1495f12..ea57798 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -1507,12 +1507,18 @@ _BTC_VERSION_CACHE_TTL = 60 # seconds — version doesn't change at runtime # Regex to extract the version from a Nix store ExecStart path. # Pattern: /nix/store/<32-char-hash>--/... -# The name segments consist of alphabetic-starting words separated by hyphens. -# The version is the first hyphen-delimited token that starts with a digit. +# Name segments may begin with a letter or digit (e.g. 'python3', 'gtk3', +# 'lib32-foo') so each segment allows [a-zA-Z0-9] as the leading character. +# The version is identified as the first token starting with digit.digit. _NIX_STORE_VERSION_RE = re.compile( - r"/nix/store/[a-z0-9]{32}-" # hash prefix - r"(?:[a-zA-Z][a-zA-Z0-9_]*(?:-[a-zA-Z][a-zA-Z0-9_]*)*)+" # package name - r"-(\d+\.\d+[a-zA-Z0-9._+-]*)/" # version (group 1) + r"/nix/store/[a-z0-9]{32}-" # hash prefix + r"(?:[a-zA-Z0-9][a-zA-Z0-9_]*(?:-[a-zA-Z0-9][a-zA-Z0-9_]*)*)+" # package name + r"-(\d+\.\d+[a-zA-Z0-9._+-]*)/" # version (group 1) +) + +# Nix path suffixes that indicate a wrapper environment, not a real package version. +_NIX_WRAPPER_SUFFIX_RE = re.compile( + r"-(?:env|wrapper|wrapped|script|hook|setup|compat)$" ) # Cache: unit → (monotonic_timestamp, version_str | None) @@ -1550,7 +1556,7 @@ def _get_service_version(unit: str) -> str | None: if m: ver = m.group(1).rstrip(".") # Skip Nix environment/wrapper suffixes that are not real versions - if not re.search(r"-(?:env|wrapper|wrapped|script|hook|setup|compat)$", ver): + if not _NIX_WRAPPER_SUFFIX_RE.search(ver): version = ver if ver.startswith("v") else f"v{ver}" except Exception: pass From 185ed4e3d823a5fae66deb95023ec188fe715ea1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 02:10:15 +0000 Subject: [PATCH 4/4] Further tighten regex: stricter version pattern, no underscores in name segments, precise trailing-dot strip Agent-Logs-Url: https://github.com/naturallaw777/staging_alpha/sessions/d75fe7da-369a-40e9-913e-7dba45de21c3 Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com> --- app/sovran_systemsos_web/server.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index ea57798..bb72721 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -1508,12 +1508,13 @@ _BTC_VERSION_CACHE_TTL = 60 # seconds — version doesn't change at runtime # Regex to extract the version from a Nix store ExecStart path. # Pattern: /nix/store/<32-char-hash>--/... # Name segments may begin with a letter or digit (e.g. 'python3', 'gtk3', -# 'lib32-foo') so each segment allows [a-zA-Z0-9] as the leading character. +# 'lib32-foo') and consist of alphanumeric characters only (no underscores, +# since Nix store paths use hyphens as separators). # The version is identified as the first token starting with digit.digit. _NIX_STORE_VERSION_RE = re.compile( - r"/nix/store/[a-z0-9]{32}-" # hash prefix - r"(?:[a-zA-Z0-9][a-zA-Z0-9_]*(?:-[a-zA-Z0-9][a-zA-Z0-9_]*)*)+" # package name - r"-(\d+\.\d+[a-zA-Z0-9._+-]*)/" # version (group 1) + r"/nix/store/[a-z0-9]{32}-" # hash prefix + r"(?:[a-zA-Z0-9][a-zA-Z0-9]*(?:-[a-zA-Z0-9][a-zA-Z0-9]*)*)+" # package name + r"-(\d+\.\d+(?:\.\d+)*(?:[+-][a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*)?)/" # version (group 1) ) # Nix path suffixes that indicate a wrapper environment, not a real package version. @@ -1554,7 +1555,9 @@ def _get_service_version(unit: str) -> str | None: if result.returncode == 0 and result.stdout.strip(): m = _NIX_STORE_VERSION_RE.search(result.stdout) if m: - ver = m.group(1).rstrip(".") + ver = m.group(1) + # Strip a single trailing period (defensive; shouldn't appear in store paths) + ver = ver[:-1] if ver.endswith(".") else ver # Skip Nix environment/wrapper suffixes that are not real versions if not _NIX_WRAPPER_SUFFIX_RE.search(ver): version = ver if ver.startswith("v") else f"v{ver}"