diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index 3cbf71f..3f6067e 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -2544,6 +2544,47 @@ _NIX_WRAPPER_SUFFIX_RE = re.compile( r"-(?:env|wrapper|wrapped|script|hook|setup|compat)$" ) +# These two applications are downloaded into /var/lib/www at install time, so +# their PHP-FPM pool units identify the runtime but not the deployed application +# release. Read each application's own version file instead of reporting a PHP +# or Nix package version that can be unrelated to what Caddy is serving. +_DEPLOYED_WEB_APPLICATION_VERSION_FILES: dict[str, tuple[str, re.Pattern[str]]] = { + "phpfpm-nextcloud.service": ( + "/var/lib/www/nextcloud/version.php", + re.compile(r"^\s*\$OC_VersionString\s*=\s*['\"]([^'\"]+)['\"]\s*;", re.MULTILINE), + ), + "phpfpm-wordpress.service": ( + "/var/lib/www/wordpress/wp-includes/version.php", + re.compile(r"^\s*\$wp_version\s*=\s*['\"]([^'\"]+)['\"]\s*;", re.MULTILINE), + ), +} + + +def _get_deployed_web_application_version(unit: str) -> str | None: + """Return the version embedded in a PHP application deployed under /var/lib/www.""" + spec = _DEPLOYED_WEB_APPLICATION_VERSION_FILES.get(unit) + if spec is None: + return None + + path, version_pattern = spec + try: + # The version files are short PHP metadata files. Limiting the read makes + # this safe even if an installation is damaged or has been replaced. + with open(path, "r", encoding="utf-8", errors="replace") as fh: + contents = fh.read(64 * 1024) + except OSError: + return None + + match = version_pattern.search(contents) + if match is None: + return None + + version = match.group(1).strip() + if not version or len(version) > 64: + return None + return version if version.startswith("v") else f"v{version}" + + # 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 @@ -2552,10 +2593,13 @@ _SVC_VERSION_CACHE_TTL = 300 # 5 minutes — versions only change on system upd def _get_service_version(unit: str) -> str | None: """Extract the version of a service. - Checks the Nix-generated build-time versions reference file (loaded via load_versions()) first. - Falls back to running ``systemctl show --property=ExecStart --value`` and parsing - the Nix store path embedded in the output if the reference file is missing or incomplete. + PHP applications deployed under /var/lib/www are read from their own version + files. Other services first use the Nix-generated build-time version reference + and then fall back to parsing a Nix store path in their systemd ExecStart. """ + if unit in _DEPLOYED_WEB_APPLICATION_VERSION_FILES: + return _get_deployed_web_application_version(unit) + try: versions_ref = load_versions() ver = versions_ref.get(unit) diff --git a/app/sovran_systemsos_web/static/css/tiles.css b/app/sovran_systemsos_web/static/css/tiles.css index dd75aaa..94e9f7f 100644 --- a/app/sovran_systemsos_web/static/css/tiles.css +++ b/app/sovran_systemsos_web/static/css/tiles.css @@ -256,44 +256,6 @@ color: var(--text-primary); } -/* Version is secondary metadata: keep it available without making it a focal point. */ -.svc-detail-version-row { - display: flex; - align-items: center; - gap: 7px; - margin: -2px 0 16px; - padding: 4px 0; - color: var(--text-dim); - font-size: 0.74rem; - opacity: 0.78; -} - -.svc-detail-version-icon { - color: var(--text-dim); - font-size: 1rem; - line-height: 1; - opacity: 0.7; -} - -.svc-detail-version-label { - font-weight: 600; -} - -.svc-detail-version-value { - min-width: 0; - color: var(--text-secondary); - font-family: 'JetBrains Mono', 'Fira Code', monospace; - font-size: 0.74rem; - word-break: break-word; -} - -.svc-detail-version-row--unavailable .svc-detail-version-value, -.svc-detail-version-row--not-applicable .svc-detail-version-value { - color: var(--text-dim); - font-family: inherit; - font-style: italic; -} - /* ── Service detail: Domain ──────────────────────────────────────── */ .svc-detail-domain-value { diff --git a/app/sovran_systemsos_web/static/js/service-detail.js b/app/sovran_systemsos_web/static/js/service-detail.js index 643c6de..3375988 100644 --- a/app/sovran_systemsos_web/static/js/service-detail.js +++ b/app/sovran_systemsos_web/static/js/service-detail.js @@ -673,30 +673,6 @@ async function openServiceDetailModal(unit, name, icon) { '' + ''); - // Keep package versions in the detail modal rather than the compact tile. - // The API uses `version` for regular services and `bitcoin_version` for - // older Bitcoin payloads, so accept both while the backend is upgraded. - var serviceVersion = data.version || data.bitcoin_version || ''; - // These services are wrappers, setup helpers, or application stacks rather - // than a single versioned daemon represented by the tile's systemd unit. - var versionNotApplicable = [ - 'zeus-connect-setup.service', 'sparrow-autoconnect.service' - ].indexOf(unit) !== -1; - var versionState = serviceVersion ? 'detected' : (versionNotApplicable ? 'not-applicable' : 'unavailable'); - var versionText = serviceVersion || (versionNotApplicable - ? 'This service does not expose a single package version' - : (effectiveEnabled ? 'Version could not be detected' : 'Start the service to detect its version')); - var versionBadge = serviceVersion ? 'Detected' : (versionNotApplicable ? 'Not applicable' : 'Not detected'); - // Version is useful supporting information, but not the primary reason for - // opening this modal. Keep it as a quiet metadata row instead of a large - // callout so it does not compete with status and service actions. - addSetup('
' + - '' + - 'Version' + - '' + escHtml(serviceVersion || versionBadge) + '' + - '
'); - // Section B2: BIP-110 live status (bip110 tile only) if (icon === 'bip110' && data.bip110) { var bip110 = data.bip110; diff --git a/app/sovran_systemsos_web/versions.json b/app/sovran_systemsos_web/versions.json index ee3a23a..e5d89f1 100644 --- a/app/sovran_systemsos_web/versions.json +++ b/app/sovran_systemsos_web/versions.json @@ -12,7 +12,5 @@ "matrix-synapse.service": "1.115.0", "livekit.service": "1.5.2", "vaultwarden.service": "1.32.0", - "phpfpm-nextcloud.service": "29.0.0", - "phpfpm-wordpress.service": "6.5.0", "haven-relay.service": "0.1.0" } diff --git a/modules/core/sovran-hub.nix b/modules/core/sovran-hub.nix index c542ed7..6eb4670 100644 --- a/modules/core/sovran-hub.nix +++ b/modules/core/sovran-hub.nix @@ -132,8 +132,8 @@ let "matrix-synapse.service" = if pkgs ? matrix-synapse then pkgs.matrix-synapse.version else "1.115.0"; "livekit.service" = if pkgs ? livekit then pkgs.livekit.version else "1.5.2"; "vaultwarden.service" = if pkgs ? vaultwarden then pkgs.vaultwarden.version else "1.32.0"; - "phpfpm-nextcloud.service" = if pkgs ? nextcloud then pkgs.nextcloud.version else "29.0.0"; - "phpfpm-wordpress.service" = if pkgs ? wordpress then pkgs.wordpress.version else "6.5.0"; + # Nextcloud and WordPress are downloaded into /var/lib/www, so their + # deployed versions are read from their own PHP version files by the Hub. "haven-relay.service" = if pkgs ? haven-relay then pkgs.haven-relay.version else (if pkgs ? haven then pkgs.haven.version else "0.1.0"); });