Skip to content
This repository was archived by the owner on Feb 23, 2026. It is now read-only.

Commit 36aec47

Browse files
1.3.6
reboot detection fixes
2 parents baf0ff8 + 44f57bc commit 36aec47

11 files changed

Lines changed: 851 additions & 390 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
BINARY_NAME=patchmon-agent
55
BUILD_DIR=build
66
# Use hardcoded version instead of git tags
7-
VERSION=1.3.3
7+
VERSION=1.3.6
88
# Strip debug info and set version variable
99
LDFLAGS=-ldflags "-s -w -X patchmon-agent/internal/version.Version=$(VERSION)"
1010
# Disable VCS stamping

cmd/patchmon-agent/commands/report.go

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,21 @@ func sendReport() error {
8282
// Get network information
8383
logger.Info("Collecting network information...")
8484
networkInfo := networkMgr.GetNetworkInfo()
85+
// Ensure DNSServers is never nil (should be empty slice, not nil)
86+
if networkInfo.DNSServers == nil {
87+
networkInfo.DNSServers = []string{}
88+
}
89+
90+
// Check if reboot is required and get installed kernel
91+
logger.Info("Checking reboot status...")
92+
needsReboot, rebootReason := systemDetector.CheckRebootRequired()
93+
installedKernel := systemDetector.GetLatestInstalledKernel()
94+
logger.WithFields(logrus.Fields{
95+
"needs_reboot": needsReboot,
96+
"reason": rebootReason,
97+
"installed_kernel": installedKernel,
98+
"running_kernel": systemInfo.KernelVersion,
99+
}).Info("Reboot status check completed")
85100

86101
// Get package information
87102
logger.Info("Collecting package information...")
@@ -155,9 +170,10 @@ func sendReport() error {
155170
IP: ipAddress,
156171
Architecture: architecture,
157172
AgentVersion: version.Version,
158-
MachineID: systemDetector.GetMachineID(),
159-
KernelVersion: systemInfo.KernelVersion,
160-
SELinuxStatus: systemInfo.SELinuxStatus,
173+
MachineID: systemDetector.GetMachineID(),
174+
KernelVersion: systemInfo.KernelVersion,
175+
InstalledKernelVersion: installedKernel,
176+
SELinuxStatus: systemInfo.SELinuxStatus,
161177
SystemUptime: systemInfo.SystemUptime,
162178
LoadAverage: systemInfo.LoadAverage,
163179
CPUModel: hardwareInfo.CPUModel,
@@ -169,6 +185,8 @@ func sendReport() error {
169185
DNSServers: networkInfo.DNSServers,
170186
NetworkInterfaces: networkInfo.NetworkInterfaces,
171187
ExecutionTime: executionTime,
188+
NeedsReboot: needsReboot,
189+
RebootReason: rebootReason,
172190
}
173191

174192
// Send report
@@ -196,27 +214,40 @@ func sendReport() error {
196214
logger.WithError(err).Warn("PatchMon agent update failed, but data was sent successfully")
197215
} else {
198216
logger.Info("PatchMon agent update completed successfully")
217+
// updateAgent() will exit the process after restart, so we won't reach here
218+
// But if it does return, skip the update check to prevent loops
219+
return nil
199220
}
200221
} else {
201-
// Proactive update check after report
202-
logger.Info("Checking for agent updates...")
203-
versionInfo, err := getServerVersionInfo()
204-
if err != nil {
205-
logger.WithError(err).Warn("Failed to check for updates after report")
206-
} else if versionInfo.HasUpdate {
207-
logger.WithFields(logrus.Fields{
208-
"current": versionInfo.CurrentVersion,
209-
"latest": versionInfo.LatestVersion,
210-
}).Info("Update available, automatically updating...")
211-
212-
if err := updateAgent(); err != nil {
213-
logger.WithError(err).Warn("PatchMon agent update failed, but data was sent successfully")
222+
// Proactive update check after report (non-blocking with timeout)
223+
// Run in a goroutine to avoid blocking the report completion
224+
go func() {
225+
// Add a delay to prevent immediate checks after service restart
226+
// This gives the new process time to fully initialize
227+
time.Sleep(5 * time.Second)
228+
229+
logger.Info("Checking for agent updates...")
230+
versionInfo, err := getServerVersionInfo()
231+
if err != nil {
232+
logger.WithError(err).Warn("Failed to check for updates after report (non-critical)")
233+
return
234+
}
235+
if versionInfo.HasUpdate {
236+
logger.WithFields(logrus.Fields{
237+
"current": versionInfo.CurrentVersion,
238+
"latest": versionInfo.LatestVersion,
239+
}).Info("Update available, automatically updating...")
240+
241+
if err := updateAgent(); err != nil {
242+
logger.WithError(err).Warn("PatchMon agent update failed, but data was sent successfully")
243+
} else {
244+
logger.Info("PatchMon agent update completed successfully")
245+
// updateAgent() will exit after restart, so this won't be reached
246+
}
214247
} else {
215-
logger.Info("PatchMon agent update completed successfully")
248+
logger.WithField("version", versionInfo.CurrentVersion).Info("Agent is up to date")
216249
}
217-
} else {
218-
logger.WithField("version", versionInfo.CurrentVersion).Debug("Agent is up to date")
219-
}
250+
}()
220251
}
221252

222253
// Collect and send integration data (Docker, etc.) separately

cmd/patchmon-agent/commands/root.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ func init() {
5656
rootCmd.AddCommand(checkVersionCmd)
5757
rootCmd.AddCommand(updateAgentCmd)
5858
rootCmd.AddCommand(diagnosticsCmd)
59-
rootCmd.AddCommand(uninstallCmd)
59+
// Note: Uninstall functionality removed - use patchmon_remove.sh script instead
60+
// rootCmd.AddCommand(uninstallCmd)
6061
}
6162

6263
// initialiseAgent initialises the configuration manager and logger

cmd/patchmon-agent/commands/serve.go

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"fmt"
88
"net/http"
9+
"os"
910
"os/exec"
1011
"strings"
1112
"time"
@@ -330,18 +331,76 @@ func toggleIntegration(integrationName string, enabled bool) error {
330331

331332
logger.Info("Config updated, restarting patchmon-agent service...")
332333

333-
// Restart the service to apply changes
334-
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
334+
// Restart the service to apply changes (supports systemd and OpenRC)
335+
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
335336
defer cancel()
336337

337-
cmd := exec.CommandContext(ctx, "systemctl", "restart", "patchmon-agent")
338-
output, err := cmd.CombinedOutput()
339-
if err != nil {
340-
logger.WithError(err).Warn("Failed to restart service (this is not critical)")
341-
return fmt.Errorf("failed to restart service: %w, output: %s", err, string(output))
338+
if _, err := exec.LookPath("systemctl"); err == nil {
339+
// Systemd is available
340+
logger.Debug("Detected systemd, using systemctl restart")
341+
cmd := exec.CommandContext(ctx, "systemctl", "restart", "patchmon-agent")
342+
output, err := cmd.CombinedOutput()
343+
if err != nil {
344+
logger.WithError(err).Warn("Failed to restart service (this is not critical)")
345+
return fmt.Errorf("failed to restart service: %w, output: %s", err, string(output))
346+
}
347+
logger.WithField("output", string(output)).Debug("Service restart command completed")
348+
logger.Info("Service restarted successfully")
349+
return nil
350+
} else if _, err := exec.LookPath("rc-service"); err == nil {
351+
// OpenRC is available (Alpine Linux)
352+
// Since we're running inside the service, we can't stop ourselves directly
353+
// Instead, we'll create a helper script that runs after we exit
354+
logger.Debug("Detected OpenRC, scheduling service restart via helper script")
355+
356+
// Create a helper script that will restart the service after we exit
357+
helperScript := `#!/bin/sh
358+
# Wait a moment for the current process to exit
359+
sleep 2
360+
# Restart the service
361+
rc-service patchmon-agent restart 2>&1 || rc-service patchmon-agent start 2>&1
362+
# Clean up this script
363+
rm -f "$0"
364+
`
365+
helperPath := "/etc/patchmon/patchmon-restart-helper.sh"
366+
if err := os.WriteFile(helperPath, []byte(helperScript), 0755); err != nil {
367+
logger.WithError(err).Warn("Failed to create restart helper script, will exit and rely on OpenRC auto-restart")
368+
// Fall through to exit approach
369+
} else {
370+
// Execute the helper script in background (detached from current process)
371+
// Use 'sh -c' with nohup to ensure it runs after we exit
372+
cmd := exec.Command("sh", "-c", fmt.Sprintf("nohup %s > /dev/null 2>&1 &", helperPath))
373+
if err := cmd.Start(); err != nil {
374+
logger.WithError(err).Warn("Failed to start restart helper script, will exit and rely on OpenRC auto-restart")
375+
// Clean up script
376+
if removeErr := os.Remove(helperPath); removeErr != nil {
377+
logger.WithError(removeErr).Debug("Failed to remove helper script")
378+
}
379+
// Fall through to exit approach
380+
} else {
381+
logger.Info("Scheduled service restart via helper script, exiting now...")
382+
// Give the helper script a moment to start
383+
time.Sleep(500 * time.Millisecond)
384+
// Exit gracefully - the helper script will restart the service
385+
os.Exit(0)
386+
}
387+
}
388+
389+
// Fallback: If helper script approach failed, just exit and let OpenRC handle it
390+
// OpenRC with command_background="yes" should restart on exit
391+
logger.Info("Exiting to allow OpenRC to restart service with updated config...")
392+
os.Exit(0)
393+
// os.Exit never returns, but we need this for code flow
394+
return nil
395+
} else {
396+
logger.Warn("No known init system detected, attempting to restart via process signal")
397+
// Try to find and kill the process, service manager should restart it
398+
killCmd := exec.CommandContext(ctx, "pkill", "-HUP", "patchmon-agent")
399+
if err := killCmd.Run(); err != nil {
400+
logger.WithError(err).Warn("Failed to restart service (this is not critical)")
401+
return fmt.Errorf("failed to restart service: no init system detected and pkill failed: %w", err)
402+
}
403+
logger.Info("Sent HUP signal to agent process")
404+
return nil
342405
}
343-
344-
logger.WithField("output", string(output)).Debug("Service restart command completed")
345-
logger.Info("Service restarted successfully")
346-
return nil
347406
}

0 commit comments

Comments
 (0)