cleanup: remove unwired lifecycle/build services
AgentLifecycleService, ConfigService, BuildService, ArtifactService,
AgentBuildHandler — added as a unification pass (e56888e6), never
instantiated by any commit since. ~600 lines.
This commit is contained in:
parent
c6e430225c
commit
43ddaab262
5 changed files with 3 additions and 762 deletions
|
|
@ -1,200 +0,0 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/Fimeg/RedFlag/server/internal/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AgentBuildHandler handles agent build operations
|
||||
type AgentBuildHandler struct {
|
||||
agentQueries *queries.AgentQueries
|
||||
}
|
||||
|
||||
// NewAgentBuildHandler creates a new agent build handler
|
||||
func NewAgentBuildHandler(agentQueries *queries.AgentQueries) *AgentBuildHandler {
|
||||
return &AgentBuildHandler{
|
||||
agentQueries: agentQueries,
|
||||
}
|
||||
}
|
||||
|
||||
// BuildAgent handles the agent build endpoint
|
||||
// Deprecated: Use AgentHandler.Rebuild instead
|
||||
func (h *AgentBuildHandler) 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 with database access
|
||||
configBuilder := services.NewConfigBuilder(req.ServerURL, h.agentQueries.DB)
|
||||
|
||||
// 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 (h *AgentBuildHandler) 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 ../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 (h *AgentBuildHandler) 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))
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@ func NewAgentBuild(c *gin.Context) {
|
|||
}
|
||||
|
||||
// UpgradeAgentBuild handles agent upgrade requests
|
||||
// Deprecated: Use ConfigService for config building
|
||||
// Deprecated: superseded by AgentHandler.Rebuild (ConfigService was removed unwired)
|
||||
func UpgradeAgentBuild(c *gin.Context) {
|
||||
agentID := c.Param("agentID")
|
||||
if agentID == "" {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
)
|
||||
|
||||
// AgentBuilder handles generating embedded agent configurations
|
||||
// Deprecated: Configuration logic should use services.ConfigService
|
||||
// Deprecated: superseded by AgentHandler.Rebuild (ConfigService was removed unwired)
|
||||
type AgentBuilder struct {
|
||||
buildContext string
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ func NewAgentBuilder() *AgentBuilder {
|
|||
}
|
||||
|
||||
// BuildAgentWithConfig generates agent configuration and prepares signed binary
|
||||
// Deprecated: Delegate config generation to services.ConfigService
|
||||
// Deprecated: superseded by AgentHandler.Rebuild (ConfigService was removed unwired)
|
||||
func (ab *AgentBuilder) BuildAgentWithConfig(config *AgentConfiguration) (*BuildResult, error) {
|
||||
// Create temporary build directory
|
||||
buildDir, err := os.MkdirTemp("", "agent-build-")
|
||||
|
|
|
|||
|
|
@ -1,354 +0,0 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/config"
|
||||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// LifecycleOperation represents the type of agent operation
|
||||
type LifecycleOperation string
|
||||
|
||||
const (
|
||||
OperationNew LifecycleOperation = "new"
|
||||
OperationUpgrade LifecycleOperation = "upgrade"
|
||||
OperationRebuild LifecycleOperation = "rebuild"
|
||||
)
|
||||
|
||||
// AgentConfig holds configuration for agent operations
|
||||
type AgentConfig struct {
|
||||
AgentID string
|
||||
Version string
|
||||
Platform string
|
||||
Architecture string
|
||||
MachineID string
|
||||
AgentType string
|
||||
ServerURL string
|
||||
Hostname string
|
||||
}
|
||||
|
||||
// AgentLifecycleService manages all agent lifecycle operations
|
||||
type AgentLifecycleService struct {
|
||||
db *sqlx.DB
|
||||
config *config.Config
|
||||
buildService *BuildService
|
||||
configService *ConfigService
|
||||
artifactService *ArtifactService
|
||||
subsystemQueries *queries.SubsystemQueries
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// NewAgentLifecycleService creates a new lifecycle service
|
||||
func NewAgentLifecycleService(
|
||||
db *sqlx.DB,
|
||||
cfg *config.Config,
|
||||
logger *log.Logger,
|
||||
) *AgentLifecycleService {
|
||||
return &AgentLifecycleService{
|
||||
db: db,
|
||||
config: cfg,
|
||||
buildService: NewBuildService(db, cfg, logger),
|
||||
configService: NewConfigService(db, cfg, logger),
|
||||
artifactService: NewArtifactService(db, cfg, logger),
|
||||
subsystemQueries: queries.NewSubsystemQueries(db),
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Process handles all agent lifecycle operations (new, upgrade, rebuild)
|
||||
func (s *AgentLifecycleService) Process(
|
||||
ctx context.Context,
|
||||
op LifecycleOperation,
|
||||
agentCfg *AgentConfig,
|
||||
) (*AgentSetupResponse, error) {
|
||||
|
||||
// Step 1: Validate operation
|
||||
if err := s.validateOperation(op, agentCfg); err != nil {
|
||||
return nil, fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Step 2: Check if agent exists (for upgrade/rebuild)
|
||||
_, err := s.getAgent(ctx, agentCfg.AgentID)
|
||||
if err != nil && op != OperationNew {
|
||||
return nil, fmt.Errorf("agent not found: %w", err)
|
||||
}
|
||||
|
||||
// Step 3: Generate or load configuration
|
||||
var configJSON []byte
|
||||
if op == OperationNew {
|
||||
configJSON, err = s.configService.GenerateNewConfig(agentCfg)
|
||||
} else {
|
||||
configJSON, err = s.configService.LoadExistingConfig(agentCfg.AgentID)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("config generation failed: %w", err)
|
||||
}
|
||||
|
||||
// Step 4: Check if build is needed
|
||||
needBuild, err := s.buildService.IsBuildRequired(agentCfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build check failed: %w", err)
|
||||
}
|
||||
|
||||
var artifacts *BuildArtifacts
|
||||
if needBuild {
|
||||
// Step 5: Build artifacts
|
||||
artifacts, err = s.buildService.BuildArtifacts(ctx, agentCfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build failed: %w", err)
|
||||
}
|
||||
|
||||
// Step 6: Store artifacts
|
||||
if err := s.artifactService.Store(ctx, artifacts); err != nil {
|
||||
return nil, fmt.Errorf("artifact storage failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
// Step 7: Use existing artifacts
|
||||
artifacts, err = s.artifactService.Get(ctx, agentCfg.Platform, agentCfg.Version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("existing artifacts not found: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 8: Create or update agent record
|
||||
if op == OperationNew {
|
||||
err = s.createAgent(ctx, agentCfg, configJSON)
|
||||
} else {
|
||||
err = s.updateAgent(ctx, agentCfg, configJSON)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("agent record update failed: %w", err)
|
||||
}
|
||||
|
||||
// Step 9: Return response
|
||||
return s.buildResponse(agentCfg, artifacts), nil
|
||||
}
|
||||
|
||||
// validateOperation validates the lifecycle operation
|
||||
func (s *AgentLifecycleService) validateOperation(
|
||||
op LifecycleOperation,
|
||||
cfg *AgentConfig,
|
||||
) error {
|
||||
if cfg.AgentID == "" {
|
||||
return fmt.Errorf("agent_id is required")
|
||||
}
|
||||
if cfg.Version == "" {
|
||||
return fmt.Errorf("version is required")
|
||||
}
|
||||
if cfg.Platform == "" {
|
||||
return fmt.Errorf("platform is required")
|
||||
}
|
||||
|
||||
// Operation-specific validation
|
||||
switch op {
|
||||
case OperationNew:
|
||||
// New agents need machine_id
|
||||
if cfg.MachineID == "" {
|
||||
return fmt.Errorf("machine_id is required for new agents")
|
||||
}
|
||||
case OperationUpgrade, OperationRebuild:
|
||||
// Upgrade/rebuild need existing agent
|
||||
// Validation done in getAgent()
|
||||
default:
|
||||
return fmt.Errorf("unknown operation: %s", op)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getAgent retrieves agent from database
|
||||
func (s *AgentLifecycleService) getAgent(ctx context.Context, agentID string) (*models.Agent, error) {
|
||||
var agent models.Agent
|
||||
query := `SELECT * FROM agents WHERE id = $1`
|
||||
err := s.db.GetContext(ctx, &agent, query, agentID)
|
||||
return &agent, err
|
||||
}
|
||||
|
||||
// createAgent creates new agent record
|
||||
func (s *AgentLifecycleService) createAgent(
|
||||
ctx context.Context,
|
||||
cfg *AgentConfig,
|
||||
configJSON []byte,
|
||||
) error {
|
||||
machineID := cfg.MachineID
|
||||
agent := &models.Agent{
|
||||
ID: uuid.Must(uuid.FromString(cfg.AgentID)),
|
||||
Hostname: cfg.Hostname,
|
||||
OSType: cfg.Platform,
|
||||
AgentVersion: cfg.Version,
|
||||
MachineID: &machineID,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}
|
||||
|
||||
query := `
|
||||
INSERT INTO agents (id, hostname, os_type, agent_version, machine_id, created_at, updated_at)
|
||||
VALUES (:id, :hostname, :os_type, :agent_version, :machine_id, :created_at, :updated_at)
|
||||
`
|
||||
_, err := s.db.NamedExecContext(ctx, query, agent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("agent record creation failed: %w", err)
|
||||
}
|
||||
|
||||
// Determine platform-specific scanners based on OS type
|
||||
availableScanners := s.determineScannersFromOS(cfg.Platform)
|
||||
s.logger.Printf("[INFO] [server] [lifecycle] agent=%s os=%s scanners=%v", cfg.AgentID, cfg.Platform, availableScanners)
|
||||
|
||||
// Create platform-specific subsystems for new agent
|
||||
if err := s.subsystemQueries.CreateDefaultSubsystems(agent.ID, availableScanners); err != nil {
|
||||
s.logger.Printf("Warning: failed to create default subsystems: %v", err)
|
||||
// Non-fatal error - agent still created
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// determineScannersFromOS returns appropriate package manager scanners based on OS type
|
||||
func (s *AgentLifecycleService) determineScannersFromOS(osType string) []string {
|
||||
scanners := []string{}
|
||||
osLower := strings.ToLower(osType)
|
||||
|
||||
switch {
|
||||
// Debian/Ubuntu based systems
|
||||
case strings.Contains(osLower, "debian"), strings.Contains(osLower, "ubuntu"), strings.Contains(osLower, "linuxmint"):
|
||||
scanners = append(scanners, "apt")
|
||||
|
||||
// Fedora/RHEL/CentOS based systems
|
||||
case strings.Contains(osLower, "fedora"), strings.Contains(osLower, "rhel"), strings.Contains(osLower, "centos"), strings.Contains(osLower, "rocky"), strings.Contains(osLower, "alma"):
|
||||
scanners = append(scanners, "dnf")
|
||||
|
||||
// Windows systems
|
||||
case strings.Contains(osLower, "windows"):
|
||||
scanners = append(scanners, "winget", "windows")
|
||||
|
||||
// Arch-based (fallback to apt for structure similarity, or could add pacman)
|
||||
case strings.Contains(osLower, "arch"), strings.Contains(osLower, "manjaro"):
|
||||
scanners = append(scanners, "apt") // Using apt as fallback
|
||||
|
||||
// Default fallback for unknown Linux
|
||||
case strings.Contains(osLower, "linux"):
|
||||
scanners = append(scanners, "apt") // Conservative fallback
|
||||
|
||||
// Unknown OS - no package scanners
|
||||
default:
|
||||
s.logger.Printf("[WARNING] [server] [lifecycle] unknown_os_type=%s no_package_scanners", osType)
|
||||
}
|
||||
|
||||
return scanners
|
||||
}
|
||||
|
||||
// updateAgent updates existing agent record
|
||||
func (s *AgentLifecycleService) updateAgent(
|
||||
ctx context.Context,
|
||||
cfg *AgentConfig,
|
||||
configJSON []byte,
|
||||
) error {
|
||||
query := `
|
||||
UPDATE agents
|
||||
SET agent_version = $1, updated_at = $2
|
||||
WHERE id = $3
|
||||
`
|
||||
_, err := s.db.ExecContext(ctx, query, cfg.Version, time.Now().UTC(), cfg.AgentID)
|
||||
return err
|
||||
}
|
||||
|
||||
// buildResponse constructs the API response
|
||||
func (s *AgentLifecycleService) buildResponse(
|
||||
cfg *AgentConfig,
|
||||
artifacts *BuildArtifacts,
|
||||
) *AgentSetupResponse {
|
||||
// Default to amd64 if architecture not specified
|
||||
arch := cfg.Architecture
|
||||
if arch == "" {
|
||||
arch = "amd64"
|
||||
}
|
||||
|
||||
return &AgentSetupResponse{
|
||||
AgentID: cfg.AgentID,
|
||||
ConfigURL: fmt.Sprintf("/api/v1/config/%s", cfg.AgentID),
|
||||
BinaryURL: fmt.Sprintf("/api/v1/downloads/%s-%s?version=%s", cfg.Platform, arch, cfg.Version),
|
||||
Signature: artifacts.Signature,
|
||||
Version: cfg.Version,
|
||||
Platform: cfg.Platform,
|
||||
NextSteps: s.generateNextSteps(cfg),
|
||||
CreatedAt: time.Now().UTC(),
|
||||
}
|
||||
}
|
||||
|
||||
// generateNextSteps creates installation instructions
|
||||
func (s *AgentLifecycleService) generateNextSteps(cfg *AgentConfig) []string {
|
||||
return []string{
|
||||
fmt.Sprintf("1. Download binary: %s/redflag-agent", cfg.Platform),
|
||||
fmt.Sprintf("2. Download config: %s/config.json", cfg.AgentID),
|
||||
"3. Install binary to: /usr/local/bin/redflag-agent",
|
||||
"4. Install config to: /etc/redflag/config.json",
|
||||
"5. Run: systemctl enable --now redflag-agent",
|
||||
}
|
||||
}
|
||||
|
||||
// AgentSetupResponse is the unified response for all agent operations
|
||||
type AgentSetupResponse struct {
|
||||
AgentID string `json:"agent_id"`
|
||||
ConfigURL string `json:"config_url"`
|
||||
BinaryURL string `json:"binary_url"`
|
||||
Signature string `json:"signature"`
|
||||
Version string `json:"version"`
|
||||
Platform string `json:"platform"`
|
||||
NextSteps []string `json:"next_steps"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// BuildService placeholder (to be implemented)
|
||||
type BuildService struct {
|
||||
db *sqlx.DB
|
||||
config *config.Config
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewBuildService(db *sqlx.DB, cfg *config.Config, logger *log.Logger) *BuildService {
|
||||
return &BuildService{db: db, config: cfg, logger: logger}
|
||||
}
|
||||
|
||||
func (s *BuildService) IsBuildRequired(cfg *AgentConfig) (bool, error) {
|
||||
// Placeholder: Always return false for now (use existing builds)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s *BuildService) BuildArtifacts(ctx context.Context, cfg *AgentConfig) (*BuildArtifacts, error) {
|
||||
// Placeholder: Return empty artifacts
|
||||
return &BuildArtifacts{}, nil
|
||||
}
|
||||
|
||||
// ArtifactService placeholder (to be implemented)
|
||||
type ArtifactService struct {
|
||||
db *sqlx.DB
|
||||
config *config.Config
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewArtifactService(db *sqlx.DB, cfg *config.Config, logger *log.Logger) *ArtifactService {
|
||||
return &ArtifactService{db: db, config: cfg, logger: logger}
|
||||
}
|
||||
|
||||
func (s *ArtifactService) Store(ctx context.Context, artifacts *BuildArtifacts) error {
|
||||
// Placeholder: Do nothing for now
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ArtifactService) Get(ctx context.Context, platform, version string) (*BuildArtifacts, error) {
|
||||
// Placeholder: Return empty artifacts
|
||||
return &BuildArtifacts{}, nil
|
||||
}
|
||||
|
||||
// BuildArtifacts represents build output
|
||||
type BuildArtifacts struct {
|
||||
Signature string
|
||||
}
|
||||
|
|
@ -1,205 +0,0 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/Fimeg/RedFlag/server/internal/config"
|
||||
"github.com/Fimeg/RedFlag/server/internal/database/queries"
|
||||
"github.com/Fimeg/RedFlag/server/internal/models"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// ConfigService manages agent configuration generation and validation
|
||||
type ConfigService struct {
|
||||
db *sqlx.DB
|
||||
config *config.Config
|
||||
logger *log.Logger
|
||||
subsystemQueries *queries.SubsystemQueries
|
||||
}
|
||||
|
||||
// NewConfigService creates a new configuration service
|
||||
func NewConfigService(db *sqlx.DB, cfg *config.Config, logger *log.Logger) *ConfigService {
|
||||
return &ConfigService{
|
||||
db: db,
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
subsystemQueries: queries.NewSubsystemQueries(db),
|
||||
}
|
||||
}
|
||||
|
||||
// getDB returns the database connection (for access to refresh token queries)
|
||||
func (s *ConfigService) getDB() *sqlx.DB {
|
||||
return s.db
|
||||
}
|
||||
|
||||
// AgentConfigData represents agent configuration structure
|
||||
type AgentConfigData struct {
|
||||
AgentID string `json:"agent_id"`
|
||||
Version string `json:"version"`
|
||||
Platform string `json:"platform"`
|
||||
ServerURL string `json:"server_url"`
|
||||
LogLevel string `json:"log_level"`
|
||||
Intervals map[string]int `json:"intervals"`
|
||||
Subsystems map[string]interface{} `json:"subsystems"`
|
||||
MaxRetries int `json:"max_retries"`
|
||||
TimeoutSeconds int `json:"timeout_seconds"`
|
||||
MachineID string `json:"machine_id"`
|
||||
AgentType string `json:"agent_type"`
|
||||
ConfigPath string `json:"config_path"`
|
||||
StatePath string `json:"state_path"`
|
||||
LogPath string `json:"log_path"`
|
||||
ServiceName string `json:"service_name"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// GenerateNewConfig creates configuration for a new agent
|
||||
func (s *ConfigService) GenerateNewConfig(agentCfg *AgentConfig) ([]byte, error) {
|
||||
// Base configuration
|
||||
serverURL := fmt.Sprintf("http://%s:%d", s.config.Server.Host, s.config.Server.Port)
|
||||
if s.config.Server.PublicURL != "" {
|
||||
serverURL = s.config.Server.PublicURL
|
||||
}
|
||||
|
||||
// Get subsystems from database (not hardcoded!)
|
||||
agentID := uuid.Must(uuid.FromString(agentCfg.AgentID))
|
||||
subsystems, err := s.subsystemQueries.GetSubsystems(agentID)
|
||||
if err != nil || len(subsystems) == 0 {
|
||||
// If not found, create defaults (nil = generic subsystems for config path)
|
||||
if err := s.subsystemQueries.CreateDefaultSubsystems(agentID, nil); err != nil {
|
||||
return nil, fmt.Errorf("failed to create default subsystems: %w", err)
|
||||
}
|
||||
subsystems, _ = s.subsystemQueries.GetSubsystems(agentID)
|
||||
}
|
||||
|
||||
// Convert to map format for JSON
|
||||
subsystemMap := make(map[string]interface{})
|
||||
for _, sub := range subsystems {
|
||||
subsystemMap[sub.Subsystem] = map[string]interface{}{
|
||||
"enabled": sub.Enabled,
|
||||
"auto_run": sub.AutoRun,
|
||||
"interval": sub.IntervalMinutes,
|
||||
}
|
||||
}
|
||||
|
||||
cfg := &AgentConfigData{
|
||||
AgentID: agentCfg.AgentID,
|
||||
Version: agentCfg.Version,
|
||||
Platform: agentCfg.Platform,
|
||||
ServerURL: serverURL,
|
||||
LogLevel: "info",
|
||||
Intervals: map[string]int{
|
||||
"metrics": 300, // 5 minutes
|
||||
"commands": 30, // 30 seconds
|
||||
},
|
||||
Subsystems: subsystemMap, // ← USE DATABASE VALUES!
|
||||
MaxRetries: 3,
|
||||
TimeoutSeconds: 30,
|
||||
MachineID: agentCfg.MachineID,
|
||||
AgentType: agentCfg.AgentType,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}
|
||||
|
||||
// Platform-specific customizations
|
||||
s.applyPlatformDefaults(cfg)
|
||||
|
||||
// Validate configuration
|
||||
if err := s.validateConfig(cfg); err != nil {
|
||||
return nil, fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Marshal to JSON
|
||||
configJSON, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal failed: %w", err)
|
||||
}
|
||||
|
||||
return configJSON, nil
|
||||
}
|
||||
|
||||
// LoadExistingConfig retrieves and updates existing agent configuration
|
||||
func (s *ConfigService) LoadExistingConfig(agentID string) ([]byte, error) {
|
||||
// Get existing agent from database
|
||||
var agent models.Agent
|
||||
query := `SELECT * FROM agents WHERE id = $1`
|
||||
err := s.db.Get(&agent, query, agentID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("agent not found: %w", err)
|
||||
}
|
||||
|
||||
// For existing registered agents, generate proper config with auth tokens
|
||||
s.logger.Printf("[DEBUG] Generating config for existing agent %s", agentID)
|
||||
machineID := ""
|
||||
if agent.MachineID != nil {
|
||||
machineID = *agent.MachineID
|
||||
}
|
||||
|
||||
agentCfg := &AgentConfig{
|
||||
AgentID: agentID,
|
||||
Version: agent.CurrentVersion,
|
||||
Platform: agent.OSType,
|
||||
Architecture: agent.OSArchitecture,
|
||||
MachineID: machineID,
|
||||
AgentType: "", // Could be stored in metadata
|
||||
Hostname: agent.Hostname,
|
||||
}
|
||||
|
||||
return s.GenerateNewConfig(agentCfg)
|
||||
}
|
||||
|
||||
// applyPlatformDefaults applies platform-specific configuration
|
||||
func (s *ConfigService) applyPlatformDefaults(cfg *AgentConfigData) {
|
||||
switch cfg.Platform {
|
||||
case "windows-amd64", "windows-arm64", "windows-386":
|
||||
// Windows-specific paths
|
||||
cfg.ConfigPath = "C:\\ProgramData\\RedFlag\\config.json"
|
||||
cfg.StatePath = "C:\\ProgramData\\RedFlag\\state\\"
|
||||
cfg.LogPath = "C:\\ProgramData\\RedFlag\\logs\\"
|
||||
cfg.ServiceName = "RedFlagAgent"
|
||||
|
||||
// Windows-specific subsystems
|
||||
cfg.Subsystems["windows"] = map[string]interface{}{"enabled": true, "auto_run": true, "timeout": 300}
|
||||
cfg.Subsystems["winget"] = map[string]interface{}{"enabled": true, "auto_run": true, "timeout": 180}
|
||||
|
||||
default:
|
||||
// Linux defaults
|
||||
cfg.ConfigPath = "/etc/redflag/config.json"
|
||||
cfg.StatePath = "/var/lib/redflag/"
|
||||
cfg.LogPath = "/var/log/redflag/"
|
||||
cfg.ServiceName = "redflag-agent"
|
||||
}
|
||||
}
|
||||
|
||||
// validateConfig validates configuration
|
||||
func (s *ConfigService) validateConfig(cfg *AgentConfigData) error {
|
||||
if cfg.AgentID == "" {
|
||||
return fmt.Errorf("agent_id is required")
|
||||
}
|
||||
if cfg.Version == "" {
|
||||
return fmt.Errorf("version is required")
|
||||
}
|
||||
if cfg.Platform == "" {
|
||||
return fmt.Errorf("platform is required")
|
||||
}
|
||||
if cfg.ServerURL == "" {
|
||||
return fmt.Errorf("server_url is required")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveConfig saves agent configuration to database
|
||||
func (s *ConfigService) SaveConfig(ctx context.Context, agentID uuid.UUID, configJSON []byte) error {
|
||||
query := `
|
||||
UPDATE agents SET config = $1, updated_at = $2
|
||||
WHERE id = $3
|
||||
`
|
||||
_, err := s.db.ExecContext(ctx, query, configJSON, time.Now().UTC(), agentID)
|
||||
return err
|
||||
}
|
||||
Loading…
Reference in a new issue