feat: Phase 7 — MeshCentral Remote Desktop Integration
Backend:
- MeshCentralOptions + MeshCentralService: Node-Lookup via Hostname, Remote-Desktop-URL-Generierung
- RemoteDesktopController: GET /api/v1/agents/{id}/remote-session mit 3 Status-Zuständen (nicht konfiguriert / Agent fehlt / bereit)
- Program.cs: HttpClient + MeshCentralService registriert, appsettings.json mit Konfigurationsblock
Go Agent:
- config.go: MeshCentralUrl + MeshEnabled Felder
- internal/meshagent/installer.go: MeshAgent Download + Installation (Windows Service / Linux systemd)
- main.go: Automatische MeshAgent-Installation nach Enrollment wenn aktiviert
Frontend:
- RemoteDesktopButton: Modales Dialog mit 3 Zustandsanzeigen (Setup nötig / Agent installieren / Remote Desktop öffnen)
- AgentDetailPage: RemoteDesktopButton im Header integriert
- api/types.ts + api/client.ts: RemoteSessionInfo Typ + remoteDesktopApi
docker-compose.yml: MeshCentral Service (ghcr.io/ylianst/meshcentral:latest, Ports 4430/4431)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
149
Agent/internal/meshagent/installer.go
Normal file
149
Agent/internal/meshagent/installer.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package meshagent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// IsInstalled prüft ob der MeshAgent-Prozess läuft oder das Binary vorhanden ist.
|
||||
func IsInstalled() bool {
|
||||
if runtime.GOOS == "windows" {
|
||||
// Prüfe ob MeshAgent Service läuft
|
||||
cmd := exec.Command("sc", "query", "Mesh Agent")
|
||||
return cmd.Run() == nil
|
||||
}
|
||||
// Linux: prüfe ob meshagent Prozess läuft
|
||||
cmd := exec.Command("pgrep", "-x", "meshagent")
|
||||
return cmd.Run() == nil
|
||||
}
|
||||
|
||||
// Install lädt den MeshAgent von MeshCentral herunter und installiert ihn.
|
||||
// meshCentralUrl: z.B. "https://192.168.1.100:4430"
|
||||
func Install(ctx context.Context, meshCentralUrl string) error {
|
||||
if IsInstalled() {
|
||||
log.Println("MeshAgent ist bereits installiert")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("MeshAgent wird von %s heruntergeladen...", meshCentralUrl)
|
||||
|
||||
// Agent-ID je nach OS
|
||||
agentID := "6" // Linux x64
|
||||
if runtime.GOOS == "windows" {
|
||||
agentID = "3" // Windows x64
|
||||
}
|
||||
|
||||
downloadUrl := fmt.Sprintf("%s/meshagents?id=%s", strings.TrimRight(meshCentralUrl, "/"), agentID)
|
||||
|
||||
// SSL-Fehler ignorieren (selbstsigniertes Zertifikat in Dev)
|
||||
httpClient := &http.Client{
|
||||
Timeout: 5 * time.Minute,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadUrl, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("HTTP Request konnte nicht erstellt werden: %w", err)
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Download fehlgeschlagen: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("MeshCentral antwortete mit Status %d — ist MeshCentral gestartet?", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Temp-Datei speichern
|
||||
var tmpPath string
|
||||
if runtime.GOOS == "windows" {
|
||||
tmpPath = filepath.Join(os.TempDir(), "meshagent.exe")
|
||||
} else {
|
||||
tmpPath = filepath.Join(os.TempDir(), "meshagent")
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Temp-Datei konnte nicht erstellt werden: %w", err)
|
||||
}
|
||||
if _, err := io.Copy(f, resp.Body); err != nil {
|
||||
f.Close()
|
||||
return fmt.Errorf("Fehler beim Schreiben: %w", err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
log.Printf("MeshAgent heruntergeladen nach %s", tmpPath)
|
||||
|
||||
// Installieren
|
||||
return installBinary(ctx, tmpPath, meshCentralUrl)
|
||||
}
|
||||
|
||||
func installBinary(ctx context.Context, binaryPath, meshCentralUrl string) error {
|
||||
if runtime.GOOS == "windows" {
|
||||
// Windows: als Service installieren
|
||||
cmd := exec.CommandContext(ctx, binaryPath, "-install", "-url", meshCentralUrl)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("MeshAgent Windows-Installation fehlgeschlagen: %w", err)
|
||||
}
|
||||
log.Println("MeshAgent als Windows Service installiert")
|
||||
} else {
|
||||
// Linux: in /usr/local/bin installieren und als Service starten
|
||||
installPath := "/usr/local/bin/meshagent"
|
||||
if err := os.Rename(binaryPath, installPath); err != nil {
|
||||
// Falls rename scheitert (cross-device), kopieren
|
||||
if err2 := copyFile(binaryPath, installPath); err2 != nil {
|
||||
return fmt.Errorf("MeshAgent konnte nicht nach %s verschoben werden: %w", installPath, err2)
|
||||
}
|
||||
}
|
||||
if err := os.Chmod(installPath, 0755); err != nil {
|
||||
return fmt.Errorf("chmod fehlgeschlagen: %w", err)
|
||||
}
|
||||
|
||||
// Als Service starten (systemd oder direkt)
|
||||
cmd := exec.CommandContext(ctx, installPath, "-install", "-url", meshCentralUrl)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
// Direkt starten als Fallback
|
||||
log.Printf("Service-Installation fehlgeschlagen, starte direkt: %v", err)
|
||||
go func() {
|
||||
exec.Command(installPath, "-url", meshCentralUrl).Run()
|
||||
}()
|
||||
}
|
||||
log.Println("MeshAgent auf Linux installiert")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) error {
|
||||
srcFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
dstFile, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
_, err = io.Copy(dstFile, srcFile)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user