From 307bb9f6ccf19f1fa58d4525feda27100807f5e1 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Tue, 16 Dec 2025 22:51:10 +0000 Subject: [PATCH] feat: add Linux agent archives to releases and improve version mismatch detection Release improvements: - Add standalone Linux agent archives (amd64, arm64, armv7, armv6, 386) to releases - Previously only Darwin/Windows had standalone agent downloads Agent version mismatch detection: - New agentVersion.ts utility compares agent versions against server version - DockerHostSummaryTable now shows accurate 'outdated' warnings when agent version is older than the connected Pulse server - Better tooltips explain version status and upgrade instructions --- .../Docker/DockerHostSummaryTable.tsx | 11 +- frontend-modern/src/utils/agentVersion.ts | 165 ++++++++++++++++++ scripts/build-release.sh | 20 ++- 3 files changed, 186 insertions(+), 10 deletions(-) create mode 100644 frontend-modern/src/utils/agentVersion.ts diff --git a/frontend-modern/src/components/Docker/DockerHostSummaryTable.tsx b/frontend-modern/src/components/Docker/DockerHostSummaryTable.tsx index f032def..43bc62a 100644 --- a/frontend-modern/src/components/Docker/DockerHostSummaryTable.tsx +++ b/frontend-modern/src/components/Docker/DockerHostSummaryTable.tsx @@ -12,6 +12,7 @@ import { getDockerHostStatusIndicator } from '@/utils/status'; import { useBreakpoint } from '@/hooks/useBreakpoint'; import { ResponsiveMetricCell, MetricText } from '@/components/shared/responsive'; import { EnhancedCPUBar } from '@/components/Dashboard/EnhancedCPUBar'; +import { isAgentOutdated, getAgentVersionTooltip } from '@/utils/agentVersion'; export interface DockerHostSummary { host: DockerHost; @@ -129,10 +130,7 @@ export const DockerHostSummaryTable: Component = (p return sortDirection() === 'asc' ? '▲' : '▼'; }; - const isAgentOutdated = (version?: string) => { - if (!version) return false; - return version.includes('dev') || version.startsWith('0.1'); - }; + // Agent version checking is now done via the shared utility that compares against server version return ( @@ -370,10 +368,7 @@ export const DockerHostSummaryTable: Component = (p ? 'px-1.5 py-0.5 rounded bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-400 font-medium' : 'px-1.5 py-0.5 rounded bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400 font-medium' } - title={`${agentOutdated - ? 'Agent is outdated on this host' - : 'Agent is up to date' - }${summary.host.intervalSeconds ? `\nReporting interval: ${summary.host.intervalSeconds}s` : ''}`} + title={`${getAgentVersionTooltip(summary.host.agentVersion)}${summary.host.intervalSeconds ? `\nReporting interval: ${summary.host.intervalSeconds}s` : ''}`} > {version()} diff --git a/frontend-modern/src/utils/agentVersion.ts b/frontend-modern/src/utils/agentVersion.ts new file mode 100644 index 0000000..56e38c5 --- /dev/null +++ b/frontend-modern/src/utils/agentVersion.ts @@ -0,0 +1,165 @@ +/** + * Agent version comparison utilities + * + * Used to detect outdated agents by comparing their version to the server version. + */ + +import { updateStore } from '@/stores/updates'; + +/** + * Parses a version string (e.g., "v5.0.0", "5.0.0-rc.3", "dev") into components + */ +interface ParsedVersion { + major: number; + minor: number; + patch: number; + prerelease: string | null; + isValid: boolean; +} + +function parseVersion(version: string): ParsedVersion { + if (!version || version === 'dev' || version === 'unknown') { + return { major: 0, minor: 0, patch: 0, prerelease: null, isValid: false }; + } + + // Remove leading 'v' if present + const cleaned = version.startsWith('v') ? version.slice(1) : version; + + // Split into version and prerelease parts + const [versionPart, ...prereleaseParts] = cleaned.split('-'); + const prerelease = prereleaseParts.length > 0 ? prereleaseParts.join('-') : null; + + // Parse major.minor.patch + const parts = versionPart.split('.').map((p) => parseInt(p, 10)); + + if (parts.some((p) => isNaN(p))) { + return { major: 0, minor: 0, patch: 0, prerelease: null, isValid: false }; + } + + return { + major: parts[0] || 0, + minor: parts[1] || 0, + patch: parts[2] || 0, + prerelease, + isValid: true, + }; +} + +/** + * Compares two version strings + * Returns: + * -1 if a < b + * 0 if a == b + * 1 if a > b + */ +export function compareVersions(a: string, b: string): number { + const va = parseVersion(a); + const vb = parseVersion(b); + + // Invalid versions are considered older + if (!va.isValid && !vb.isValid) return 0; + if (!va.isValid) return -1; + if (!vb.isValid) return 1; + + // Compare major.minor.patch + if (va.major !== vb.major) return va.major - vb.major; + if (va.minor !== vb.minor) return va.minor - vb.minor; + if (va.patch !== vb.patch) return va.patch - vb.patch; + + // Same base version, compare prerelease + // No prerelease = stable = newer than prerelease + if (!va.prerelease && vb.prerelease) return 1; + if (va.prerelease && !vb.prerelease) return -1; + if (!va.prerelease && !vb.prerelease) return 0; + + // Both have prerelease, compare lexicographically + return (va.prerelease || '').localeCompare(vb.prerelease || ''); +} + +/** + * Gets the current server version from the update store + */ +export function getServerVersion(): string { + const info = updateStore.versionInfo(); + return info?.version ?? ''; +} + +/** + * Checks if an agent version is outdated compared to the server version + * Returns an object with the status and details + */ +export interface AgentVersionStatus { + isOutdated: boolean; + isDev: boolean; + isUnknown: boolean; + serverVersion: string; + comparisonResult: number; // -1 = older, 0 = same, 1 = newer +} + +export function checkAgentVersion(agentVersion?: string): AgentVersionStatus { + const result: AgentVersionStatus = { + isOutdated: false, + isDev: false, + isUnknown: false, + serverVersion: getServerVersion(), + comparisonResult: 0, + }; + + if (!agentVersion) { + result.isUnknown = true; + return result; + } + + // Dev versions are always considered potentially outdated + if (agentVersion === 'dev' || agentVersion.includes('-dirty')) { + result.isDev = true; + // Don't mark dev as outdated if server is also dev + if (result.serverVersion && !result.serverVersion.includes('dev')) { + result.isOutdated = true; + } + return result; + } + + // Compare to server version if available + if (result.serverVersion) { + result.comparisonResult = compareVersions(agentVersion, result.serverVersion); + // Agent is outdated if it's older than server + result.isOutdated = result.comparisonResult < 0; + } + + return result; +} + +/** + * Simple check for whether an agent should show an outdated warning + * This is a convenience wrapper around checkAgentVersion + */ +export function isAgentOutdated(agentVersion?: string): boolean { + const status = checkAgentVersion(agentVersion); + return status.isOutdated || status.isDev; +} + +/** + * Gets a tooltip message explaining the agent version status + */ +export function getAgentVersionTooltip(agentVersion?: string): string { + const status = checkAgentVersion(agentVersion); + + if (status.isUnknown) { + return 'Agent version unknown'; + } + + if (status.isDev) { + return 'Development build - consider updating to a release version'; + } + + if (status.isOutdated && status.serverVersion) { + return `Agent is outdated (${agentVersion} < ${status.serverVersion}). Re-run the install script to update.`; + } + + if (status.comparisonResult > 0 && status.serverVersion) { + return `Agent is newer than server (${agentVersion} > ${status.serverVersion})`; + } + + return 'Agent is up to date'; +} diff --git a/scripts/build-release.sh b/scripts/build-release.sh index 27e2541..ef3b5c2 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -330,16 +330,32 @@ chmod +x "$universal_dir/bin/pulse-agent" # Add VERSION file echo "$VERSION" > "$universal_dir/VERSION" -# Package standalone host agent binaries +# Package standalone host agent binaries (all platforms) +# Linux +tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-linux-amd64.tar.gz" -C "$BUILD_DIR" pulse-host-agent-linux-amd64 +tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-linux-arm64.tar.gz" -C "$BUILD_DIR" pulse-host-agent-linux-arm64 +tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-linux-armv7.tar.gz" -C "$BUILD_DIR" pulse-host-agent-linux-armv7 +tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-linux-armv6.tar.gz" -C "$BUILD_DIR" pulse-host-agent-linux-armv6 +tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-linux-386.tar.gz" -C "$BUILD_DIR" pulse-host-agent-linux-386 +# Darwin tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-darwin-amd64.tar.gz" -C "$BUILD_DIR" pulse-host-agent-darwin-amd64 tar -czf "$RELEASE_DIR/pulse-host-agent-v${VERSION}-darwin-arm64.tar.gz" -C "$BUILD_DIR" pulse-host-agent-darwin-arm64 +# Windows zip -j "$RELEASE_DIR/pulse-host-agent-v${VERSION}-windows-amd64.zip" "$BUILD_DIR/pulse-host-agent-windows-amd64.exe" zip -j "$RELEASE_DIR/pulse-host-agent-v${VERSION}-windows-arm64.zip" "$BUILD_DIR/pulse-host-agent-windows-arm64.exe" zip -j "$RELEASE_DIR/pulse-host-agent-v${VERSION}-windows-386.zip" "$BUILD_DIR/pulse-host-agent-windows-386.exe" -# Package standalone unified agent binaries +# Package standalone unified agent binaries (all platforms) +# Linux +tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-linux-amd64.tar.gz" -C "$BUILD_DIR" pulse-agent-linux-amd64 +tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-linux-arm64.tar.gz" -C "$BUILD_DIR" pulse-agent-linux-arm64 +tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-linux-armv7.tar.gz" -C "$BUILD_DIR" pulse-agent-linux-armv7 +tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-linux-armv6.tar.gz" -C "$BUILD_DIR" pulse-agent-linux-armv6 +tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-linux-386.tar.gz" -C "$BUILD_DIR" pulse-agent-linux-386 +# Darwin tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-darwin-amd64.tar.gz" -C "$BUILD_DIR" pulse-agent-darwin-amd64 tar -czf "$RELEASE_DIR/pulse-agent-v${VERSION}-darwin-arm64.tar.gz" -C "$BUILD_DIR" pulse-agent-darwin-arm64 +# Windows zip -j "$RELEASE_DIR/pulse-agent-v${VERSION}-windows-amd64.zip" "$BUILD_DIR/pulse-agent-windows-amd64.exe" zip -j "$RELEASE_DIR/pulse-agent-v${VERSION}-windows-arm64.zip" "$BUILD_DIR/pulse-agent-windows-arm64.exe" zip -j "$RELEASE_DIR/pulse-agent-v${VERSION}-windows-386.zip" "$BUILD_DIR/pulse-agent-windows-386.exe"