package handlers import ( "context" "fmt" "net/http" "os" "path/filepath" "time" "gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/ansible" "gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/repository" "github.com/gin-gonic/gin" ) type AgentDeployGroup struct { *Handlers executor *ansible.Executor } func NewAgentDeployGroup(h *Handlers) *AgentDeployGroup { workDir := os.Getenv("ANSIBLE_WORK_DIR") if workDir == "" { workDir = "/tmp/hellreign/ansible" } grpcPort := os.Getenv("GRPC_PORT") if grpcPort == "" { grpcPort = "9001" } backendURL := os.Getenv("BACKEND_URL") if backendURL == "" { backendURL = "http://localhost:8080" } exec := ansible.NewExecutor(ansible.ExecutorConfig{ WorkDir: workDir, GRPCServerHost: "0.0.0.0", // TODO: make configurable GRPCServerPort: grpcPort, BackendURL: backendURL, }) // Write playbooks on init if err := exec.WriteAllPlaybooks(); err != nil { // Log but don't fail - playbooks can be written later _ = err } return &AgentDeployGroup{ Handlers: h, executor: exec, } } // DeployAgents deploys agents to multiple servers // @Summary Deploy agents to multiple servers via Ansible // @Description Deploy HellreigN agents to multiple servers using Ansible playbooks. Supports Docker and Binary deployment types. // @Tags agents // @Accept json // @Produce json // @Param request body repository.DeployAgentsRequest true "Deployment configuration for servers" // @Success 200 {object} repository.DeployResponse "Deployment results with tokens for each server" // @Failure 400 {object} map[string]string "Invalid request" // @Failure 500 {object} map[string]string "Internal server error" // @Security Bearer // @Router /agents/deploy [post] func (adg *AgentDeployGroup) DeployAgents(c *gin.Context) { var req repository.DeployAgentsRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Create work directory workDir := adg.executor.WorkDir() if err := os.MkdirAll(workDir, 0755); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create work directory"}) return } // Generate registration tokens for each server results := make([]repository.DeployResult, 0, len(req.Servers)) timestamp := time.Now().UnixMilli() ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Minute) defer cancel() for i, server := range req.Servers { // Create registration token token, err := adg.Repo.CreateRegistrationToken(server.AgentLabel) if err != nil { results = append(results, repository.DeployResult{ IP: server.IP, AgentLabel: server.AgentLabel, Success: false, Error: fmt.Sprintf("failed to create token: %v", err), }) continue } // Set default port port := server.Port if port == 0 { port = 22 } // Generate inventory for this single server inventoryHosts := []ansible.InventoryHost{ { Name: server.AgentLabel, IP: server.IP, Port: port, User: server.User, AuthMethod: string(server.AuthMethod), SSHKey: server.SSHKey, Password: server.Password, DeployType: string(server.DeployType), Token: token, }, } inventoryPath := filepath.Join(workDir, fmt.Sprintf("inventory_%d_%d", timestamp, i)) if err := ansible.GenerateInventory(inventoryHosts, inventoryPath); err != nil { results = append(results, repository.DeployResult{ IP: server.IP, AgentLabel: server.AgentLabel, Token: token, Success: false, Error: fmt.Sprintf("failed to generate inventory: %v", err), }) continue } // Run Ansible playbook for this server deployResults, err := adg.executor.Deploy(ctx, inventoryPath, string(server.DeployType)) // Clean up inventory file os.Remove(inventoryPath) if err != nil { results = append(results, repository.DeployResult{ IP: server.IP, AgentLabel: server.AgentLabel, Token: token, Success: false, Error: fmt.Sprintf("deployment failed: %v", err), }) continue } success := true errMsg := "" if len(deployResults) > 0 && !deployResults[0].Success { success = false errMsg = deployResults[0].Stderr } results = append(results, repository.DeployResult{ IP: server.IP, AgentLabel: server.AgentLabel, Token: token, Success: success, Error: errMsg, }) } c.JSON(http.StatusOK, repository.DeployResponse{ Message: "Deployment completed", Results: results, }) }