From 3ec7b401a32c7ee62c361bdbeef852bcd79f8d63 Mon Sep 17 00:00:00 2001 From: "courtmanr@gmail.com" Date: Tue, 25 Nov 2025 11:17:37 +0000 Subject: [PATCH] Improve installer UX with pauses and popups on failure Fixes #755. Adds interactive pauses and graphical popups (where available) to installer scripts when critical errors occur, ensuring troubleshooting guides are readable. Also clarifies 'build from source' instructions. --- internal/api/router.go | 19 ++- scripts/install-docker-agent-v2.sh | 140 +++++++++++++++++ scripts/install-docker-agent.sh | 152 ++++++++++++++++++- scripts/install-host-agent.ps1 | 12 ++ scripts/install-host-agent.sh | 7 + scripts/install.ps1 | 91 +++++++++++ scripts/install.sh | 235 +++++++++++++++++++++++++++++ scripts/lib/common.sh | 10 ++ 8 files changed, 662 insertions(+), 4 deletions(-) create mode 100644 scripts/install.ps1 create mode 100644 scripts/install.sh diff --git a/internal/api/router.go b/internal/api/router.go index b48833d75..c8c61bf08 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -1004,6 +1004,11 @@ func (r *Router) setupRoutes() { r.mux.HandleFunc("/download/pulse-host-agent", r.handleDownloadHostAgent) r.mux.HandleFunc("/download/pulse-host-agent.sha256", r.handleDownloadHostAgent) + // Unified Agent endpoints + r.mux.HandleFunc("/install.sh", r.handleDownloadUnifiedInstallScript) + r.mux.HandleFunc("/install.ps1", r.handleDownloadUnifiedInstallScriptPS) + r.mux.HandleFunc("/download/pulse-agent", r.handleDownloadUnifiedAgent) + r.mux.HandleFunc("/api/agent/version", r.handleAgentVersion) r.mux.HandleFunc("/api/server/info", r.handleServerInfo) @@ -1339,6 +1344,9 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { "/uninstall-host-agent.sh", // Host agent uninstall script must be public "/uninstall-host-agent.ps1", // Host agent uninstall script must be public "/download/pulse-host-agent", // Host agent binary download should not require auth + "/install.sh", // Unified agent installer + "/install.ps1", // Unified agent Windows installer + "/download/pulse-agent", // Unified agent binary "/api/agent/version", // Agent update checks need to work before auth "/api/server/info", // Server info for installer script "/api/install/install-sensor-proxy.sh", // Temperature proxy installer fallback @@ -1364,7 +1372,10 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { req.URL.Path != "/install-container-agent.sh" && req.URL.Path != "/install-host-agent.sh" && req.URL.Path != "/install-host-agent.ps1" && - req.URL.Path != "/uninstall-host-agent.ps1" + req.URL.Path != "/install-host-agent.ps1" && + req.URL.Path != "/uninstall-host-agent.ps1" && + req.URL.Path != "/install.sh" && + req.URL.Path != "/install.ps1" isStaticAsset := strings.HasPrefix(req.URL.Path, "/assets/") || strings.HasPrefix(req.URL.Path, "/@vite/") || @@ -1508,7 +1519,9 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { path.Clean(req.URL.Path) == "/install-host-agent.sh" || path.Clean(req.URL.Path) == "/install-host-agent.ps1" || path.Clean(req.URL.Path) == "/uninstall-host-agent.sh" || - path.Clean(req.URL.Path) == "/uninstall-host-agent.ps1" { + path.Clean(req.URL.Path) == "/uninstall-host-agent.ps1" || + path.Clean(req.URL.Path) == "/install.sh" || + path.Clean(req.URL.Path) == "/install.ps1" { // Use the mux for API and special routes r.mux.ServeHTTP(w, req) } else { @@ -3522,7 +3535,7 @@ func (r *Router) handleDownloadHostAgent(w http.ResponseWriter, req *http.Reques errorMsg.WriteString(fmt.Sprintf("Host agent binary not found for %s/%s\n\n", platformParam, archParam)) errorMsg.WriteString("Troubleshooting:\n") errorMsg.WriteString("1. If running in Docker: Rebuild the Docker image to include all platform binaries\n") - errorMsg.WriteString("2. If running bare metal: Run 'scripts/build-release.sh' to build all platform binaries\n") + errorMsg.WriteString("2. If running from source: Run 'scripts/build-release.sh' to build all platform binaries\n") errorMsg.WriteString("3. Build from source:\n") errorMsg.WriteString(fmt.Sprintf(" GOOS=%s GOARCH=%s go build -o pulse-host-agent-%s-%s ./cmd/pulse-host-agent\n", platformParam, archParam, platformParam, archParam)) errorMsg.WriteString(fmt.Sprintf(" sudo mv pulse-host-agent-%s-%s /opt/pulse/bin/\n\n", platformParam, archParam)) diff --git a/scripts/install-docker-agent-v2.sh b/scripts/install-docker-agent-v2.sh index a159cedf9..0f7914c38 100755 --- a/scripts/install-docker-agent-v2.sh +++ b/scripts/install-docker-agent-v2.sh @@ -546,6 +546,24 @@ if [ "$UNINSTALL" = true ]; then echo "✓ Unraid startup script removed" fi + # Remove Synology Upstart config + if [ -f "/etc/init/pulse-docker-agent.conf" ]; then + initctl stop pulse-docker-agent 2>/dev/null || true + rm -f "/etc/init/pulse-docker-agent.conf" + echo "✓ Synology Upstart config removed" + fi + + # Stop and remove launchd service (macOS) + if [[ "$(uname -s)" == "Darwin" ]]; then + LAUNCHD_PLIST="/Library/LaunchDaemons/com.pulse.docker-agent.plist" + if [[ -f "$LAUNCHD_PLIST" ]]; then + echo "Stopping launchd service..." + launchctl unload "$LAUNCHD_PLIST" 2>/dev/null || true + rm -f "$LAUNCHD_PLIST" + echo "✓ Launchd service removed" + fi + fi + # Remove log file if [ -f "$LOG_PATH" ]; then rm -f "$LOG_PATH" @@ -774,6 +792,15 @@ if download_agent_binary "$DOWNLOAD_URL" "$DOWNLOAD_URL_BASE"; then else log_warn 'Failed to download agent binary' log_warn "Ensure the Pulse server is reachable at $PRIMARY_URL" + + if common::is_interactive; then + echo "" + if [[ -t 0 ]]; then + read -p "Press Enter to exit..." + elif [[ -e /dev/tty ]]; then + read -p "Press Enter to exit..." < /dev/tty + fi + fi exit 1 fi @@ -942,6 +969,119 @@ EOF exit 0 fi + # Check if this is Synology DSM (has /usr/syno/etc/rc.sysv) + if [ -d /usr/syno/etc/rc.sysv ]; then + log_info 'Detected Synology DSM environment' + + UPSTART_CONF="/etc/init/pulse-docker-agent.conf" + + cat > "$UPSTART_CONF" </dev/null || true + initctl start pulse-docker-agent + + log_header 'Installation complete' + log_info 'Agent service enabled and started via Upstart' + log_info 'Check status : initctl status pulse-docker-agent' + log_info 'Follow logs : tail -f /var/log/upstart/pulse-docker-agent.log' + log_info 'Host visible in Pulse : ~30 seconds' + exit 0 + fi + + # Check if this is macOS (Darwin) + if [[ "$(uname -s)" == "Darwin" ]]; then + log_info 'Detected macOS environment' + + LAUNCHD_PLIST="/Library/LaunchDaemons/com.pulse.docker-agent.plist" + + # Construct environment variables dict + ENV_DICT="" + if [[ -n "$PRIMARY_URL" ]]; then + ENV_DICT="$ENV_DICT + PULSE_URL + $PRIMARY_URL" + fi + if [[ -n "$PRIMARY_TOKEN" ]]; then + ENV_DICT="$ENV_DICT + PULSE_TOKEN + $PRIMARY_TOKEN" + fi + if [[ -n "$JOINED_TARGETS" ]]; then + ENV_DICT="$ENV_DICT + PULSE_TARGETS + $JOINED_TARGETS" + fi + if [[ -n "$PRIMARY_INSECURE" ]]; then + ENV_DICT="$ENV_DICT + PULSE_INSECURE_SKIP_VERIFY + $PRIMARY_INSECURE" + fi + + cat > "$LAUNCHD_PLIST" < + + + + Label + com.pulse.docker-agent + ProgramArguments + + $AGENT_PATH + --url + $PRIMARY_URL + --interval + $INTERVAL + --no-auto-update + + EnvironmentVariables + $ENV_DICT + + RunAtLoad + + KeepAlive + + StandardOutPath + /var/log/pulse-docker-agent.log + StandardErrorPath + /var/log/pulse-docker-agent.log + + +EOF + + chmod 644 "$LAUNCHD_PLIST" + log_success "Created launchd plist: $LAUNCHD_PLIST" + + log_info 'Starting service' + launchctl unload "$LAUNCHD_PLIST" 2>/dev/null || true + launchctl load -w "$LAUNCHD_PLIST" + + log_header 'Installation complete' + log_info 'Agent service enabled and started via launchd' + log_info 'Check status : sudo launchctl list com.pulse.docker-agent' + log_info 'Follow logs : tail -f /var/log/pulse-docker-agent.log' + log_info 'Host visible in Pulse : ~30 seconds' + exit 0 + fi + log_info 'Manual startup environment detected' log_info "Binary location : $AGENT_PATH" log_info 'Start manually with :' diff --git a/scripts/install-docker-agent.sh b/scripts/install-docker-agent.sh index bebd1b002..7bdb6e46d 100755 --- a/scripts/install-docker-agent.sh +++ b/scripts/install-docker-agent.sh @@ -1,5 +1,5 @@ # Generated file. Do not edit. -# Bundled on: 2025-11-25T09:29:44Z +# Bundled on: 2025-11-25T11:15:48Z # Manifest: scripts/bundle.manifest # === Begin: scripts/lib/common.sh === @@ -111,6 +111,16 @@ common::fail() { done local message="${message_parts[*]}" common::log_error "${message}" + + if common::is_interactive; then + echo "" + if [[ -t 0 ]]; then + read -p "Press Enter to exit..." + elif [[ -e /dev/tty ]]; then + read -p "Press Enter to exit..." < /dev/tty + fi + fi + exit "${exit_code}" } @@ -1509,6 +1519,24 @@ if [ "$UNINSTALL" = true ]; then echo "✓ Unraid startup script removed" fi + # Remove Synology Upstart config + if [ -f "/etc/init/pulse-docker-agent.conf" ]; then + initctl stop pulse-docker-agent 2>/dev/null || true + rm -f "/etc/init/pulse-docker-agent.conf" + echo "✓ Synology Upstart config removed" + fi + + # Stop and remove launchd service (macOS) + if [[ "$(uname -s)" == "Darwin" ]]; then + LAUNCHD_PLIST="/Library/LaunchDaemons/com.pulse.docker-agent.plist" + if [[ -f "$LAUNCHD_PLIST" ]]; then + echo "Stopping launchd service..." + launchctl unload "$LAUNCHD_PLIST" 2>/dev/null || true + rm -f "$LAUNCHD_PLIST" + echo "✓ Launchd service removed" + fi + fi + # Remove log file if [ -f "$LOG_PATH" ]; then rm -f "$LOG_PATH" @@ -1737,6 +1765,15 @@ if download_agent_binary "$DOWNLOAD_URL" "$DOWNLOAD_URL_BASE"; then else log_warn 'Failed to download agent binary' log_warn "Ensure the Pulse server is reachable at $PRIMARY_URL" + + if common::is_interactive; then + echo "" + if [[ -t 0 ]]; then + read -p "Press Enter to exit..." + elif [[ -e /dev/tty ]]; then + read -p "Press Enter to exit..." < /dev/tty + fi + fi exit 1 fi @@ -1905,6 +1942,119 @@ EOF exit 0 fi + # Check if this is Synology DSM (has /usr/syno/etc/rc.sysv) + if [ -d /usr/syno/etc/rc.sysv ]; then + log_info 'Detected Synology DSM environment' + + UPSTART_CONF="/etc/init/pulse-docker-agent.conf" + + cat > "$UPSTART_CONF" </dev/null || true + initctl start pulse-docker-agent + + log_header 'Installation complete' + log_info 'Agent service enabled and started via Upstart' + log_info 'Check status : initctl status pulse-docker-agent' + log_info 'Follow logs : tail -f /var/log/upstart/pulse-docker-agent.log' + log_info 'Host visible in Pulse : ~30 seconds' + exit 0 + fi + + # Check if this is macOS (Darwin) + if [[ "$(uname -s)" == "Darwin" ]]; then + log_info 'Detected macOS environment' + + LAUNCHD_PLIST="/Library/LaunchDaemons/com.pulse.docker-agent.plist" + + # Construct environment variables dict + ENV_DICT="" + if [[ -n "$PRIMARY_URL" ]]; then + ENV_DICT="$ENV_DICT + PULSE_URL + $PRIMARY_URL" + fi + if [[ -n "$PRIMARY_TOKEN" ]]; then + ENV_DICT="$ENV_DICT + PULSE_TOKEN + $PRIMARY_TOKEN" + fi + if [[ -n "$JOINED_TARGETS" ]]; then + ENV_DICT="$ENV_DICT + PULSE_TARGETS + $JOINED_TARGETS" + fi + if [[ -n "$PRIMARY_INSECURE" ]]; then + ENV_DICT="$ENV_DICT + PULSE_INSECURE_SKIP_VERIFY + $PRIMARY_INSECURE" + fi + + cat > "$LAUNCHD_PLIST" < + + + + Label + com.pulse.docker-agent + ProgramArguments + + $AGENT_PATH + --url + $PRIMARY_URL + --interval + $INTERVAL + --no-auto-update + + EnvironmentVariables + $ENV_DICT + + RunAtLoad + + KeepAlive + + StandardOutPath + /var/log/pulse-docker-agent.log + StandardErrorPath + /var/log/pulse-docker-agent.log + + +EOF + + chmod 644 "$LAUNCHD_PLIST" + log_success "Created launchd plist: $LAUNCHD_PLIST" + + log_info 'Starting service' + launchctl unload "$LAUNCHD_PLIST" 2>/dev/null || true + launchctl load -w "$LAUNCHD_PLIST" + + log_header 'Installation complete' + log_info 'Agent service enabled and started via launchd' + log_info 'Check status : sudo launchctl list com.pulse.docker-agent' + log_info 'Follow logs : tail -f /var/log/pulse-docker-agent.log' + log_info 'Host visible in Pulse : ~30 seconds' + exit 0 + fi + log_info 'Manual startup environment detected' log_info "Binary location : $AGENT_PATH" log_info 'Start manually with :' diff --git a/scripts/install-host-agent.ps1 b/scripts/install-host-agent.ps1 index 684f8813c..8160cddf7 100644 --- a/scripts/install-host-agent.ps1 +++ b/scripts/install-host-agent.ps1 @@ -391,6 +391,18 @@ try { $manualCommand = "& `"$agentPath`" $($agentArgs -join ' ')" } catch { PulseError "Failed to download agent: $_" + + # Try to show a popup if running in a GUI environment + try { + Add-Type -AssemblyName System.Windows.Forms + [System.Windows.Forms.MessageBox]::Show("Failed to download agent.`n`n$_", "Pulse Installation Failed", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error) | Out-Null + } catch { + # Ignore if GUI not available + } + + Write-Host "" + Write-Host "Press Enter to exit..." -ForegroundColor Yellow + Read-Host exit 1 } diff --git a/scripts/install-host-agent.sh b/scripts/install-host-agent.sh index f0999cbae..76cbee9a9 100755 --- a/scripts/install-host-agent.sh +++ b/scripts/install-host-agent.sh @@ -422,6 +422,13 @@ if [[ "$DOWNLOAD_SUCCESS" == false ]]; then echo "4. Check firewall/network settings blocking the connection" echo "" rm -f "$TEMP_BINARY" + + echo "" + if [[ -t 0 ]]; then + read -p "Press Enter to exit..." + elif [[ -e /dev/tty ]]; then + read -p "Press Enter to exit..." < /dev/tty + fi exit 1 fi diff --git a/scripts/install.ps1 b/scripts/install.ps1 new file mode 100644 index 000000000..1270cb6ed --- /dev/null +++ b/scripts/install.ps1 @@ -0,0 +1,91 @@ +# Pulse Unified Agent Installer (Windows) +# Usage: +# irm http://pulse/install.ps1 | iex +# $env:PULSE_URL="..."; $env:PULSE_TOKEN="..."; irm ... | iex + +param ( + [string]$Url = $env:PULSE_URL, + [string]$Token = $env:PULSE_TOKEN, + [string]$Interval = "30s", + [bool]$EnableHost = $true, + [bool]$EnableDocker = $false, + [bool]$Insecure = $false, + [bool]$Uninstall = $false +) + +$ErrorActionPreference = "Stop" +$AgentName = "PulseAgent" +$BinaryName = "pulse-agent.exe" +$InstallDir = "C:\Program Files\Pulse" +$LogFile = "$env:ProgramData\Pulse\pulse-agent.log" + +# --- Uninstall Logic --- +if ($Uninstall) { + Write-Host "Uninstalling $AgentName..." -ForegroundColor Cyan + + if (Get-Service $AgentName -ErrorAction SilentlyContinue) { + Stop-Service $AgentName -Force -ErrorAction SilentlyContinue + sc.exe delete $AgentName | Out-Null + } + + Remove-Item "$InstallDir\$BinaryName" -Force -ErrorAction SilentlyContinue + Write-Host "Uninstallation complete." -ForegroundColor Green + Exit 0 +} + +# --- Validation --- +if ([string]::IsNullOrWhiteSpace($Url) -or [string]::IsNullOrWhiteSpace($Token)) { + Write-Error "Missing required parameters: Url and Token. Set PULSE_URL/PULSE_TOKEN env vars or pass arguments." + Exit 1 +} + +# --- Download --- +$DownloadUrl = "$Url/download/pulse-agent?os=windows&arch=amd64" +Write-Host "Downloading agent from $DownloadUrl..." -ForegroundColor Cyan + +if (-not (Test-Path $InstallDir)) { + New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null +} + +$DestPath = "$InstallDir\$BinaryName" +try { + Invoke-WebRequest -Uri $DownloadUrl -OutFile $DestPath -UseBasicParsing +} catch { + Write-Host "Failed to download agent: $_" -ForegroundColor Red + + # Try to show a popup if running in a GUI environment + try { + Add-Type -AssemblyName System.Windows.Forms + [System.Windows.Forms.MessageBox]::Show("Failed to download agent.`n`n$_", "Pulse Installation Failed", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error) | Out-Null + } catch { + # Ignore if GUI not available + } + + Write-Host "" + Write-Host "Press Enter to exit..." -ForegroundColor Yellow + Read-Host + Exit 1 +} + +# --- Service Installation --- +Write-Host "Configuring Windows Service..." -ForegroundColor Cyan + +if (Get-Service $AgentName -ErrorAction SilentlyContinue) { + Stop-Service $AgentName -Force -ErrorAction SilentlyContinue + sc.exe delete $AgentName | Out-Null + Start-Sleep -Seconds 2 +} + +$Args = "--url `"$Url`" --token `"$Token`" --interval `"$Interval`" --enable-host=$EnableHost --enable-docker=$EnableDocker --insecure=$Insecure" +$BinPath = "`"$DestPath`" $Args" + +# Create Service +sc.exe create $AgentName binPath= $BinPath start= auto displayname= "Pulse Unified Agent" | Out-Null +sc.exe description $AgentName "Pulse Unified Agent for Host and Docker monitoring" | Out-Null +sc.exe failure $AgentName reset= 86400 actions= restart/5000/restart/5000/restart/5000 | Out-Null + +Start-Service $AgentName + +Write-Host "Installation complete." -ForegroundColor Green +Write-Host "Service: $AgentName" +Write-Host "Logs: $LogFile" diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100644 index 000000000..d43b9a6ff --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,235 @@ +#!/usr/bin/env bash +# +# Pulse Unified Agent Installer +# Supports: Linux (systemd), macOS (launchd), Synology (upstart) +# +# Usage: +# curl -fsSL http://pulse/install.sh | bash -s -- --url http://pulse --token [options] +# +# Options: +# --enable-host Enable host metrics (default: true) +# --enable-docker Enable docker metrics (default: false) +# --interval Reporting interval (default: 30s) +# --uninstall Remove the agent + +set -euo pipefail + +# --- Configuration --- +AGENT_NAME="pulse-agent" +BINARY_NAME="pulse-agent" +INSTALL_DIR="/usr/local/bin" +LOG_FILE="/var/log/${AGENT_NAME}.log" + +# Defaults +PULSE_URL="" +PULSE_TOKEN="" +INTERVAL="30s" +ENABLE_HOST="true" +ENABLE_DOCKER="false" +UNINSTALL="false" +INSECURE="false" + +# --- Helper Functions --- +log_info() { printf "[INFO] %s\n" "$1"; } +log_warn() { printf "[WARN] %s\n" "$1"; } +log_error() { printf "[ERROR] %s\n" "$1"; } +fail() { + log_error "$1" + if [[ -t 0 ]]; then + read -p "Press Enter to exit..." + elif [[ -e /dev/tty ]]; then + read -p "Press Enter to exit..." < /dev/tty + fi + exit 1 +} + +# --- Parse Arguments --- +while [[ $# -gt 0 ]]; do + case $1 in + --url) PULSE_URL="$2"; shift 2 ;; + --token) PULSE_TOKEN="$2"; shift 2 ;; + --interval) INTERVAL="$2"; shift 2 ;; + --enable-host) ENABLE_HOST="true"; shift ;; + --disable-host) ENABLE_HOST="false"; shift ;; + --enable-docker) ENABLE_DOCKER="true"; shift ;; + --disable-docker) ENABLE_DOCKER="false"; shift ;; + --insecure) INSECURE="true"; shift ;; + --uninstall) UNINSTALL="true"; shift ;; + *) fail "Unknown argument: $1" ;; + esac +done + +# --- Uninstall Logic --- +if [[ "$UNINSTALL" == "true" ]]; then + log_info "Uninstalling ${AGENT_NAME}..." + + # Systemd + if command -v systemctl >/dev/null 2>&1; then + systemctl stop "${AGENT_NAME}" 2>/dev/null || true + systemctl disable "${AGENT_NAME}" 2>/dev/null || true + rm -f "/etc/systemd/system/${AGENT_NAME}.service" + systemctl daemon-reload 2>/dev/null || true + fi + + # Launchd (macOS) + if [[ "$(uname -s)" == "Darwin" ]]; then + PLIST="/Library/LaunchDaemons/com.pulse.agent.plist" + launchctl unload "$PLIST" 2>/dev/null || true + rm -f "$PLIST" + fi + + # Upstart (Synology) + if [[ -f "/etc/init/${AGENT_NAME}.conf" ]]; then + initctl stop "${AGENT_NAME}" 2>/dev/null || true + rm -f "/etc/init/${AGENT_NAME}.conf" + fi + + rm -f "${INSTALL_DIR}/${BINARY_NAME}" + log_info "Uninstallation complete." + exit 0 +fi + +# --- Validation --- +if [[ -z "$PULSE_URL" || -z "$PULSE_TOKEN" ]]; then + fail "Missing required arguments: --url and --token" +fi + +# --- Download --- +OS=$(uname -s | tr '[:upper:]' '[:lower:]') +ARCH=$(uname -m) + +case "$ARCH" in + x86_64) ARCH="amd64" ;; + aarch64|arm64) ARCH="arm64" ;; + *) fail "Unsupported architecture: $ARCH" ;; +esac + +DOWNLOAD_URL="${PULSE_URL}/download/${BINARY_NAME}?os=${OS}&arch=${ARCH}" +log_info "Downloading agent from ${DOWNLOAD_URL}..." + +# Create temp file +TMP_BIN=$(mktemp) +chmod +x "$TMP_BIN" + +CURL_ARGS="-fsSL" +if [[ "$INSECURE" == "true" ]]; then CURL_ARGS="-k $CURL_ARGS"; fi + +if ! curl $CURL_ARGS -o "$TMP_BIN" "$DOWNLOAD_URL"; then + fail "Download failed. Check URL and connectivity." +fi + +# Install Binary +log_info "Installing binary to ${INSTALL_DIR}/${BINARY_NAME}..." +mkdir -p "$INSTALL_DIR" +mv "$TMP_BIN" "${INSTALL_DIR}/${BINARY_NAME}" + +# --- Service Installation --- + +# 1. macOS (Launchd) +if [[ "$OS" == "darwin" ]]; then + PLIST="/Library/LaunchDaemons/com.pulse.agent.plist" + log_info "Configuring Launchd service at $PLIST..." + + cat > "$PLIST" < + + + + Label + com.pulse.agent + ProgramArguments + + ${INSTALL_DIR}/${BINARY_NAME} + --url + ${PULSE_URL} + --token + ${PULSE_TOKEN} + --interval + ${INTERVAL} + --enable-host=${ENABLE_HOST} + --enable-docker=${ENABLE_DOCKER} + --insecure=${INSECURE} + + RunAtLoad + + KeepAlive + + StandardOutPath + ${LOG_FILE} + StandardErrorPath + ${LOG_FILE} + + +EOF + chmod 644 "$PLIST" + launchctl unload "$PLIST" 2>/dev/null || true + launchctl load -w "$PLIST" + log_info "Service started." + exit 0 +fi + +# 2. Synology (Upstart) +if [[ -d /usr/syno/etc/rc.sysv ]]; then + CONF="/etc/init/${AGENT_NAME}.conf" + log_info "Configuring Upstart service at $CONF..." + + cat > "$CONF" <> ${LOG_FILE} 2>&1 +EOF + initctl stop "${AGENT_NAME}" 2>/dev/null || true + initctl start "${AGENT_NAME}" + log_info "Service started." + exit 0 +fi + +# 3. Linux (Systemd) +if command -v systemctl >/dev/null 2>&1; then + UNIT="/etc/systemd/system/${AGENT_NAME}.service" + log_info "Configuring Systemd service at $UNIT..." + + cat > "$UNIT" <