Files
Redflag/aggregator-server/internal/api/handlers/downloads.go
Fimeg 3f0838affc refactor: replace 899 lines of script generation with templates
Created InstallTemplateService with clean template-based script generation.
Added linux.sh.tmpl and windows.ps1.tmpl for install scripts.
Removed massive generateLinuxScript and generateWindowsScript functions.
Downloads handler now uses template service (1073 lines → 174 lines).
Templates easily maintainable without modifying Go code.
2025-11-10 22:41:47 -05:00

175 lines
4.9 KiB
Go

package handlers
import (
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/Fimeg/RedFlag/aggregator-server/internal/config"
"github.com/Fimeg/RedFlag/aggregator-server/internal/services"
"github.com/gin-gonic/gin"
)
// DownloadHandler handles agent binary downloads
type DownloadHandler struct {
agentDir string
config *config.Config
installTemplateService *services.InstallTemplateService
}
func NewDownloadHandler(agentDir string, cfg *config.Config) *DownloadHandler {
return &DownloadHandler{
agentDir: agentDir,
config: cfg,
installTemplateService: services.NewInstallTemplateService(),
}
}
// getServerURL determines the server URL with proper protocol detection
func (h *DownloadHandler) getServerURL(c *gin.Context) string {
// Priority 1: Use configured public URL if set
if h.config.Server.PublicURL != "" {
return h.config.Server.PublicURL
}
// Priority 2: Construct API server URL from configuration
scheme := "http"
host := h.config.Server.Host
port := h.config.Server.Port
// Use HTTPS if TLS is enabled in config
if h.config.Server.TLS.Enabled {
scheme = "https"
}
// For default host (0.0.0.0), use localhost for client connections
if host == "0.0.0.0" {
host = "localhost"
}
// Only include port if it's not the default for the protocol
if (scheme == "http" && port != 80) || (scheme == "https" && port != 443) {
return fmt.Sprintf("%s://%s:%d", scheme, host, port)
}
return fmt.Sprintf("%s://%s", scheme, host)
}
// DownloadAgent serves agent binaries for different platforms
func (h *DownloadHandler) DownloadAgent(c *gin.Context) {
platform := c.Param("platform")
version := c.Query("version") // Optional version parameter for signed binaries
// Validate platform to prevent directory traversal
validPlatforms := map[string]bool{
"linux-amd64": true,
"linux-arm64": true,
"windows-amd64": true,
"windows-arm64": true,
}
if !validPlatforms[platform] {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid or unsupported platform"})
return
}
// Build filename based on platform
filename := "redflag-agent"
if strings.HasPrefix(platform, "windows") {
filename += ".exe"
}
var agentPath string
// Try to serve signed package first if version is specified
// TODO: Implement database lookup for signed packages
// if version != "" {
// signedPackage, err := h.packageQueries.GetSignedPackage(version, platform)
// if err == nil && fileExists(signedPackage.BinaryPath) {
// agentPath = signedPackage.BinaryPath
// }
// }
// Fallback to unsigned generic binary
if agentPath == "" {
agentPath = filepath.Join(h.agentDir, "binaries", platform, filename)
}
// Check if file exists
if _, err := os.Stat(agentPath); os.IsNotExist(err) {
c.JSON(http.StatusNotFound, gin.H{
"error": "Agent binary not found",
"platform": platform,
"version": version,
})
return
}
// Handle both GET and HEAD requests
if c.Request.Method == "HEAD" {
c.Status(http.StatusOK)
return
}
c.File(agentPath)
}
// DownloadUpdatePackage serves signed agent update packages
func (h *DownloadHandler) DownloadUpdatePackage(c *gin.Context) {
packageID := c.Param("package_id")
// Validate package ID format (UUID)
if len(packageID) != 36 {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid package ID format"})
return
}
// TODO: Implement actual package serving from database/filesystem
// For now, return a placeholder response
c.JSON(http.StatusNotImplemented, gin.H{
"error": "Update package download not yet implemented",
"package_id": packageID,
"message": "This will serve the signed update package file",
})
}
// InstallScript serves the installation script
func (h *DownloadHandler) InstallScript(c *gin.Context) {
platform := c.Param("platform")
// Validate platform
validPlatforms := map[string]bool{
"linux": true,
"windows": true,
}
if !validPlatforms[platform] {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid or unsupported platform"})
return
}
serverURL := h.getServerURL(c)
scriptContent := h.generateInstallScript(platform, serverURL)
c.Header("Content-Type", "text/plain")
c.String(http.StatusOK, scriptContent)
}
func (h *DownloadHandler) generateInstallScript(platform, baseURL string) string {
// Use template service to generate install scripts
// For generic downloads, use placeholder values
script, err := h.installTemplateService.RenderInstallScriptFromBuild(
"<AGENT_ID>", // Will be generated during install
platform, // Platform (linux/windows)
"latest", // Version
fmt.Sprintf("%s/downloads/%s", baseURL, platform), // Binary URL
fmt.Sprintf("%s/api/v1/config/<AGENT_ID>", baseURL), // Config URL (placeholder)
)
if err != nil {
return fmt.Sprintf("# Error generating install script: %v", err)
}
return script
}