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.
This commit is contained in:
courtmanr@gmail.com
2025-11-25 11:17:37 +00:00
parent b524d1d79d
commit 3ec7b401a3
8 changed files with 662 additions and 4 deletions

View File

@@ -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))

View File

@@ -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" <<EOF
description "Pulse Docker Agent"
author "Pulse"
start on syno.network.ready
stop on runlevel [06]
respawn
respawn limit 5 10
env PULSE_URL="$PRIMARY_URL"
env PULSE_TOKEN="$PRIMARY_TOKEN"
env PULSE_TARGETS="$JOINED_TARGETS"
env PULSE_INSECURE_SKIP_VERIFY="$PRIMARY_INSECURE"
exec $AGENT_PATH --url "$PRIMARY_URL" --interval "$INTERVAL"$NO_AUTO_UPDATE_FLAG
EOF
log_success "Created Upstart config: $UPSTART_CONF"
log_info 'Starting service'
initctl stop pulse-docker-agent 2>/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
<key>PULSE_URL</key>
<string>$PRIMARY_URL</string>"
fi
if [[ -n "$PRIMARY_TOKEN" ]]; then
ENV_DICT="$ENV_DICT
<key>PULSE_TOKEN</key>
<string>$PRIMARY_TOKEN</string>"
fi
if [[ -n "$JOINED_TARGETS" ]]; then
ENV_DICT="$ENV_DICT
<key>PULSE_TARGETS</key>
<string>$JOINED_TARGETS</string>"
fi
if [[ -n "$PRIMARY_INSECURE" ]]; then
ENV_DICT="$ENV_DICT
<key>PULSE_INSECURE_SKIP_VERIFY</key>
<string>$PRIMARY_INSECURE</string>"
fi
cat > "$LAUNCHD_PLIST" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.pulse.docker-agent</string>
<key>ProgramArguments</key>
<array>
<string>$AGENT_PATH</string>
<string>--url</string>
<string>$PRIMARY_URL</string>
<string>--interval</string>
<string>$INTERVAL</string>
<string>--no-auto-update</string>
</array>
<key>EnvironmentVariables</key>
<dict>$ENV_DICT
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/var/log/pulse-docker-agent.log</string>
<key>StandardErrorPath</key>
<string>/var/log/pulse-docker-agent.log</string>
</dict>
</plist>
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 :'

View File

@@ -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" <<EOF
description "Pulse Docker Agent"
author "Pulse"
start on syno.network.ready
stop on runlevel [06]
respawn
respawn limit 5 10
env PULSE_URL="$PRIMARY_URL"
env PULSE_TOKEN="$PRIMARY_TOKEN"
env PULSE_TARGETS="$JOINED_TARGETS"
env PULSE_INSECURE_SKIP_VERIFY="$PRIMARY_INSECURE"
exec $AGENT_PATH --url "$PRIMARY_URL" --interval "$INTERVAL"$NO_AUTO_UPDATE_FLAG
EOF
log_success "Created Upstart config: $UPSTART_CONF"
log_info 'Starting service'
initctl stop pulse-docker-agent 2>/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
<key>PULSE_URL</key>
<string>$PRIMARY_URL</string>"
fi
if [[ -n "$PRIMARY_TOKEN" ]]; then
ENV_DICT="$ENV_DICT
<key>PULSE_TOKEN</key>
<string>$PRIMARY_TOKEN</string>"
fi
if [[ -n "$JOINED_TARGETS" ]]; then
ENV_DICT="$ENV_DICT
<key>PULSE_TARGETS</key>
<string>$JOINED_TARGETS</string>"
fi
if [[ -n "$PRIMARY_INSECURE" ]]; then
ENV_DICT="$ENV_DICT
<key>PULSE_INSECURE_SKIP_VERIFY</key>
<string>$PRIMARY_INSECURE</string>"
fi
cat > "$LAUNCHD_PLIST" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.pulse.docker-agent</string>
<key>ProgramArguments</key>
<array>
<string>$AGENT_PATH</string>
<string>--url</string>
<string>$PRIMARY_URL</string>
<string>--interval</string>
<string>$INTERVAL</string>
<string>--no-auto-update</string>
</array>
<key>EnvironmentVariables</key>
<dict>$ENV_DICT
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/var/log/pulse-docker-agent.log</string>
<key>StandardErrorPath</key>
<string>/var/log/pulse-docker-agent.log</string>
</dict>
</plist>
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 :'

View File

@@ -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
}

View File

@@ -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

91
scripts/install.ps1 Normal file
View File

@@ -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"

235
scripts/install.sh Normal file
View File

@@ -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 <token> [options]
#
# Options:
# --enable-host Enable host metrics (default: true)
# --enable-docker Enable docker metrics (default: false)
# --interval <dur> 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" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.pulse.agent</string>
<key>ProgramArguments</key>
<array>
<string>${INSTALL_DIR}/${BINARY_NAME}</string>
<string>--url</string>
<string>${PULSE_URL}</string>
<string>--token</string>
<string>${PULSE_TOKEN}</string>
<string>--interval</string>
<string>${INTERVAL}</string>
<string>--enable-host=${ENABLE_HOST}</string>
<string>--enable-docker=${ENABLE_DOCKER}</string>
<string>--insecure=${INSECURE}</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>${LOG_FILE}</string>
<key>StandardErrorPath</key>
<string>${LOG_FILE}</string>
</dict>
</plist>
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" <<EOF
description "Pulse Unified Agent"
author "Pulse"
start on syno.network.ready
stop on runlevel [06]
respawn
respawn limit 5 10
exec ${INSTALL_DIR}/${BINARY_NAME} \
--url "${PULSE_URL}" \
--token "${PULSE_TOKEN}" \
--interval "${INTERVAL}" \
--enable-host=${ENABLE_HOST} \
--enable-docker=${ENABLE_DOCKER} \
--insecure=${INSECURE} \
>> ${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" <<EOF
[Unit]
Description=Pulse Unified Agent
After=network-online.target docker.service
Wants=network-online.target
[Service]
Type=simple
ExecStart=${INSTALL_DIR}/${BINARY_NAME} \
--url "${PULSE_URL}" \
--token "${PULSE_TOKEN}" \
--interval "${INTERVAL}" \
--enable-host=${ENABLE_HOST} \
--enable-docker=${ENABLE_DOCKER} \
--insecure=${INSECURE}
Restart=always
RestartSec=5s
User=root
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable "${AGENT_NAME}"
systemctl restart "${AGENT_NAME}"
log_info "Service started."
exit 0
fi
fail "Could not detect a supported service manager (systemd, launchd, upstart)."

View File

@@ -106,6 +106,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}"
}