Files
IT_Tool/Agent/cmd/agent/main.go

287 lines
8.0 KiB
Go
Raw Normal View History

package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"os/signal"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
"nexusrmm.local/agent/internal/collector"
"nexusrmm.local/agent/internal/config"
"nexusrmm.local/agent/internal/connection"
"nexusrmm.local/agent/internal/deployer"
"nexusrmm.local/agent/internal/executor"
"nexusrmm.local/agent/internal/meshagent"
"nexusrmm.local/agent/internal/scanner"
"nexusrmm.local/agent/internal/updater"
"nexusrmm.local/agent/internal/winsvc"
pb "nexusrmm.local/agent/pkg/proto"
)
var version = "dev"
func main() {
// Absoluten Pfad zur EXE ermitteln — kritisch im Service-Modus,
// da der Working Directory dann C:\Windows\System32 ist.
exePath, err := os.Executable()
if err != nil {
log.Fatalf("Konnte EXE-Pfad nicht ermitteln: %v", err)
}
exeDir := filepath.Dir(exePath)
configPath := filepath.Join(exeDir, "nexus-agent.yaml")
// Kommandozeilen-Argument verarbeiten (install / uninstall / start / stop)
if len(os.Args) > 1 {
switch os.Args[1] {
case "install":
if err := winsvc.Install(exePath); err != nil {
fmt.Fprintf(os.Stderr, "Fehler: %v\n", err)
os.Exit(1)
}
fmt.Println("Service erfolgreich installiert.")
return
case "uninstall":
if err := winsvc.Uninstall(); err != nil {
fmt.Fprintf(os.Stderr, "Fehler: %v\n", err)
os.Exit(1)
}
fmt.Println("Service erfolgreich deinstalliert.")
return
case "start":
if err := winsvc.Start(); err != nil {
fmt.Fprintf(os.Stderr, "Fehler: %v\n", err)
os.Exit(1)
}
fmt.Println("Service gestartet.")
return
case "stop":
if err := winsvc.Stop(); err != nil {
fmt.Fprintf(os.Stderr, "Fehler: %v\n", err)
os.Exit(1)
}
fmt.Println("Service gestoppt.")
return
}
}
// Prüfen ob wir als Windows Service laufen
isSvc, err := winsvc.IsWindowsService()
if err != nil {
log.Fatalf("Service-Erkennung fehlgeschlagen: %v", err)
}
if isSvc {
// Als Windows Service starten — blockiert bis Service gestoppt wird
if err := winsvc.Run(makeRunFn(configPath)); err != nil {
log.Fatalf("Service-Fehler: %v", err)
}
return
}
// Konsolen-Modus: direkt ausführen mit Ctrl+C-Signal
log.Printf("NexusRMM Agent %s starting on %s/%s", version, runtime.GOOS, runtime.GOARCH)
stop := make(chan struct{})
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigCh
log.Println("Shutting down...")
close(stop)
}()
makeRunFn(configPath)(stop)
}
// makeRunFn gibt die Haupt-Agent-Loop als Funktion zurück.
// stop wird geschlossen wenn der Agent beendet werden soll (Service-Stop oder Ctrl+C).
func makeRunFn(configPath string) func(stop <-chan struct{}) {
return func(stop <-chan struct{}) {
log.Printf("NexusRMM Agent %s starting on %s/%s", version, runtime.GOOS, runtime.GOARCH)
cfg, err := config.Load(configPath)
if err != nil {
log.Fatalf("Config load error: %v", err)
}
client, err := connection.ConnectWithRetry(cfg.ServerAddress, 10)
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer client.Close()
if cfg.AgentID == "" {
hostname, _ := os.Hostname()
metrics, _ := collector.Collect()
mac, ip := "", ""
for _, n := range metrics.Networks {
if n.MAC == "" || n.IPAddress == "" {
continue
}
if mac == "" {
mac, ip = n.MAC, n.IPAddress
}
if !strings.Contains(n.IPAddress, ":") {
mac, ip = n.MAC, n.IPAddress
break
}
}
resp, err := client.Client.Enroll(context.Background(), &pb.EnrollRequest{
Hostname: hostname,
OsType: runtime.GOOS,
OsVersion: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
MacAddress: mac,
IpAddress: ip,
AgentVersion: version,
})
if err != nil {
log.Fatalf("Enrollment failed: %v", err)
}
cfg.AgentID = resp.AgentId
cfg.HeartbeatInterval = int(resp.HeartbeatInterval)
if err := cfg.Save(configPath); err != nil {
log.Printf("Warning: could not save config: %v", err)
}
log.Printf("Enrolled with ID: %s", cfg.AgentID)
}
if cfg.MeshEnabled && cfg.MeshCentralUrl != "" {
log.Printf("Installiere MeshAgent von %s...", cfg.MeshCentralUrl)
if err := meshagent.Install(context.Background(), cfg.MeshCentralUrl); err != nil {
log.Printf("MeshAgent-Installation fehlgeschlagen (nicht kritisch): %v", err)
}
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ticker := time.NewTicker(time.Duration(cfg.HeartbeatInterval) * time.Second)
defer ticker.Stop()
log.Printf("Agent running. Heartbeat every %ds", cfg.HeartbeatInterval)
doHeartbeat(ctx, client, cfg)
for {
select {
case <-ticker.C:
doHeartbeat(ctx, client, cfg)
case <-stop:
log.Println("Agent wird beendet...")
return
case <-ctx.Done():
return
}
}
}
}
func doHeartbeat(ctx context.Context, client *connection.GrpcClient, cfg *config.Config) {
metrics, err := collector.Collect()
if err != nil {
log.Printf("Metric collection error: %v", err)
return
}
req := &pb.HeartbeatRequest{
AgentId: cfg.AgentID,
Metrics: &pb.SystemMetrics{
CpuUsagePercent: metrics.CPUPercent,
MemoryUsagePercent: metrics.MemoryPercent,
MemoryTotalBytes: int64(metrics.MemoryTotal),
MemoryAvailableBytes: int64(metrics.MemoryAvailable),
UptimeSeconds: metrics.UptimeSeconds,
},
}
for _, d := range metrics.Disks {
req.Metrics.Disks = append(req.Metrics.Disks, &pb.DiskInfo{
MountPoint: d.MountPoint,
TotalBytes: int64(d.Total),
FreeBytes: int64(d.Free),
Filesystem: d.Filesystem,
})
}
resp, err := client.Client.Heartbeat(ctx, req)
if err != nil {
log.Printf("Heartbeat error: %v", err)
return
}
for _, cmd := range resp.PendingCommands {
log.Printf("Executing command %s (type: %v)", cmd.CommandId, cmd.Type)
go executeCommand(ctx, client, cfg, cmd)
}
}
func executeCommand(ctx context.Context, client *connection.GrpcClient, cfg *config.Config, cmd *pb.AgentCommand) {
agentID := cfg.AgentID
var result *executor.Result
switch cmd.Type {
case pb.CommandType_COMMAND_TYPE_SHELL:
result = executor.Execute(ctx, cmd.Payload, 300)
case pb.CommandType_COMMAND_TYPE_INSTALL_SOFTWARE:
result = deployer.Install(ctx, cmd.Payload)
case pb.CommandType_COMMAND_TYPE_UNINSTALL_SOFTWARE:
result = deployer.Uninstall(ctx, cmd.Payload)
case pb.CommandType_COMMAND_TYPE_NETWORK_SCAN:
var params struct {
Subnet string `json:"subnet"`
}
_ = json.Unmarshal([]byte(cmd.Payload), &params)
devices, err := scanner.Scan(ctx, params.Subnet)
if err != nil {
result = &executor.Result{ExitCode: 1, Stderr: err.Error()}
} else {
result = &executor.Result{
ExitCode: 0,
Stdout: scanner.ToJSON(devices),
Success: true,
}
}
case pb.CommandType_COMMAND_TYPE_UPDATE_AGENT:
var params struct {
ServerAddress string `json:"serverAddress"`
}
_ = json.Unmarshal([]byte(cmd.Payload), &params)
if params.ServerAddress == "" {
params.ServerAddress = strings.Replace(cfg.ServerAddress, "5001", "5000", 1)
}
info, err := updater.CheckForUpdate(ctx, params.ServerAddress, version)
if err != nil {
result = &executor.Result{ExitCode: 1, Stderr: err.Error()}
} else if info == nil {
result = &executor.Result{ExitCode: 0, Stdout: "Bereits aktuell: " + version, Success: true}
} else {
if updateErr := updater.Update(ctx, info); updateErr != nil {
result = &executor.Result{ExitCode: 1, Stderr: updateErr.Error()}
} else {
result = &executor.Result{ExitCode: 0, Stdout: "Update auf " + info.Version + " gestartet", Success: true}
}
}
default:
result = &executor.Result{ExitCode: -1, Stderr: fmt.Sprintf("unknown command type: %v", cmd.Type)}
}
if err := func() error {
_, err := client.Client.ReportCommandResult(ctx, &pb.CommandResult{
AgentId: agentID,
CommandId: cmd.CommandId,
ExitCode: int32(result.ExitCode),
Stdout: result.Stdout,
Stderr: result.Stderr,
Success: result.Success,
})
return err
}(); err != nil {
log.Printf("Failed to report result for %s: %v", cmd.CommandId, err)
}
}