Pulse/internal/api/unified_agent.go

110 lines
3.6 KiB
Go

package api
import (
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/rs/zerolog/log"
)
// handleDownloadUnifiedInstallScript serves the universal install.sh script
func (r *Router) handleDownloadUnifiedInstallScript(w http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodGet && req.Method != http.MethodHead {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
scriptPath := filepath.Join(r.config.AppRoot, "scripts", "install.sh")
// Check if file exists
if _, err := os.Stat(scriptPath); os.IsNotExist(err) {
log.Error().Str("path", scriptPath).Msg("Unified install script not found")
http.Error(w, "Install script not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "text/x-shellscript")
w.Header().Set("Content-Disposition", "inline; filename=\"install.sh\"")
http.ServeFile(w, req, scriptPath)
}
// handleDownloadUnifiedInstallScriptPS serves the universal install.ps1 script
func (r *Router) handleDownloadUnifiedInstallScriptPS(w http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodGet && req.Method != http.MethodHead {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
scriptPath := filepath.Join(r.config.AppRoot, "scripts", "install.ps1")
// Check if file exists
if _, err := os.Stat(scriptPath); os.IsNotExist(err) {
log.Error().Str("path", scriptPath).Msg("Unified PowerShell install script not found")
http.Error(w, "Install script not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "text/plain")
w.Header().Set("Content-Disposition", "inline; filename=\"install.ps1\"")
http.ServeFile(w, req, scriptPath)
}
// handleDownloadUnifiedAgent serves the pulse-agent binary
func (r *Router) handleDownloadUnifiedAgent(w http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodGet && req.Method != http.MethodHead {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// For now, we only have the locally built binary.
// In production, this would look up the correct binary for the requested OS/Arch.
// Query params: ?os=linux&arch=amd64
osName := req.URL.Query().Get("os")
arch := req.URL.Query().Get("arch")
if osName == "" {
osName = "linux" // Default
}
if arch == "" {
arch = "amd64" // Default
}
// Normalize OS
osName = strings.ToLower(osName)
if osName == "darwin" {
osName = "macos"
}
// In dev mode, we just serve the binary we built in the root
// In prod, we'd look in a dist folder
binaryName := "pulse-agent"
if osName == "windows" {
binaryName = "pulse-agent.exe"
}
// Try to find the binary
// 1. Check root (dev)
binaryPath := filepath.Join(r.config.AppRoot, binaryName)
if _, err := os.Stat(binaryPath); os.IsNotExist(err) {
// 2. Check dist folder (prod/build)
binaryPath = filepath.Join(r.config.AppRoot, "dist", fmt.Sprintf("%s-%s", osName, arch), binaryName)
if _, err := os.Stat(binaryPath); os.IsNotExist(err) {
// Fallback for dev: just serve the root binary regardless of requested OS/Arch
// This allows testing the flow even if cross-compilation hasn't happened
binaryPath = filepath.Join(r.config.AppRoot, "pulse-agent")
if _, err := os.Stat(binaryPath); os.IsNotExist(err) {
log.Error().Str("path", binaryPath).Msg("Unified agent binary not found")
http.Error(w, "Agent binary not found", http.StatusNotFound)
return
}
}
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", binaryName))
http.ServeFile(w, req, binaryPath)
}