Ctrl K
ComunidadGuías…

server/internal/handlers/admin_systemd.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package handlers

import (
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"regexp"
	"strings"
	"time"

	"blackbox/server/internal/models"
	"github.com/go-chi/chi/v5"
	"github.com/oklog/ulid/v2"
	"gorm.io/gorm"
)

var validUnitName = regexp.MustCompile(`^[A-Za-z0-9:_.\-@\\]+\.(service|socket|target|timer|mount|automount|path|scope|slice|swap|device)$`)

func GetSystemdSettings(db *gorm.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var instances []models.DataSourceInstance
		if err := db.Where("type = ?", "systemd").Find(&instances).Error; err != nil {
			writeError(w, http.StatusInternalServerError, "failed to load systemd settings")
			return
		}

		result := make(map[string][]string)
		for _, inst := range instances {
			if inst.NodeID == nil {
				continue
			}
			var cfg struct {
				Units []string `json:"units"`
			}
			if err := json.Unmarshal([]byte(inst.Config), &cfg); err != nil {
				log.Printf("GetSystemdSettings: failed to parse config for source %s: %v", inst.ID, err)
				cfg.Units = []string{}
			}
			if cfg.Units == nil {
				cfg.Units = []string{}
			}
			result[*inst.NodeID] = cfg.Units
		}

		w.Header().Set("Content-Type", "application/json")
		if err := json.NewEncoder(w).Encode(result); err != nil {
			log.Printf("GetSystemdSettings encode: %v", err)
		}
	}
}

func UpdateSystemdSettings(db *gorm.DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		nodeName := strings.TrimSpace(chi.URLParam(r, "node_name"))
		if nodeName == "" {
			writeError(w, http.StatusBadRequest, "node_name is required")
			return
		}

		var req struct {
			Units []string `json:"units"`
		}
		if !decodeJSONBody(w, r, 64<<10, &req) {
			return
		}
		if req.Units == nil {
			req.Units = []string{}
		}

		seen := make(map[string]struct{}, len(req.Units))
		clean := make([]string, 0, len(req.Units))
		for _, u := range req.Units {
			t := strings.TrimSpace(u)
			if t == "" {
				continue
			}
			if !hasUnitTypeSuffix(t) {
				t = t + ".service"
			}
			if _, dup := seen[t]; dup {
				continue
			}
			seen[t] = struct{}{}
			clean = append(clean, t)
		}

		for _, unit := range clean {
			if !validUnitName.MatchString(unit) {
				writeError(w, http.StatusBadRequest, "invalid unit name: "+unit)
				return
			}
		}

		cfgJSON, err := json.Marshal(map[string]any{"units": clean})
		if err != nil {
			writeError(w, http.StatusInternalServerError, "failed to serialize units")
			return
		}

		var nodeExists int64
		if err := db.Model(&models.Node{}).Where("name = ?", nodeName).Count(&nodeExists).Error; err != nil {
			writeError(w, http.StatusInternalServerError, "failed to verify node")
			return
		}

		err = db.Transaction(func(tx *gorm.DB) error {
			var existing models.DataSourceInstance
			findErr := tx.Where("type = ? AND node_id = ?", "systemd", nodeName).First(&existing).Error
			now := time.Now().UTC()
			if findErr == nil {
				existing.Config = string(cfgJSON)
				existing.UpdatedAt = now
				return tx.Save(&existing).Error
			}
			if !errors.Is(findErr, gorm.ErrRecordNotFound) {
				return findErr
			}
			if nodeExists == 0 {
				return gorm.ErrRecordNotFound
			}
			inst := models.DataSourceInstance{
				ID: ulid.Make().String(), Type: "systemd", Scope: "agent",
				NodeID: &nodeName, Name: "Systemd", Config: string(cfgJSON),
				Enabled: true, CreatedAt: now, UpdatedAt: now,
			}
			return tx.Create(&inst).Error
		})
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				writeError(w, http.StatusNotFound, "node not found")
				return
			}
			writeError(w, http.StatusInternalServerError, "failed to save systemd settings")
			return
		}

		w.WriteHeader(http.StatusNoContent)
	}
}

var unitTypeSuffixes = []string{
	".service", ".socket", ".device", ".mount", ".automount",
	".swap", ".target", ".path", ".timer", ".slice", ".scope",
}

func hasUnitTypeSuffix(name string) bool {
	for _, s := range unitTypeSuffixes {
		if strings.HasSuffix(name, s) {
			return true
		}
	}
	return false
}