Show deployed PHP app versions in service titles

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
This commit is contained in:
naturallaw777
2026-07-29 14:39:21 +00:00
co-authored by arena-agent
parent 17797d55bc
commit 199bde0bc6
5 changed files with 49 additions and 69 deletions
+47 -3
View File
@@ -2544,6 +2544,47 @@ _NIX_WRAPPER_SUFFIX_RE = re.compile(
r"-(?:env|wrapper|wrapped|script|hook|setup|compat)$" 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) # 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
@@ -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: def _get_service_version(unit: str) -> str | None:
"""Extract the version of a service. """Extract the version of a service.
Checks the Nix-generated build-time versions reference file (loaded via load_versions()) first. PHP applications deployed under /var/lib/www are read from their own version
Falls back to running ``systemctl show <unit> --property=ExecStart --value`` and parsing files. Other services first use the Nix-generated build-time version reference
the Nix store path embedded in the output if the reference file is missing or incomplete. 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: try:
versions_ref = load_versions() versions_ref = load_versions()
ver = versions_ref.get(unit) ver = versions_ref.get(unit)
@@ -256,44 +256,6 @@
color: var(--text-primary); 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 ──────────────────────────────────────── */ /* ── Service detail: Domain ──────────────────────────────────────── */
.svc-detail-domain-value { .svc-detail-domain-value {
@@ -673,30 +673,6 @@ async function openServiceDetailModal(unit, name, icon) {
'</div>' + '</div>' +
'</div>'); '</div>');
// 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('<div class="svc-detail-version-row svc-detail-version-row--' + versionState + '"' +
' title="' + escHtml(versionText) + '">' +
'<span class="svc-detail-version-icon" aria-hidden="true">⌁</span>' +
'<span class="svc-detail-version-label">Version</span>' +
'<span class="svc-detail-version-value">' + escHtml(serviceVersion || versionBadge) + '</span>' +
'</div>');
// Section B2: BIP-110 live status (bip110 tile only) // Section B2: BIP-110 live status (bip110 tile only)
if (icon === 'bip110' && data.bip110) { if (icon === 'bip110' && data.bip110) {
var bip110 = data.bip110; var bip110 = data.bip110;
-2
View File
@@ -12,7 +12,5 @@
"matrix-synapse.service": "1.115.0", "matrix-synapse.service": "1.115.0",
"livekit.service": "1.5.2", "livekit.service": "1.5.2",
"vaultwarden.service": "1.32.0", "vaultwarden.service": "1.32.0",
"phpfpm-nextcloud.service": "29.0.0",
"phpfpm-wordpress.service": "6.5.0",
"haven-relay.service": "0.1.0" "haven-relay.service": "0.1.0"
} }
+2 -2
View File
@@ -132,8 +132,8 @@ let
"matrix-synapse.service" = if pkgs ? matrix-synapse then pkgs.matrix-synapse.version else "1.115.0"; "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"; "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"; "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"; # Nextcloud and WordPress are downloaded into /var/lib/www, so their
"phpfpm-wordpress.service" = if pkgs ? wordpress then pkgs.wordpress.version else "6.5.0"; # 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"); "haven-relay.service" = if pkgs ? haven-relay then pkgs.haven-relay.version else (if pkgs ? haven then pkgs.haven.version else "0.1.0");
}); });