feat: Windows Service Wrapper für NexusRMM Agent
- winsvc package mit Build-Tag-basierter plattformspezifischer Implementierung: - service_windows.go: svc.Handler + Install/Uninstall/Start/Stop via windows/svc/mgr - service_stub.go: Stub-Implementierungen für nicht-Windows Builds - main.go refaktoriert: - os.Executable() für absoluten Config-Pfad (Service-Modus: CWD = C:\Windows\System32) - Kommandozeilen-Args: install / uninstall / start / stop - winsvc.IsWindowsService() Erkennung → Service-Modus oder Konsolen-Modus - Agent-Loop als makeRunFn() extrahiert (wiederverwendbar für beide Modi) - install-service.ps1: Convenience-Skript zum Bauen, Installieren und Starten Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -20,15 +21,92 @@ import (
|
|||||||
"nexusrmm.local/agent/internal/meshagent"
|
"nexusrmm.local/agent/internal/meshagent"
|
||||||
"nexusrmm.local/agent/internal/scanner"
|
"nexusrmm.local/agent/internal/scanner"
|
||||||
"nexusrmm.local/agent/internal/updater"
|
"nexusrmm.local/agent/internal/updater"
|
||||||
|
"nexusrmm.local/agent/internal/winsvc"
|
||||||
pb "nexusrmm.local/agent/pkg/proto"
|
pb "nexusrmm.local/agent/pkg/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "dev"
|
var version = "dev"
|
||||||
|
|
||||||
func main() {
|
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)
|
log.Printf("NexusRMM Agent %s starting on %s/%s", version, runtime.GOOS, runtime.GOARCH)
|
||||||
|
|
||||||
cfg, err := config.Load("nexus-agent.yaml")
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("Config load error: %v", err)
|
log.Fatalf("Config load error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -42,7 +120,6 @@ func main() {
|
|||||||
if cfg.AgentID == "" {
|
if cfg.AgentID == "" {
|
||||||
hostname, _ := os.Hostname()
|
hostname, _ := os.Hostname()
|
||||||
metrics, _ := collector.Collect()
|
metrics, _ := collector.Collect()
|
||||||
// Beste Netzwerkschnittstelle für Enrollment wählen (bevorzuge IPv4)
|
|
||||||
mac, ip := "", ""
|
mac, ip := "", ""
|
||||||
for _, n := range metrics.Networks {
|
for _, n := range metrics.Networks {
|
||||||
if n.MAC == "" || n.IPAddress == "" {
|
if n.MAC == "" || n.IPAddress == "" {
|
||||||
@@ -51,7 +128,6 @@ func main() {
|
|||||||
if mac == "" {
|
if mac == "" {
|
||||||
mac, ip = n.MAC, n.IPAddress
|
mac, ip = n.MAC, n.IPAddress
|
||||||
}
|
}
|
||||||
// Sobald IPv4 gefunden: nehmen und fertig
|
|
||||||
if !strings.Contains(n.IPAddress, ":") {
|
if !strings.Contains(n.IPAddress, ":") {
|
||||||
mac, ip = n.MAC, n.IPAddress
|
mac, ip = n.MAC, n.IPAddress
|
||||||
break
|
break
|
||||||
@@ -71,13 +147,12 @@ func main() {
|
|||||||
}
|
}
|
||||||
cfg.AgentID = resp.AgentId
|
cfg.AgentID = resp.AgentId
|
||||||
cfg.HeartbeatInterval = int(resp.HeartbeatInterval)
|
cfg.HeartbeatInterval = int(resp.HeartbeatInterval)
|
||||||
if err := cfg.Save("nexus-agent.yaml"); err != nil {
|
if err := cfg.Save(configPath); err != nil {
|
||||||
log.Printf("Warning: could not save config: %v", err)
|
log.Printf("Warning: could not save config: %v", err)
|
||||||
}
|
}
|
||||||
log.Printf("Enrolled with ID: %s", cfg.AgentID)
|
log.Printf("Enrolled with ID: %s", cfg.AgentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MeshAgent installieren falls konfiguriert
|
|
||||||
if cfg.MeshEnabled && cfg.MeshCentralUrl != "" {
|
if cfg.MeshEnabled && cfg.MeshCentralUrl != "" {
|
||||||
log.Printf("Installiere MeshAgent von %s...", cfg.MeshCentralUrl)
|
log.Printf("Installiere MeshAgent von %s...", cfg.MeshCentralUrl)
|
||||||
if err := meshagent.Install(context.Background(), cfg.MeshCentralUrl); err != nil {
|
if err := meshagent.Install(context.Background(), cfg.MeshCentralUrl); err != nil {
|
||||||
@@ -87,8 +162,6 @@ func main() {
|
|||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
sigCh := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
|
|
||||||
ticker := time.NewTicker(time.Duration(cfg.HeartbeatInterval) * time.Second)
|
ticker := time.NewTicker(time.Duration(cfg.HeartbeatInterval) * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
@@ -100,14 +173,15 @@ func main() {
|
|||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
doHeartbeat(ctx, client, cfg)
|
doHeartbeat(ctx, client, cfg)
|
||||||
case <-sigCh:
|
case <-stop:
|
||||||
log.Println("Shutting down...")
|
log.Println("Agent wird beendet...")
|
||||||
return
|
return
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func doHeartbeat(ctx context.Context, client *connection.GrpcClient, cfg *config.Config) {
|
func doHeartbeat(ctx context.Context, client *connection.GrpcClient, cfg *config.Config) {
|
||||||
metrics, err := collector.Collect()
|
metrics, err := collector.Collect()
|
||||||
@@ -173,13 +247,11 @@ func executeCommand(ctx context.Context, client *connection.GrpcClient, cfg *con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case pb.CommandType_COMMAND_TYPE_UPDATE_AGENT:
|
case pb.CommandType_COMMAND_TYPE_UPDATE_AGENT:
|
||||||
// Payload optional: {"serverAddress": "localhost:5000"}
|
|
||||||
var params struct {
|
var params struct {
|
||||||
ServerAddress string `json:"serverAddress"`
|
ServerAddress string `json:"serverAddress"`
|
||||||
}
|
}
|
||||||
_ = json.Unmarshal([]byte(cmd.Payload), ¶ms)
|
_ = json.Unmarshal([]byte(cmd.Payload), ¶ms)
|
||||||
if params.ServerAddress == "" {
|
if params.ServerAddress == "" {
|
||||||
// Aus gRPC-Adresse REST-Adresse ableiten (Port 5000 statt 5001)
|
|
||||||
params.ServerAddress = strings.Replace(cfg.ServerAddress, "5001", "5000", 1)
|
params.ServerAddress = strings.Replace(cfg.ServerAddress, "5001", "5000", 1)
|
||||||
}
|
}
|
||||||
info, err := updater.CheckForUpdate(ctx, params.ServerAddress, version)
|
info, err := updater.CheckForUpdate(ctx, params.ServerAddress, version)
|
||||||
|
|||||||
33
Agent/internal/winsvc/service_stub.go
Normal file
33
Agent/internal/winsvc/service_stub.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package winsvc
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
const (
|
||||||
|
ServiceName = "NexusRMMAgent"
|
||||||
|
ServiceDisplayName = "NexusRMM Agent"
|
||||||
|
ServiceDescription = "NexusRMM Remote Monitoring and Management Agent"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsWindowsService() (bool, error) { return false, nil }
|
||||||
|
|
||||||
|
func Run(_ func(stop <-chan struct{})) error {
|
||||||
|
return errors.New("Windows Service wird nur unter Windows unterstützt")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Install(_ string) error {
|
||||||
|
return errors.New("Windows Service wird nur unter Windows unterstützt")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uninstall() error {
|
||||||
|
return errors.New("Windows Service wird nur unter Windows unterstützt")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start() error {
|
||||||
|
return errors.New("Windows Service wird nur unter Windows unterstützt")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Stop() error {
|
||||||
|
return errors.New("Windows Service wird nur unter Windows unterstützt")
|
||||||
|
}
|
||||||
169
Agent/internal/winsvc/service_windows.go
Normal file
169
Agent/internal/winsvc/service_windows.go
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package winsvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows/svc"
|
||||||
|
"golang.org/x/sys/windows/svc/mgr"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ServiceName = "NexusRMMAgent"
|
||||||
|
ServiceDisplayName = "NexusRMM Agent"
|
||||||
|
ServiceDescription = "NexusRMM Remote Monitoring and Management Agent"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsWindowsService gibt true zurück wenn der Prozess als Windows Service läuft.
|
||||||
|
func IsWindowsService() (bool, error) {
|
||||||
|
return svc.IsWindowsService()
|
||||||
|
}
|
||||||
|
|
||||||
|
type agentSvc struct {
|
||||||
|
runFn func(stop <-chan struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *agentSvc) Execute(_ []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
|
||||||
|
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
|
||||||
|
|
||||||
|
changes <- svc.Status{State: svc.StartPending}
|
||||||
|
|
||||||
|
stop := make(chan struct{})
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
s.runFn(stop)
|
||||||
|
}()
|
||||||
|
|
||||||
|
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case c := <-r:
|
||||||
|
switch c.Cmd {
|
||||||
|
case svc.Stop, svc.Shutdown:
|
||||||
|
changes <- svc.Status{State: svc.StopPending}
|
||||||
|
close(stop)
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
case <-done:
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auf Agent-Shutdown warten (max 10s)
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
}
|
||||||
|
return false, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run führt den Agent als Windows Service aus.
|
||||||
|
// runFn wird in einer Goroutine gestartet; stop wird geschlossen wenn der Service gestoppt wird.
|
||||||
|
func Run(runFn func(stop <-chan struct{})) error {
|
||||||
|
return svc.Run(ServiceName, &agentSvc{runFn: runFn})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install registriert den Service im Windows Service Manager.
|
||||||
|
// exePath muss der absolute Pfad zur nexus-agent.exe sein.
|
||||||
|
func Install(exePath string) error {
|
||||||
|
m, err := mgr.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Service Manager Verbindung fehlgeschlagen: %w", err)
|
||||||
|
}
|
||||||
|
defer m.Disconnect()
|
||||||
|
|
||||||
|
// Prüfen ob Service bereits existiert
|
||||||
|
existing, err := m.OpenService(ServiceName)
|
||||||
|
if err == nil {
|
||||||
|
existing.Close()
|
||||||
|
return fmt.Errorf("Service '%s' ist bereits installiert", ServiceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := m.CreateService(ServiceName, exePath, mgr.Config{
|
||||||
|
DisplayName: ServiceDisplayName,
|
||||||
|
Description: ServiceDescription,
|
||||||
|
StartType: mgr.StartAutomatic,
|
||||||
|
ErrorControl: mgr.ServiceRestart,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Service erstellen fehlgeschlagen: %w", err)
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uninstall entfernt den Service (stoppt ihn vorher falls nötig).
|
||||||
|
func Uninstall() error {
|
||||||
|
m, err := mgr.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Service Manager Verbindung fehlgeschlagen: %w", err)
|
||||||
|
}
|
||||||
|
defer m.Disconnect()
|
||||||
|
|
||||||
|
s, err := m.OpenService(ServiceName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Service '%s' nicht gefunden: %w", ServiceName, err)
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
// Service stoppen falls er läuft
|
||||||
|
status, err := s.Query()
|
||||||
|
if err == nil && status.State != svc.Stopped {
|
||||||
|
if _, err := s.Control(svc.Stop); err == nil {
|
||||||
|
// Kurz warten bis er gestoppt ist
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
st, e := s.Query()
|
||||||
|
if e != nil || st.State == svc.Stopped {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Delete(); err != nil {
|
||||||
|
return fmt.Errorf("Service löschen fehlgeschlagen: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start startet den installierten Service.
|
||||||
|
func Start() error {
|
||||||
|
m, err := mgr.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Service Manager Verbindung fehlgeschlagen: %w", err)
|
||||||
|
}
|
||||||
|
defer m.Disconnect()
|
||||||
|
|
||||||
|
s, err := m.OpenService(ServiceName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Service '%s' nicht gefunden: %w", ServiceName, err)
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
return s.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stoppt den laufenden Service.
|
||||||
|
func Stop() error {
|
||||||
|
m, err := mgr.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Service Manager Verbindung fehlgeschlagen: %w", err)
|
||||||
|
}
|
||||||
|
defer m.Disconnect()
|
||||||
|
|
||||||
|
s, err := m.OpenService(ServiceName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Service '%s' nicht gefunden: %w", ServiceName, err)
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
_, err = s.Control(svc.Stop)
|
||||||
|
return err
|
||||||
|
}
|
||||||
127
install-service.ps1
Normal file
127
install-service.ps1
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
#Requires -RunAsAdministrator
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Installiert den NexusRMM Agent als Windows Service
|
||||||
|
.DESCRIPTION
|
||||||
|
Baut den Agent, kopiert ihn ins Zielverzeichnis und registriert ihn als Windows Service.
|
||||||
|
Muss als Administrator ausgefuehrt werden.
|
||||||
|
.PARAMETER ServerAddress
|
||||||
|
gRPC-Adresse des NexusRMM Servers (Standard: localhost:5001)
|
||||||
|
.PARAMETER InstallDir
|
||||||
|
Installationsverzeichnis (Standard: C:\Program Files\NexusRMM)
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[string]$ServerAddress = "localhost:5001",
|
||||||
|
[string]$InstallDir = "C:\Program Files\NexusRMM"
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
$Root = $PSScriptRoot
|
||||||
|
|
||||||
|
function Write-Step { param($msg) Write-Host "" ; Write-Host "==> $msg" -ForegroundColor Cyan }
|
||||||
|
function Write-OK { param($msg) Write-Host " [OK] $msg" -ForegroundColor Green }
|
||||||
|
function Write-Fail { param($msg) Write-Host " [FEHLER] $msg" -ForegroundColor Red }
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 1. Agent bauen
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
Write-Step "Baue NexusRMM Agent..."
|
||||||
|
|
||||||
|
Set-Location "$Root\Agent"
|
||||||
|
$env:GOOS = "windows"
|
||||||
|
$env:GOARCH = "amd64"
|
||||||
|
go build -ldflags "-s -w -X main.version=1.0.0" -o nexus-agent.exe ./cmd/agent
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Fail "go build fehlgeschlagen."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-OK "nexus-agent.exe erstellt."
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 2. Installationsverzeichnis anlegen
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
Write-Step "Lege Installationsverzeichnis an: $InstallDir"
|
||||||
|
|
||||||
|
if (-not (Test-Path $InstallDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
Write-OK "Verzeichnis bereit."
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 3. Dateien kopieren
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
Write-Step "Kopiere Dateien..."
|
||||||
|
|
||||||
|
Copy-Item "$Root\Agent\nexus-agent.exe" "$InstallDir\nexus-agent.exe" -Force
|
||||||
|
|
||||||
|
# Konfigurationsdatei anlegen falls nicht vorhanden
|
||||||
|
$configPath = "$InstallDir\nexus-agent.yaml"
|
||||||
|
if (-not (Test-Path $configPath)) {
|
||||||
|
@"
|
||||||
|
serverAddress: $ServerAddress
|
||||||
|
agentId: ""
|
||||||
|
heartbeatInterval: 30
|
||||||
|
meshEnabled: false
|
||||||
|
meshCentralUrl: ""
|
||||||
|
"@ | Out-File -FilePath $configPath -Encoding utf8
|
||||||
|
Write-OK "nexus-agent.yaml erstellt (serverAddress: $ServerAddress)."
|
||||||
|
} else {
|
||||||
|
Write-OK "nexus-agent.yaml bereits vorhanden, wird nicht ueberschrieben."
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 4. Bestehenden Service stoppen/deinstallieren falls vorhanden
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
$exePath = "$InstallDir\nexus-agent.exe"
|
||||||
|
|
||||||
|
$svcCheck = Get-Service -Name "NexusRMMAgent" -ErrorAction SilentlyContinue
|
||||||
|
if ($svcCheck) {
|
||||||
|
Write-Step "Entferne vorhandenen Service..."
|
||||||
|
& $exePath stop 2>$null
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
& $exePath uninstall
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Fail "Service-Deinstallation fehlgeschlagen."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-OK "Alter Service entfernt."
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 5. Service installieren und starten
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
Write-Step "Installiere Service..."
|
||||||
|
|
||||||
|
& $exePath install
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Fail "Service-Installation fehlgeschlagen."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-OK "Service installiert."
|
||||||
|
|
||||||
|
Write-Step "Starte Service..."
|
||||||
|
& $exePath start
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Fail "Service konnte nicht gestartet werden."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-OK "Service gestartet."
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Zusammenfassung
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "+--------------------------------------------------+" -ForegroundColor Green
|
||||||
|
Write-Host "| NexusRMM Agent als Service installiert! |" -ForegroundColor Green
|
||||||
|
Write-Host "+--------------------------------------------------+" -ForegroundColor Green
|
||||||
|
Write-Host "| Installiert in: $InstallDir" -ForegroundColor Green
|
||||||
|
Write-Host "| Server: $ServerAddress" -ForegroundColor Green
|
||||||
|
Write-Host "| Service-Name: NexusRMMAgent" -ForegroundColor Green
|
||||||
|
Write-Host "+--------------------------------------------------+" -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Verwalten:" -ForegroundColor Yellow
|
||||||
|
Write-Host " Start: & '$exePath' start" -ForegroundColor Yellow
|
||||||
|
Write-Host " Stop: & '$exePath' stop" -ForegroundColor Yellow
|
||||||
|
Write-Host " Entfernen: & '$exePath' uninstall" -ForegroundColor Yellow
|
||||||
|
Write-Host " Oder via: services.msc -> NexusRMMAgent" -ForegroundColor Yellow
|
||||||
|
Write-Host ""
|
||||||
Reference in New Issue
Block a user