feat: improve legacy agent detection and migration UX

Add seamless migration path from legacy agents to unified agent:

- Add AgentType field to report payloads (unified vs legacy detection)
- Update server to detect legacy agents by type instead of version
- Add UI banner showing upgrade command when legacy agents are detected
- Add deprecation notice to install-host-agent.ps1
- Create install-docker-agent.sh stub that redirects to unified installer

Legacy agents (pulse-host-agent, pulse-docker-agent) now show a "Legacy"
badge in the UI with a one-click copy command to upgrade to the unified
agent.
This commit is contained in:
rcourtman
2025-11-25 23:26:22 +00:00
parent 0436101ee5
commit ea335546fc
9 changed files with 145 additions and 18 deletions

View File

@@ -81,6 +81,7 @@ func main() {
Interval: cfg.Interval,
HostnameOverride: cfg.HostnameOverride,
AgentID: cfg.AgentID, // Shared ID? Or separate? Usually separate for now.
AgentType: "unified",
Tags: cfg.Tags,
InsecureSkipVerify: cfg.InsecureSkipVerify,
LogLevel: cfg.LogLevel,
@@ -101,7 +102,7 @@ func main() {
})
}
// 5. Start Docker Agent (if enabled)
// 6. Start Docker Agent (if enabled)
if cfg.EnableDocker {
dockerCfg := dockeragent.Config{
PulseURL: cfg.PulseURL,
@@ -109,8 +110,9 @@ func main() {
Interval: cfg.Interval,
HostnameOverride: cfg.HostnameOverride,
AgentID: cfg.AgentID,
AgentType: "unified",
InsecureSkipVerify: cfg.InsecureSkipVerify,
DisableAutoUpdate: true, // Unified agent handles updates (future)
DisableAutoUpdate: true, // Unified agent handles updates
LogLevel: cfg.LogLevel,
Logger: &logger,
// Docker specific defaults

View File

@@ -299,6 +299,14 @@ export const UnifiedAgents: Component = () => {
return Array.from(unified.values()).sort((a, b) => a.hostname.localeCompare(b.hostname));
});
const legacyAgents = createMemo(() => allHosts().filter(h => h.isLegacy));
const hasLegacyAgents = createMemo(() => legacyAgents().length > 0);
const getUpgradeCommand = (hostname: string) => {
const token = resolvedToken();
return `curl -fsSL ${pulseUrl()}/install.sh | sudo bash -s -- --url ${pulseUrl()} --token ${token}`;
};
const handleRemoveAgent = async (id: string, type: 'host' | 'docker') => {
if (!confirm('Are you sure you want to remove this agent? This will stop monitoring but will not uninstall the agent from the remote machine.')) return;
@@ -589,6 +597,44 @@ export const UnifiedAgents: Component = () => {
</p>
</div>
<Show when={hasLegacyAgents()}>
<div class="rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 dark:border-amber-700 dark:bg-amber-900/20">
<div class="flex items-start gap-3">
<svg class="h-5 w-5 flex-shrink-0 text-amber-500 dark:text-amber-400 mt-0.5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
</svg>
<div class="flex-1 space-y-2">
<p class="text-sm font-medium text-amber-800 dark:text-amber-200">
{legacyAgents().length} legacy agent{legacyAgents().length > 1 ? 's' : ''} detected
</p>
<p class="text-sm text-amber-700 dark:text-amber-300">
Legacy agents (pulse-host-agent, pulse-docker-agent) are deprecated. Upgrade to the unified agent for auto-updates and combined host + Docker monitoring.
</p>
<p class="text-xs text-amber-600 dark:text-amber-400">
Run this command on each legacy host to upgrade:
</p>
<div class="flex items-center gap-2">
<code class="flex-1 break-all rounded bg-amber-100 px-3 py-2 font-mono text-xs text-amber-900 dark:bg-amber-900/40 dark:text-amber-100">
{getUpgradeCommand('')}
</code>
<button
type="button"
onClick={async () => {
const success = await copyToClipboard(getUpgradeCommand(''));
if (typeof window !== 'undefined' && window.showToast) {
window.showToast(success ? 'success' : 'error', success ? 'Copied!' : 'Failed to copy');
}
}}
class="rounded-lg bg-amber-200 px-3 py-1.5 text-xs font-medium text-amber-800 transition-colors hover:bg-amber-300 dark:bg-amber-800 dark:text-amber-100 dark:hover:bg-amber-700"
>
Copy
</button>
</div>
</div>
</div>
</div>
</Show>
<div class="overflow-hidden rounded-lg border border-gray-200 dark:border-gray-700">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-50 dark:bg-gray-800">

View File

@@ -47,6 +47,7 @@ type Config struct {
Interval time.Duration
HostnameOverride string
AgentID string
AgentType string // "unified" when running as part of pulse-agent, empty for standalone
InsecureSkipVerify bool
DisableAutoUpdate bool
Targets []TargetConfig
@@ -656,6 +657,7 @@ func (a *Agent) buildReport(ctx context.Context) (agentsdocker.Report, error) {
Agent: agentsdocker.AgentInfo{
ID: agentID,
Version: Version,
Type: a.cfg.AgentType,
IntervalSeconds: int(a.cfg.Interval / time.Second),
},
Host: agentsdocker.HostInfo{

View File

@@ -27,6 +27,7 @@ type Config struct {
Interval time.Duration
HostnameOverride string
AgentID string
AgentType string // "unified" when running as part of pulse-agent, empty for standalone
Tags []string
InsecureSkipVerify bool
RunOnce bool
@@ -241,6 +242,7 @@ func (a *Agent) buildReport(ctx context.Context) (agentshost.Report, error) {
Agent: agentshost.AgentInfo{
ID: a.agentID,
Version: Version,
Type: a.cfg.AgentType,
IntervalSeconds: int(a.interval / time.Second),
Hostname: a.hostname,
},

View File

@@ -1877,7 +1877,7 @@ func (m *Monitor) ApplyDockerReport(report agentsdocker.Report, tokenRecord *con
Services: services,
Tasks: tasks,
Swarm: swarmInfo,
IsLegacy: isLegacyDockerAgent(report.Agent.Version),
IsLegacy: isLegacyDockerAgent(report.Agent.Type),
}
if tokenRecord != nil {
@@ -2160,7 +2160,7 @@ func (m *Monitor) ApplyHostReport(report agentshost.Report, tokenRecord *config.
LastSeen: timestamp,
AgentVersion: strings.TrimSpace(report.Agent.Version),
Tags: append([]string(nil), report.Tags...),
IsLegacy: isLegacyHostAgent(report.Agent.Version),
IsLegacy: isLegacyHostAgent(report.Agent.Type),
}
if len(host.LoadAverage) == 0 {
@@ -9562,20 +9562,14 @@ func (m *Monitor) checkMockAlerts() {
// mock state without needing to grab the alert manager lock again.
mock.UpdateAlertSnapshots(m.alertManager.GetActiveAlerts(), m.alertManager.GetRecentlyResolved())
}
func isLegacyHostAgent(version string) bool {
// New unified agent is 1.0.0+
// Legacy agents are 0.x.x or empty
if version == "" {
return true
}
return strings.HasPrefix(version, "0.")
func isLegacyHostAgent(agentType string) bool {
// Unified agent reports type="unified"
// Legacy standalone agents have empty type
return agentType != "unified"
}
func isLegacyDockerAgent(version string) bool {
// New unified agent is 1.0.0+
// Legacy agents are 0.x.x or empty
if version == "" {
return true
}
return strings.HasPrefix(version, "0.")
func isLegacyDockerAgent(agentType string) bool {
// Unified agent reports type="unified"
// Legacy standalone agents have empty type
return agentType != "unified"
}

View File

@@ -24,6 +24,7 @@ type Report struct {
type AgentInfo struct {
ID string `json:"id"`
Version string `json:"version"`
Type string `json:"type,omitempty"` // "unified", "host", or "docker" - empty means legacy
IntervalSeconds int `json:"intervalSeconds"`
}

View File

@@ -20,6 +20,7 @@ type Report struct {
type AgentInfo struct {
ID string `json:"id"`
Version string `json:"version,omitempty"`
Type string `json:"type,omitempty"` // "unified", "host", or "docker" - empty means legacy
IntervalSeconds int `json:"intervalSeconds,omitempty"`
Hostname string `json:"hostname,omitempty"`
}

65
scripts/install-docker-agent.sh Executable file
View File

@@ -0,0 +1,65 @@
#!/usr/bin/env bash
#
# DEPRECATED: pulse-docker-agent installer
#
# This script is deprecated. Please use the unified agent instead:
#
# curl -fsSL http://<pulse-server>:7655/install.sh | \
# sudo bash -s -- --url http://<pulse-server>:7655 --token <token> --enable-docker
#
# The unified agent provides:
# - Combined host + Docker monitoring in one binary
# - Automatic updates
# - Simplified management
#
# This script will redirect you to the unified agent installer.
#
set -euo pipefail
echo ""
echo "┌─────────────────────────────────────────────────────────────────────────────┐"
echo "│ DEPRECATED: pulse-docker-agent has been replaced by the unified agent. │"
echo "│ │"
echo "│ The unified agent provides: │"
echo "│ • Combined host + Docker monitoring in one binary │"
echo "│ • Automatic updates │"
echo "│ • Simplified management │"
echo "│ │"
echo "│ Installing unified agent instead... │"
echo "└─────────────────────────────────────────────────────────────────────────────┘"
echo ""
# Parse arguments to extract URL and token for redirection
PULSE_URL=""
PULSE_TOKEN=""
while [[ $# -gt 0 ]]; do
case $1 in
--url) PULSE_URL="$2"; shift 2 ;;
--token) PULSE_TOKEN="$2"; shift 2 ;;
*) shift ;; # ignore other args
esac
done
if [[ -z "$PULSE_URL" ]]; then
echo "[ERROR] --url is required"
echo ""
echo "Usage:"
echo " curl -fsSL http://<pulse-server>:7655/install.sh | \\"
echo " sudo bash -s -- --url http://<pulse-server>:7655 --token <token> --enable-docker"
exit 1
fi
if [[ -z "$PULSE_TOKEN" ]]; then
echo "[ERROR] --token is required"
echo ""
echo "Usage:"
echo " curl -fsSL ${PULSE_URL}/install.sh | \\"
echo " sudo bash -s -- --url ${PULSE_URL} --token <token> --enable-docker"
exit 1
fi
# Download and run the unified installer with --enable-docker
echo "[INFO] Downloading unified agent installer..."
curl -fsSL "${PULSE_URL}/install.sh" | bash -s -- --url "$PULSE_URL" --token "$PULSE_TOKEN" --enable-docker

View File

@@ -1,5 +1,19 @@
# Pulse Host Agent Installation Script for Windows
#
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ DEPRECATED: This script installs the legacy pulse-host-agent. │
# │ Please use the unified agent instead: │
# │ │
# │ $env:PULSE_URL="http://pulse-server:7655" │
# │ $env:PULSE_TOKEN="your-token" │
# │ irm http://pulse-server:7655/install.ps1 | iex │
# │ │
# │ The unified agent provides: │
# │ - Combined host + Docker monitoring in one binary │
# │ - Automatic updates │
# │ - Simplified management │
# └─────────────────────────────────────────────────────────────────────────────┘
#
# Usage:
# iwr -useb http://pulse-server:7656/install-host-agent.ps1 | iex
# OR with parameters: