feat: Fleet Forge UEM - device management, agent, remote desktop (VNC), TLS reverse proxy

This commit is contained in:
Pepe44DEV
2026-09-06 22:09:28 +00:00
commit db16ad5cc1
154 changed files with 22920 additions and 0 deletions
@@ -0,0 +1,142 @@
//go:build windows
package winsvc
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/fleetforge/forge-agent/internal/config"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/eventlog"
"golang.org/x/sys/windows/svc/mgr"
)
func InstallService(serverURL, enrollmentToken, label string) error {
exePath, err := os.Executable()
if err != nil {
return fmt.Errorf("failed to get executable path: %w", err)
}
exePath, err = filepath.EvalSymlinks(exePath)
if err != nil {
return fmt.Errorf("failed to resolve executable path: %w", err)
}
cfg := &config.Config{
ServerURL: serverURL,
EnrollmentToken: enrollmentToken,
Label: label,
HeartbeatSec: 30,
}
if err := config.Save(cfg); err != nil {
return fmt.Errorf("failed to save config: %w", err)
}
fmt.Printf("Config saved to: %s\n", config.ConfigPath())
scm, err := mgr.Connect()
if err != nil {
return fmt.Errorf("failed to connect to Service Control Manager: %w", err)
}
defer scm.Disconnect()
existing, err := scm.OpenService(ServiceName)
if err == nil {
existing.Close()
return fmt.Errorf("service already exists: %s", ServiceName)
}
s, err := scm.CreateService(ServiceName, exePath, mgr.Config{
DisplayName: ServiceDisplayName,
Description: ServiceDescription,
StartType: mgr.StartAutomatic,
ErrorControl: mgr.ErrorNormal,
}, "run", "--service")
if err != nil {
return fmt.Errorf("failed to create service: %w", err)
}
defer s.Close()
if err := eventlog.InstallAsEventCreate(ServiceName, eventlog.Error|eventlog.Warning|eventlog.Info); err != nil {
s.Delete()
return fmt.Errorf("failed to install event log source: %w", err)
}
fmt.Printf("Service '%s' installed successfully.\n", ServiceDisplayName)
fmt.Println("The service will start automatically on system boot.")
if err := s.Start(); err != nil {
fmt.Printf("Warning: Could not start service: %v\n", err)
} else {
waitUntilRunning(s)
fmt.Println("Service started.")
}
return nil
}
func UninstallService() error {
scm, err := mgr.Connect()
if err != nil {
return fmt.Errorf("failed to connect to Service Control Manager: %w", err)
}
defer scm.Disconnect()
s, err := scm.OpenService(ServiceName)
if err != nil {
return fmt.Errorf("service does not exist: %s", ServiceName)
}
defer s.Close()
st, err := s.Control(svc.Stop)
if err == nil {
waitUntilStopped(s, st)
}
if err := eventlog.Remove(ServiceName); err != nil {
fmt.Printf("Warning: Could not remove event log source: %v\n", err)
}
if err := s.Delete(); err != nil {
return fmt.Errorf("failed to delete service: %w", err)
}
fmt.Printf("Service '%s' removed successfully.\n", ServiceDisplayName)
if err := config.Delete(); err != nil && !os.IsNotExist(err) {
fmt.Printf("Warning: Could not remove config file: %v\n", err)
} else {
fmt.Println("Config file removed.")
}
return nil
}
func waitUntilStopped(s *mgr.Service, initialStatus svc.Status) {
state := initialStatus.State
for i := 0; i < 30; i++ {
if state == svc.Stopped {
return
}
time.Sleep(time.Second)
st, err := s.Query()
if err != nil {
return
}
state = st.State
}
}
func waitUntilRunning(s *mgr.Service) {
for i := 0; i < 30; i++ {
st, err := s.Query()
if err != nil {
return
}
if st.State == svc.Running {
return
}
time.Sleep(time.Second)
}
}
@@ -0,0 +1,76 @@
//go:build windows
package winsvc
import (
"fmt"
"log"
"time"
"github.com/fleetforge/forge-agent/internal/agent"
"github.com/fleetforge/forge-agent/internal/config"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/eventlog"
)
const ServiceName = "FleetForgeAgent"
const ServiceDisplayName = "Fleet Forge Agent"
const ServiceDescription = "Fleet Forge endpoint management agent."
type agentService struct {
cfg *config.Config
}
func (s *agentService) Execute(args []string, r <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) {
status <- svc.Status{State: svc.StartPending, Accepts: svc.AcceptStop | svc.AcceptShutdown}
elog, err := eventlog.Open(ServiceName)
if err == nil {
defer elog.Close()
elog.Info(1, "Fleet Forge Agent service starting")
}
a := agent.New(s.cfg)
done := make(chan error, 1)
go func() {
done <- a.Run()
}()
status <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}
for {
select {
case err := <-done:
if err != nil {
log.Printf("[service] Agent stopped with error: %v", err)
if elog != nil {
elog.Error(1, fmt.Sprintf("Agent stopped: %v", err))
}
}
return false, 0
case c := <-r:
switch c.Cmd {
case svc.Stop, svc.Shutdown:
log.Println("[service] Stop/Shutdown received")
status <- svc.Status{State: svc.StopPending}
return false, 0
case svc.Interrogate:
status <- c.CurrentStatus
}
case <-time.After(5 * time.Second):
}
}
}
func RunAsService() error {
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
svcInst := &agentService{cfg: cfg}
return svc.Run(ServiceName, svcInst)
}
@@ -0,0 +1,17 @@
//go:build !windows
package winsvc
import "fmt"
func RunAsService() error {
return fmt.Errorf("windows service is not supported on this platform")
}
func InstallService(serverURL, enrollmentToken, label string) error {
return fmt.Errorf("windows service installation is not supported on this platform")
}
func UninstallService() error {
return fmt.Errorf("windows service uninstallation is not supported on this platform")
}