feat: Phase 9 — Offline Detection, API Key Auth, Agent Self-Update
Offline Detection (9.1):
- AgentOfflineDetectorService: BackgroundService, prüft alle 60s
ob Agents seit >5 min kein Heartbeat hatten → Status=Offline
- IServiceScopeFactory für korrektes Scoped-DI im Singleton
- SignalR-Push AgentStatusChanged bei jeder Offline-Markierung
API Key Auth (9.2):
- ApiKeyMiddleware: prüft X-Api-Key Header gegen Security:ApiKey Config
- Deaktiviert wenn ApiKey leer (Dev-Modus), Swagger/hubs bypassed
- Frontend: getApiKey() aus localStorage, automatisch in allen Requests
- Settings-Modal in Sidebar: API-Key eingeben + maskiert anzeigen
Agent Self-Update (9.3):
- internal/updater/updater.go: CheckForUpdate() + Update()
Download, SHA256-Verify, Windows Batch-Neustart / Linux Shell-Neustart
- AgentReleasesController: GET /api/v1/agent/releases/latest,
GET /api/v1/agent/releases/download/{platform}
- AgentReleaseOptions: LatestVersion, ReleasePath, Checksum in appsettings
- executeCommand() erhält cfg *Config statt agentID string
(für ServerAddress-Ableitung im UpdateAgent-Case)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ import (
|
||||
"nexusrmm.local/agent/internal/executor"
|
||||
"nexusrmm.local/agent/internal/meshagent"
|
||||
"nexusrmm.local/agent/internal/scanner"
|
||||
"nexusrmm.local/agent/internal/updater"
|
||||
pb "nexusrmm.local/agent/pkg/proto"
|
||||
)
|
||||
|
||||
@@ -142,11 +143,12 @@ func doHeartbeat(ctx context.Context, client *connection.GrpcClient, cfg *config
|
||||
|
||||
for _, cmd := range resp.PendingCommands {
|
||||
log.Printf("Executing command %s (type: %v)", cmd.CommandId, cmd.Type)
|
||||
go executeCommand(ctx, client, cfg.AgentID, cmd)
|
||||
go executeCommand(ctx, client, cfg, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func executeCommand(ctx context.Context, client *connection.GrpcClient, agentID string, cmd *pb.AgentCommand) {
|
||||
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:
|
||||
@@ -170,6 +172,28 @@ func executeCommand(ctx context.Context, client *connection.GrpcClient, agentID
|
||||
Success: true,
|
||||
}
|
||||
}
|
||||
case pb.CommandType_COMMAND_TYPE_UPDATE_AGENT:
|
||||
// Payload optional: {"serverAddress": "localhost:5000"}
|
||||
var params struct {
|
||||
ServerAddress string `json:"serverAddress"`
|
||||
}
|
||||
_ = json.Unmarshal([]byte(cmd.Payload), ¶ms)
|
||||
if params.ServerAddress == "" {
|
||||
// Aus gRPC-Adresse REST-Adresse ableiten (Port 5000 statt 5001)
|
||||
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)}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user