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>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-07 02:06:13 +00:00
committed by GitHub
parent 8a49a3d04e
commit deb66c9cb7

View File

@@ -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 _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. # Regex to extract the version from a Nix store ExecStart path.
# Only include services where a reliable --version flag exists. # Pattern: /nix/store/<32-char-hash>-<name-segments>-<version>/...
_SERVICE_VERSION_COMMANDS: dict[str, list[str]] = { # The name segments consist of alphabetic-starting words separated by hyphens.
"electrs.service": ["electrs", "--version"], # The version is the first hyphen-delimited token that starts with a digit.
"lnd.service": ["lnd", "--version"], _NIX_STORE_VERSION_RE = re.compile(
"caddy.service": ["caddy", "version"], r"/nix/store/[a-z0-9]{32}-" # hash prefix
"tor.service": ["tor", "--version"], r"(?:[a-zA-Z][a-zA-Z0-9_]*(?:-[a-zA-Z][a-zA-Z0-9_]*)*)+" # package name
"livekit.service": ["livekit-server", "--version"], r"-(\d+\.\d+[a-zA-Z0-9._+-]*)/" # version (group 1)
"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"],
}
# Cache: unit → (monotonic_timestamp, version_str | None) # Cache: unit → (monotonic_timestamp, version_str | None)
_svc_version_cache: dict[str, tuple[float, str | None]] = {} _svc_version_cache: dict[str, tuple[float, str | None]] = {}
_SVC_VERSION_CACHE_TTL = 300 # 5 minutes — versions only change on system update _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: 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 <unit> --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 Results are cached for _SVC_VERSION_CACHE_TTL seconds so that repeated
/api/services polls don't re-exec binaries on every request. Returns /api/services polls don't spawn extra processes on every request.
None if no version command is configured or if the command fails. Returns None if the version cannot be determined.
""" """
now = time.monotonic() now = time.monotonic()
cached = _svc_version_cache.get(unit) cached = _svc_version_cache.get(unit)
@@ -1558,18 +1538,20 @@ def _get_service_version(unit: str) -> str | None:
return cached_val return cached_val
version: str | None = None version: str | None = None
cmd = _SERVICE_VERSION_COMMANDS.get(unit)
if cmd:
try: try:
result = subprocess.run( result = subprocess.run(
cmd, ["systemctl", "show", unit, "--property=ExecStart", "--value"],
capture_output=True, capture_output=True,
text=True, text=True,
timeout=5, timeout=5,
) )
output_raw = result.stdout.strip() or result.stderr.strip() if result.returncode == 0 and result.stdout.strip():
output = output_raw.splitlines()[0] if output_raw else "" m = _NIX_STORE_VERSION_RE.search(result.stdout)
version = _parse_version_from_output(output) 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: except Exception:
pass pass