From fdf0977be26f8858874955d35979feea8aac16ce Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 5 Nov 2025 17:38:17 +0000 Subject: [PATCH] Add host agent multi-platform binary distribution and improve host details UI - Build host agent binaries for all platforms (linux/darwin/windows, amd64/arm64/armv7) in Docker - Add Makefile target for building agent binaries locally - Add startup validation to check for missing agent binaries - Improve download endpoint error messages with troubleshooting guidance - Enhance host details drawer layout with better organization and visual hierarchy - Update base images to rolling versions (node:20-alpine, golang:1.24-alpine, alpine:3.20) --- Dockerfile | 49 ++++++++++-- Makefile | 19 ++++- cmd/pulse/main.go | 46 +++++++++++ .../src/components/Hosts/HostsOverview.tsx | 78 +++++++++---------- internal/api/router.go | 16 +++- 5 files changed, 160 insertions(+), 48 deletions(-) diff --git a/Dockerfile b/Dockerfile index 68e0c5eaf..11ac0ce6f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ ARG BUILD_AGENT=1 # Build stage for frontend (must be built first for embedding) -FROM node:20.16.0-alpine3.19 AS frontend-builder +FROM node:20-alpine AS frontend-builder WORKDIR /app/frontend-modern @@ -19,7 +19,7 @@ RUN --mount=type=cache,id=pulse-npm-cache,target=/root/.npm \ npm run build # Build stage for Go backend -FROM golang:1.24.9-alpine3.19 AS backend-builder +FROM golang:1.24-alpine AS backend-builder ARG BUILD_AGENT WORKDIR /app @@ -77,6 +77,34 @@ RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \ fi && \ cp pulse-docker-agent-linux-amd64 pulse-docker-agent +# Build host-agent binaries for all platforms (for download endpoint) +RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \ + --mount=type=cache,id=pulse-go-build,target=/root/.cache/go-build \ + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ + -ldflags="-s -w" \ + -trimpath \ + -o pulse-host-agent-linux-amd64 ./cmd/pulse-host-agent && \ + CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build \ + -ldflags="-s -w" \ + -trimpath \ + -o pulse-host-agent-linux-arm64 ./cmd/pulse-host-agent && \ + CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build \ + -ldflags="-s -w" \ + -trimpath \ + -o pulse-host-agent-linux-armv7 ./cmd/pulse-host-agent && \ + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build \ + -ldflags="-s -w" \ + -trimpath \ + -o pulse-host-agent-darwin-amd64 ./cmd/pulse-host-agent && \ + CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build \ + -ldflags="-s -w" \ + -trimpath \ + -o pulse-host-agent-darwin-arm64 ./cmd/pulse-host-agent && \ + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build \ + -ldflags="-s -w" \ + -trimpath \ + -o pulse-host-agent-windows-amd64.exe ./cmd/pulse-host-agent + # Build pulse-sensor-proxy RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \ --mount=type=cache,id=pulse-go-build,target=/root/.cache/go-build \ @@ -86,7 +114,7 @@ RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \ -o pulse-sensor-proxy ./cmd/pulse-sensor-proxy # Runtime image for the Docker agent (offered via --target agent_runtime) -FROM alpine:3.19.1 AS agent_runtime +FROM alpine:3.20 AS agent_runtime # Use TARGETARCH to select the correct binary for the build platform ARG TARGETARCH @@ -118,7 +146,7 @@ ENV PULSE_NO_AUTO_UPDATE=true ENTRYPOINT ["/usr/local/bin/pulse-docker-agent"] # Final stage (Pulse server runtime) -FROM alpine:3.19.1 +FROM alpine:3.20 RUN apk --no-cache add ca-certificates tzdata su-exec openssh-client @@ -138,9 +166,10 @@ RUN chmod +x /docker-entrypoint.sh # Provide installer scripts for HTTP download endpoints RUN mkdir -p /opt/pulse/scripts COPY scripts/install-docker-agent.sh /opt/pulse/scripts/install-docker-agent.sh +COPY scripts/install-host-agent.sh /opt/pulse/scripts/install-host-agent.sh COPY scripts/install-sensor-proxy.sh /opt/pulse/scripts/install-sensor-proxy.sh COPY scripts/install-docker.sh /opt/pulse/scripts/install-docker.sh -RUN chmod 755 /opt/pulse/scripts/install-docker-agent.sh /opt/pulse/scripts/install-sensor-proxy.sh /opt/pulse/scripts/install-docker.sh +RUN chmod 755 /opt/pulse/scripts/install-docker-agent.sh /opt/pulse/scripts/install-host-agent.sh /opt/pulse/scripts/install-sensor-proxy.sh /opt/pulse/scripts/install-docker.sh # Copy multi-arch docker-agent binaries for download endpoint RUN mkdir -p /opt/pulse/bin @@ -149,6 +178,16 @@ COPY --from=backend-builder /app/pulse-docker-agent-linux-arm64 /opt/pulse/bin/ COPY --from=backend-builder /app/pulse-docker-agent-linux-armv7 /opt/pulse/bin/ COPY --from=backend-builder /app/pulse-docker-agent /opt/pulse/bin/pulse-docker-agent +# Copy multi-arch host-agent binaries for download endpoint +COPY --from=backend-builder /app/pulse-host-agent-linux-amd64 /opt/pulse/bin/ +COPY --from=backend-builder /app/pulse-host-agent-linux-arm64 /opt/pulse/bin/ +COPY --from=backend-builder /app/pulse-host-agent-linux-armv7 /opt/pulse/bin/ +COPY --from=backend-builder /app/pulse-host-agent-darwin-amd64 /opt/pulse/bin/ +COPY --from=backend-builder /app/pulse-host-agent-darwin-arm64 /opt/pulse/bin/ +COPY --from=backend-builder /app/pulse-host-agent-windows-amd64.exe /opt/pulse/bin/ +# Create symlink for Windows without .exe extension +RUN ln -s pulse-host-agent-windows-amd64.exe /opt/pulse/bin/pulse-host-agent-windows-amd64 + # Copy pulse-sensor-proxy binary for download endpoint COPY --from=backend-builder /app/pulse-sensor-proxy /opt/pulse/bin/pulse-sensor-proxy diff --git a/Makefile b/Makefile index 365d91f8f..515b78984 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,13 @@ # Pulse Makefile for development -.PHONY: build run dev frontend backend all clean distclean dev-hot lint lint-backend lint-frontend format format-backend format-frontend +.PHONY: build run dev frontend backend all clean distclean dev-hot lint lint-backend lint-frontend format format-backend format-frontend build-agents FRONTEND_DIR := frontend-modern FRONTEND_DIST := $(FRONTEND_DIR)/dist FRONTEND_EMBED_DIR := internal/api/frontend-modern -# Build everything -all: frontend backend +# Build everything (including all agent binaries) +all: frontend backend build-agents # Build frontend only frontend: @@ -68,3 +68,16 @@ format-backend: format-frontend: npm --prefix $(FRONTEND_DIR) run format + +# Build all host agent binaries for all platforms +build-agents: + @echo "Building host agent binaries for all platforms..." + @mkdir -p bin + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o bin/pulse-host-agent-linux-amd64 ./cmd/pulse-host-agent + CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -trimpath -o bin/pulse-host-agent-linux-arm64 ./cmd/pulse-host-agent + CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="-s -w" -trimpath -o bin/pulse-host-agent-linux-armv7 ./cmd/pulse-host-agent + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o bin/pulse-host-agent-darwin-amd64 ./cmd/pulse-host-agent + CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -trimpath -o bin/pulse-host-agent-darwin-arm64 ./cmd/pulse-host-agent + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o bin/pulse-host-agent-windows-amd64.exe ./cmd/pulse-host-agent + @ln -sf pulse-host-agent-windows-amd64.exe bin/pulse-host-agent-windows-amd64 + @echo "✓ All host agent binaries built in bin/" diff --git a/cmd/pulse/main.go b/cmd/pulse/main.go index 42931d58d..9c66ea222 100644 --- a/cmd/pulse/main.go +++ b/cmd/pulse/main.go @@ -99,6 +99,9 @@ func runServer() { log.Info().Msg("Starting Pulse monitoring server") + // Validate agent binaries are available for download + validateAgentBinaries() + // Create context that cancels on interrupt ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -345,3 +348,46 @@ func performAutoImport() error { log.Info().Msg("Configuration auto-imported successfully") return nil } + +// validateAgentBinaries checks if agent binaries are available for download +// and logs warnings if any are missing +func validateAgentBinaries() { + binDirs := []string{"/opt/pulse/bin", "./bin", "."} + platforms := []struct { + name string + file string + }{ + {"linux-amd64", "pulse-host-agent-linux-amd64"}, + {"linux-arm64", "pulse-host-agent-linux-arm64"}, + {"linux-armv7", "pulse-host-agent-linux-armv7"}, + {"darwin-amd64", "pulse-host-agent-darwin-amd64"}, + {"darwin-arm64", "pulse-host-agent-darwin-arm64"}, + {"windows-amd64", "pulse-host-agent-windows-amd64"}, + } + + missing := []string{} + searchedPaths := []string{} + + for _, platform := range platforms { + found := false + for _, dir := range binDirs { + path := filepath.Join(dir, platform.file) + searchedPaths = append(searchedPaths, path) + if _, err := os.Stat(path); err == nil { + found = true + break + } + } + if !found { + missing = append(missing, platform.name) + } + } + + if len(missing) > 0 { + log.Warn(). + Strs("missing_platforms", missing). + Msg("Host agent binaries missing - install script downloads will fail. Rebuild Docker image or run build-release.sh to generate all platform binaries.") + } else { + log.Info().Msg("All host agent binaries available for download") + } +} diff --git a/frontend-modern/src/components/Hosts/HostsOverview.tsx b/frontend-modern/src/components/Hosts/HostsOverview.tsx index e6e1dbae4..ee18ee23e 100644 --- a/frontend-modern/src/components/Hosts/HostsOverview.tsx +++ b/frontend-modern/src/components/Hosts/HostsOverview.tsx @@ -333,41 +333,41 @@ export const HostsOverview: Component = (props) => { {/* Drawer - Additional Info */} - - -
+ + +
{/* System Info */} -
-
System
-
+
+
System
+
-
- CPUs: - {host.cpuCount} +
+ CPUs + {host.cpuCount}
0}> -
- Load Avg: - {host.loadAverage!.map(l => l.toFixed(2)).join(', ')} +
+ Load Avg + {host.loadAverage!.map(l => l.toFixed(2)).join(', ')}
-
- Arch: - {host.architecture} +
+ Arch + {host.architecture}
-
- Kernel: - {host.kernelVersion} +
+ Kernel + {host.kernelVersion}
-
- Agent: - {host.agentVersion} +
+ Agent + {host.agentVersion}
@@ -375,15 +375,15 @@ export const HostsOverview: Component = (props) => { {/* Network Interfaces */} 0}> -
-
Network
-
+
+
Network
+
{(iface) => ( -
-
{iface.name}
+
+
{iface.name}
0}> -
+
{(addr) => ( @@ -402,22 +402,22 @@ export const HostsOverview: Component = (props) => { {/* Disk Info */} 0}> -
-
Disks
-
+
+
Disks
+
{(disk) => { const diskPercent = () => disk.usage ?? 0; return ( -
+
- {disk.mountpoint || disk.device} + {disk.mountpoint || disk.device} {formatBytes(disk.used ?? 0, 0)} / {formatBytes(disk.total ?? 0, 0)}
0}> -
+
= (props) => { {/* Temperature Sensors */} 0}> -
-
Temperatures
-
+
+
Temperatures
+
{([name, temp]) => ( -
- {name} - 80 ? 'text-red-600 dark:text-red-400 font-semibold' : ''}> +
+ {name} + 80 ? 'text-red-600 dark:text-red-400 font-semibold' : 'text-gray-600 dark:text-gray-300'}`}> {temp.toFixed(1)}°C
diff --git a/internal/api/router.go b/internal/api/router.go index bc46e2223..d18390292 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -3302,7 +3302,21 @@ func (r *Router) handleDownloadHostAgent(w http.ResponseWriter, req *http.Reques } } - http.Error(w, "Host agent binary not found. Please build from source: go build ./cmd/pulse-host-agent", http.StatusNotFound) + // Build detailed error message with troubleshooting guidance + var errorMsg strings.Builder + 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("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)) + errorMsg.WriteString("Searched locations:\n") + for _, path := range searchPaths { + errorMsg.WriteString(fmt.Sprintf(" - %s\n", path)) + } + + http.Error(w, errorMsg.String(), http.StatusNotFound) } // serveChecksum computes and serves the SHA256 checksum of a file