Files
Redflag/aggregator-server/internal/api/handlers/agent_build.go
Fimeg 52c9c1a45b refactor: add AgentLifecycleService for unified agent operations
Created centralized lifecycle service to handle new, upgrade, and rebuild operations.
Added deprecation notices to old handlers (agent_setup, build_orchestrator, agent_build).
Foundation for consolidating duplicate agent lifecycle logic.
2025-11-10 22:15:03 -05:00

187 lines
4.9 KiB
Go

package handlers
import (
"net/http"
"os"
"path/filepath"
"github.com/Fimeg/RedFlag/aggregator-server/internal/services"
"github.com/gin-gonic/gin"
)
// BuildAgent handles the agent build endpoint
// Deprecated: Use AgentHandler.Rebuild instead
func BuildAgent(c *gin.Context) {
var req services.AgentSetupRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Create config builder
configBuilder := services.NewConfigBuilder(req.ServerURL)
// Build agent configuration
config, err := configBuilder.BuildAgentConfig(req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Create agent builder
agentBuilder := services.NewAgentBuilder()
// Generate build artifacts
buildResult, err := agentBuilder.BuildAgentWithConfig(config)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Create response with native binary instructions
response := gin.H{
"agent_id": config.AgentID,
"config_file": buildResult.ConfigFile,
"platform": buildResult.Platform,
"config_version": config.ConfigVersion,
"agent_version": config.AgentVersion,
"build_time": buildResult.BuildTime,
"next_steps": []string{
"1. Download native binary from server",
"2. Place binary in /usr/local/bin/redflag-agent",
"3. Set permissions: chmod 755 /usr/local/bin/redflag-agent",
"4. Create config directory: mkdir -p /etc/redflag",
"5. Save config to /etc/redflag/config.json",
"6. Set config permissions: chmod 600 /etc/redflag/config.json",
"7. Start service: systemctl enable --now redflag-agent",
},
"configuration": config.PublicConfig,
}
c.JSON(http.StatusOK, response)
}
// GetBuildInstructions returns build instructions for manual setup
func GetBuildInstructions(c *gin.Context) {
agentID := c.Param("agentID")
if agentID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "agent ID is required"})
return
}
instructions := gin.H{
"title": "RedFlag Agent Build Instructions",
"agent_id": agentID,
"steps": []gin.H{
{
"step": 1,
"title": "Prepare Build Environment",
"commands": []string{
"mkdir -p redflag-build",
"cd redflag-build",
},
},
{
"step": 2,
"title": "Copy Agent Source Code",
"commands": []string{
"cp -r ../aggregator-agent/* .",
"ls -la",
},
},
{
"step": 3,
"title": "Build Docker Image",
"commands": []string{
"docker build -t redflag-agent:" + agentID[:8] + " .",
},
},
{
"step": 4,
"title": "Create Docker Network",
"commands": []string{
"docker network create redflag 2>/dev/null || true",
},
},
{
"step": 5,
"title": "Deploy Agent",
"commands": []string{
"docker compose up -d",
},
},
{
"step": 6,
"title": "Verify Deployment",
"commands": []string{
"docker compose logs -f",
"docker ps",
},
},
},
"troubleshooting": []gin.H{
{
"issue": "Build fails with 'go mod download' errors",
"solution": "Ensure go.mod and go.sum are copied correctly and internet connectivity is available",
},
{
"issue": "Container fails to start",
"solution": "Check docker-compose.yml and ensure Docker secrets are created with 'echo \"secret-value\" | docker secret create secret-name -'",
},
{
"issue": "Agent cannot connect to server",
"solution": "Verify server URL is accessible from container and firewall rules allow traffic",
},
},
}
c.JSON(http.StatusOK, instructions)
}
// DownloadBuildArtifacts provides download links for generated files
func DownloadBuildArtifacts(c *gin.Context) {
agentID := c.Param("agentID")
fileType := c.Param("fileType")
buildDir := c.Query("buildDir")
// Validate agent ID parameter
if agentID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "agent ID is required"})
return
}
if buildDir == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "build directory is required"})
return
}
// Security check: ensure the buildDir is within expected path
absBuildDir, err := filepath.Abs(buildDir)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid build directory"})
return
}
// Construct file path based on type
var filePath string
switch fileType {
case "compose":
filePath = filepath.Join(absBuildDir, "docker-compose.yml")
case "dockerfile":
filePath = filepath.Join(absBuildDir, "Dockerfile")
case "config":
filePath = filepath.Join(absBuildDir, "pkg", "embedded", "config.go")
default:
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid file type"})
return
}
// Check if file exists
if _, err := os.Stat(filePath); os.IsNotExist(err) {
c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
return
}
// Serve file for download
c.FileAttachment(filePath, filepath.Base(filePath))
}