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:
parent
ee35d9e5a5
commit
920f271b41
9 changed files with 145 additions and 18 deletions
|
|
@ -81,6 +81,7 @@ func main() {
|
||||||
Interval: cfg.Interval,
|
Interval: cfg.Interval,
|
||||||
HostnameOverride: cfg.HostnameOverride,
|
HostnameOverride: cfg.HostnameOverride,
|
||||||
AgentID: cfg.AgentID, // Shared ID? Or separate? Usually separate for now.
|
AgentID: cfg.AgentID, // Shared ID? Or separate? Usually separate for now.
|
||||||
|
AgentType: "unified",
|
||||||
Tags: cfg.Tags,
|
Tags: cfg.Tags,
|
||||||
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
||||||
LogLevel: cfg.LogLevel,
|
LogLevel: cfg.LogLevel,
|
||||||
|
|
@ -101,7 +102,7 @@ func main() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Start Docker Agent (if enabled)
|
// 6. Start Docker Agent (if enabled)
|
||||||
if cfg.EnableDocker {
|
if cfg.EnableDocker {
|
||||||
dockerCfg := dockeragent.Config{
|
dockerCfg := dockeragent.Config{
|
||||||
PulseURL: cfg.PulseURL,
|
PulseURL: cfg.PulseURL,
|
||||||
|
|
@ -109,8 +110,9 @@ func main() {
|
||||||
Interval: cfg.Interval,
|
Interval: cfg.Interval,
|
||||||
HostnameOverride: cfg.HostnameOverride,
|
HostnameOverride: cfg.HostnameOverride,
|
||||||
AgentID: cfg.AgentID,
|
AgentID: cfg.AgentID,
|
||||||
|
AgentType: "unified",
|
||||||
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
||||||
DisableAutoUpdate: true, // Unified agent handles updates (future)
|
DisableAutoUpdate: true, // Unified agent handles updates
|
||||||
LogLevel: cfg.LogLevel,
|
LogLevel: cfg.LogLevel,
|
||||||
Logger: &logger,
|
Logger: &logger,
|
||||||
// Docker specific defaults
|
// Docker specific defaults
|
||||||
|
|
|
||||||
|
|
@ -299,6 +299,14 @@ export const UnifiedAgents: Component = () => {
|
||||||
return Array.from(unified.values()).sort((a, b) => a.hostname.localeCompare(b.hostname));
|
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') => {
|
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;
|
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>
|
</p>
|
||||||
</div>
|
</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">
|
<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">
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-800">
|
<thead class="bg-gray-50 dark:bg-gray-800">
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ type Config struct {
|
||||||
Interval time.Duration
|
Interval time.Duration
|
||||||
HostnameOverride string
|
HostnameOverride string
|
||||||
AgentID string
|
AgentID string
|
||||||
|
AgentType string // "unified" when running as part of pulse-agent, empty for standalone
|
||||||
InsecureSkipVerify bool
|
InsecureSkipVerify bool
|
||||||
DisableAutoUpdate bool
|
DisableAutoUpdate bool
|
||||||
Targets []TargetConfig
|
Targets []TargetConfig
|
||||||
|
|
@ -656,6 +657,7 @@ func (a *Agent) buildReport(ctx context.Context) (agentsdocker.Report, error) {
|
||||||
Agent: agentsdocker.AgentInfo{
|
Agent: agentsdocker.AgentInfo{
|
||||||
ID: agentID,
|
ID: agentID,
|
||||||
Version: Version,
|
Version: Version,
|
||||||
|
Type: a.cfg.AgentType,
|
||||||
IntervalSeconds: int(a.cfg.Interval / time.Second),
|
IntervalSeconds: int(a.cfg.Interval / time.Second),
|
||||||
},
|
},
|
||||||
Host: agentsdocker.HostInfo{
|
Host: agentsdocker.HostInfo{
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ type Config struct {
|
||||||
Interval time.Duration
|
Interval time.Duration
|
||||||
HostnameOverride string
|
HostnameOverride string
|
||||||
AgentID string
|
AgentID string
|
||||||
|
AgentType string // "unified" when running as part of pulse-agent, empty for standalone
|
||||||
Tags []string
|
Tags []string
|
||||||
InsecureSkipVerify bool
|
InsecureSkipVerify bool
|
||||||
RunOnce bool
|
RunOnce bool
|
||||||
|
|
@ -241,6 +242,7 @@ func (a *Agent) buildReport(ctx context.Context) (agentshost.Report, error) {
|
||||||
Agent: agentshost.AgentInfo{
|
Agent: agentshost.AgentInfo{
|
||||||
ID: a.agentID,
|
ID: a.agentID,
|
||||||
Version: Version,
|
Version: Version,
|
||||||
|
Type: a.cfg.AgentType,
|
||||||
IntervalSeconds: int(a.interval / time.Second),
|
IntervalSeconds: int(a.interval / time.Second),
|
||||||
Hostname: a.hostname,
|
Hostname: a.hostname,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1877,7 +1877,7 @@ func (m *Monitor) ApplyDockerReport(report agentsdocker.Report, tokenRecord *con
|
||||||
Services: services,
|
Services: services,
|
||||||
Tasks: tasks,
|
Tasks: tasks,
|
||||||
Swarm: swarmInfo,
|
Swarm: swarmInfo,
|
||||||
IsLegacy: isLegacyDockerAgent(report.Agent.Version),
|
IsLegacy: isLegacyDockerAgent(report.Agent.Type),
|
||||||
}
|
}
|
||||||
|
|
||||||
if tokenRecord != nil {
|
if tokenRecord != nil {
|
||||||
|
|
@ -2160,7 +2160,7 @@ func (m *Monitor) ApplyHostReport(report agentshost.Report, tokenRecord *config.
|
||||||
LastSeen: timestamp,
|
LastSeen: timestamp,
|
||||||
AgentVersion: strings.TrimSpace(report.Agent.Version),
|
AgentVersion: strings.TrimSpace(report.Agent.Version),
|
||||||
Tags: append([]string(nil), report.Tags...),
|
Tags: append([]string(nil), report.Tags...),
|
||||||
IsLegacy: isLegacyHostAgent(report.Agent.Version),
|
IsLegacy: isLegacyHostAgent(report.Agent.Type),
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(host.LoadAverage) == 0 {
|
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 state without needing to grab the alert manager lock again.
|
||||||
mock.UpdateAlertSnapshots(m.alertManager.GetActiveAlerts(), m.alertManager.GetRecentlyResolved())
|
mock.UpdateAlertSnapshots(m.alertManager.GetActiveAlerts(), m.alertManager.GetRecentlyResolved())
|
||||||
}
|
}
|
||||||
func isLegacyHostAgent(version string) bool {
|
func isLegacyHostAgent(agentType string) bool {
|
||||||
// New unified agent is 1.0.0+
|
// Unified agent reports type="unified"
|
||||||
// Legacy agents are 0.x.x or empty
|
// Legacy standalone agents have empty type
|
||||||
if version == "" {
|
return agentType != "unified"
|
||||||
return true
|
|
||||||
}
|
|
||||||
return strings.HasPrefix(version, "0.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isLegacyDockerAgent(version string) bool {
|
func isLegacyDockerAgent(agentType string) bool {
|
||||||
// New unified agent is 1.0.0+
|
// Unified agent reports type="unified"
|
||||||
// Legacy agents are 0.x.x or empty
|
// Legacy standalone agents have empty type
|
||||||
if version == "" {
|
return agentType != "unified"
|
||||||
return true
|
|
||||||
}
|
|
||||||
return strings.HasPrefix(version, "0.")
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ type Report struct {
|
||||||
type AgentInfo struct {
|
type AgentInfo struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
|
Type string `json:"type,omitempty"` // "unified", "host", or "docker" - empty means legacy
|
||||||
IntervalSeconds int `json:"intervalSeconds"`
|
IntervalSeconds int `json:"intervalSeconds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ type Report struct {
|
||||||
type AgentInfo struct {
|
type AgentInfo struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Version string `json:"version,omitempty"`
|
Version string `json:"version,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"` // "unified", "host", or "docker" - empty means legacy
|
||||||
IntervalSeconds int `json:"intervalSeconds,omitempty"`
|
IntervalSeconds int `json:"intervalSeconds,omitempty"`
|
||||||
Hostname string `json:"hostname,omitempty"`
|
Hostname string `json:"hostname,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
65
scripts/install-docker-agent.sh
Executable file
65
scripts/install-docker-agent.sh
Executable 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
|
||||||
|
|
@ -1,5 +1,19 @@
|
||||||
# Pulse Host Agent Installation Script for Windows
|
# 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:
|
# Usage:
|
||||||
# iwr -useb http://pulse-server:7656/install-host-agent.ps1 | iex
|
# iwr -useb http://pulse-server:7656/install-host-agent.ps1 | iex
|
||||||
# OR with parameters:
|
# OR with parameters:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue