mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-30 20:40:09 +00:00
The standalone pulse-host-agent was missing self-update functionality that existed in pulse-docker-agent and the unified pulse-agent. Changes: - Add agentupdate integration to pulse-host-agent - Add --no-auto-update flag and PULSE_NO_AUTO_UPDATE env var - Update Windows service to use errgroup pattern with auto-updater - Move version from internal/hostagent to main package for ldflags Related to #737
188 lines
4.8 KiB
Go
188 lines
4.8 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/agentupdate"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/hostagent"
|
|
"github.com/rs/zerolog"
|
|
"golang.org/x/sync/errgroup"
|
|
"golang.org/x/sys/windows/svc"
|
|
"golang.org/x/sys/windows/svc/eventlog"
|
|
)
|
|
|
|
type windowsService struct {
|
|
cfg Config
|
|
logger zerolog.Logger
|
|
eventLog *eventlog.Log
|
|
}
|
|
|
|
func (ws *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
|
|
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
|
|
|
|
changes <- svc.Status{State: svc.StartPending}
|
|
|
|
// Log to Windows Event Log
|
|
if ws.eventLog != nil {
|
|
ws.eventLog.Info(1, "Pulse Host Agent service starting")
|
|
}
|
|
|
|
hostCfg := ws.cfg.HostConfig
|
|
hostCfg.Logger = &ws.logger
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
g, ctx := errgroup.WithContext(ctx)
|
|
|
|
// Start Auto-Updater
|
|
updater := agentupdate.New(agentupdate.Config{
|
|
PulseURL: hostCfg.PulseURL,
|
|
APIToken: hostCfg.APIToken,
|
|
AgentName: "pulse-host-agent",
|
|
CurrentVersion: Version,
|
|
CheckInterval: 1 * time.Hour,
|
|
InsecureSkipVerify: hostCfg.InsecureSkipVerify,
|
|
Logger: &ws.logger,
|
|
Disabled: ws.cfg.DisableAutoUpdate,
|
|
})
|
|
|
|
g.Go(func() error {
|
|
updater.RunLoop(ctx)
|
|
return nil
|
|
})
|
|
|
|
// Start the host agent
|
|
agent, err := hostagent.New(hostCfg)
|
|
if err != nil {
|
|
ws.logger.Error().Err(err).Msg("Failed to create host agent")
|
|
if ws.eventLog != nil {
|
|
ws.eventLog.Error(1, fmt.Sprintf("Failed to create host agent: %v", err))
|
|
}
|
|
changes <- svc.Status{State: svc.Stopped}
|
|
return true, 1
|
|
}
|
|
|
|
g.Go(func() error {
|
|
ws.logger.Info().
|
|
Str("version", Version).
|
|
Str("pulse_url", hostCfg.PulseURL).
|
|
Str("agent_id", hostCfg.AgentID).
|
|
Dur("interval", hostCfg.Interval).
|
|
Bool("auto_update", !ws.cfg.DisableAutoUpdate).
|
|
Msg("Starting Pulse host agent as Windows service")
|
|
return agent.Run(ctx)
|
|
})
|
|
|
|
// Channel to receive errgroup completion
|
|
doneChan := make(chan error, 1)
|
|
go func() {
|
|
doneChan <- g.Wait()
|
|
}()
|
|
|
|
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
|
|
ws.logger.Info().Msg("Host agent service is running")
|
|
if ws.eventLog != nil {
|
|
ws.eventLog.Info(1, fmt.Sprintf("Pulse Host Agent started successfully (URL: %s, Interval: %s)", hostCfg.PulseURL, hostCfg.Interval))
|
|
}
|
|
|
|
// Service control loop
|
|
loop:
|
|
for {
|
|
select {
|
|
case c := <-r:
|
|
switch c.Cmd {
|
|
case svc.Interrogate:
|
|
changes <- c.CurrentStatus
|
|
case svc.Stop, svc.Shutdown:
|
|
ws.logger.Info().Uint32("command", uint32(c.Cmd)).Msg("Received service control command")
|
|
if ws.eventLog != nil {
|
|
ws.eventLog.Info(1, "Pulse Host Agent received stop command")
|
|
}
|
|
changes <- svc.Status{State: svc.StopPending}
|
|
cancel()
|
|
break loop
|
|
default:
|
|
ws.logger.Warn().Uint32("command", uint32(c.Cmd)).Msg("Unexpected service control command")
|
|
}
|
|
case err := <-doneChan:
|
|
if err != nil && err != context.Canceled {
|
|
ws.logger.Error().Err(err).Msg("Agent error")
|
|
if ws.eventLog != nil {
|
|
ws.eventLog.Error(1, fmt.Sprintf("Pulse Host Agent error: %v", err))
|
|
}
|
|
changes <- svc.Status{State: svc.Stopped}
|
|
return true, 1
|
|
}
|
|
break loop
|
|
}
|
|
}
|
|
|
|
// Wait for agent to stop gracefully (with timeout)
|
|
shutdownTimeout := time.NewTimer(10 * time.Second)
|
|
defer shutdownTimeout.Stop()
|
|
|
|
select {
|
|
case <-doneChan:
|
|
ws.logger.Info().Msg("Agent stopped gracefully")
|
|
if ws.eventLog != nil {
|
|
ws.eventLog.Info(1, "Pulse Host Agent stopped gracefully")
|
|
}
|
|
case <-shutdownTimeout.C:
|
|
ws.logger.Warn().Msg("Agent shutdown timeout, forcing stop")
|
|
if ws.eventLog != nil {
|
|
ws.eventLog.Warning(1, "Pulse Host Agent shutdown timeout")
|
|
}
|
|
}
|
|
|
|
changes <- svc.Status{State: svc.Stopped}
|
|
return false, 0
|
|
}
|
|
|
|
func runAsWindowsService(cfg Config, logger zerolog.Logger) error {
|
|
// Check if we're running as a Windows service
|
|
isService, err := svc.IsWindowsService()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to determine if running as service: %w", err)
|
|
}
|
|
|
|
if !isService {
|
|
// Not running as a service, run normally
|
|
return nil
|
|
}
|
|
|
|
logger.Info().Msg("Running as Windows service")
|
|
|
|
// Open Windows Event Log (best effort - don't fail if it doesn't work)
|
|
elog, err := eventlog.Open("PulseHostAgent")
|
|
if err != nil {
|
|
logger.Warn().Err(err).Msg("Could not open Windows Event Log, continuing without it")
|
|
elog = nil
|
|
}
|
|
defer func() {
|
|
if elog != nil {
|
|
elog.Close()
|
|
}
|
|
}()
|
|
|
|
ws := &windowsService{
|
|
cfg: cfg,
|
|
logger: logger,
|
|
eventLog: elog,
|
|
}
|
|
|
|
// Run as a Windows service
|
|
err = svc.Run("PulseHostAgent", ws)
|
|
if err != nil {
|
|
if elog != nil {
|
|
elog.Error(1, fmt.Sprintf("Failed to run service: %v", err))
|
|
}
|
|
return fmt.Errorf("failed to run Windows service: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|