mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-09 18:15:50 +00:00
Align Windows agent state with the installer
Use the installer-owned ProgramData directory consistently for token lookup, enrollment state, agent identity, receipts, and service runtime data. Pass that directory explicitly to the Windows service and retain the existing Linux default elsewhere. Contract-Neutral: Windows agent state-path alignment preserves the existing installer and agent public contract
This commit is contained in:
+28
-11
@@ -14,6 +14,7 @@ import (
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -1027,7 +1028,7 @@ func loadConfig(args []string, getenv func(string) string) (Config, error) {
|
||||
kubeIncludeAllPodsFlag := fs.Bool("kube-include-all-pods", utils.ParseBool(envKubeIncludeAllPods), "Include all non-succeeded pods (may be large)")
|
||||
kubeIncludeAllDeploymentsFlag := fs.Bool("kube-include-all-deployments", utils.ParseBool(envKubeIncludeAllDeployments), "Include all deployments, not just problem ones")
|
||||
kubeMaxPodsFlag := fs.Int("kube-max-pods", defaultInt(envKubeMaxPods, 200), "Max pods included in report")
|
||||
stateDirFlag := fs.String("state-dir", envStateDir, "Persistent state directory (default: /var/lib/pulse-agent)")
|
||||
stateDirFlag := fs.String("state-dir", envStateDir, "Persistent state directory (default: platform service state directory)")
|
||||
reportIPFlag := fs.String("report-ip", envReportIP, "IP address to report (for multi-NIC systems)")
|
||||
disableCephFlag := fs.Bool("disable-ceph", utils.ParseBool(envDisableCeph), "Disable local Ceph status polling")
|
||||
showVersion := fs.Bool("version", false, "Print the agent version and exit")
|
||||
@@ -1072,12 +1073,11 @@ func loadConfig(args []string, getenv func(string) string) (Config, error) {
|
||||
// enrollment, use it instead of the bootstrap token embedded in the service
|
||||
// config. This ensures the agent survives restarts after enrollment.
|
||||
stateDir := strings.TrimSpace(*stateDirFlag)
|
||||
if stateDir == "" {
|
||||
stateDir = defaultAgentStateDir()
|
||||
}
|
||||
if *enrollFlag {
|
||||
enrollStateDir := stateDir
|
||||
if enrollStateDir == "" {
|
||||
enrollStateDir = "/var/lib/pulse-agent"
|
||||
}
|
||||
runtimeTokenPath := filepath.Join(enrollStateDir, "runtime.token")
|
||||
runtimeTokenPath := filepath.Join(stateDir, "runtime.token")
|
||||
if content, err := os.ReadFile(runtimeTokenPath); err == nil {
|
||||
if t := strings.TrimSpace(string(content)); t != "" {
|
||||
token = t
|
||||
@@ -1086,7 +1086,7 @@ func loadConfig(args []string, getenv func(string) string) (Config, error) {
|
||||
}
|
||||
|
||||
if token == "" && *enrollFlag && !*selfTest {
|
||||
return Config{}, fmt.Errorf("Pulse API token is required for enrollment (use --token, --token-file, PULSE_TOKEN env, or /var/lib/pulse-agent/token)")
|
||||
return Config{}, fmt.Errorf("Pulse API token is required for enrollment (use --token, --token-file, PULSE_TOKEN env, or %s)", defaultTokenFilePath())
|
||||
}
|
||||
|
||||
logLevel, err := parseLogLevel(*logLevelFlag)
|
||||
@@ -1172,7 +1172,7 @@ func loadConfig(args []string, getenv func(string) string) (Config, error) {
|
||||
KubeIncludeAllPods: *kubeIncludeAllPodsFlag,
|
||||
KubeIncludeAllDeployments: *kubeIncludeAllDeploymentsFlag,
|
||||
KubeMaxPods: kubeMaxPods,
|
||||
StateDir: strings.TrimSpace(*stateDirFlag),
|
||||
StateDir: stateDir,
|
||||
DiskExclude: diskExclude,
|
||||
ReportIP: strings.TrimSpace(*reportIPFlag),
|
||||
DisableCeph: *disableCephFlag,
|
||||
@@ -1294,13 +1294,30 @@ func resolveEnableCommands(enableFlag, disableFlag bool, envEnable, envDisable s
|
||||
// 1. --token flag (direct value)
|
||||
// 2. --token-file flag (read from file)
|
||||
// 3. PULSE_TOKEN environment variable
|
||||
// 4. Default token file at /var/lib/pulse-agent/token
|
||||
// 4. Default token file under the platform state directory
|
||||
//
|
||||
// Reading from a file is more secure than CLI args as tokens won't appear in `ps` output.
|
||||
func resolveToken(tokenFlag, tokenFileFlag, envToken string) string {
|
||||
return resolveTokenInternal(tokenFlag, tokenFileFlag, envToken, os.ReadFile)
|
||||
}
|
||||
|
||||
// defaultAgentStateDir mirrors where each platform's installer keeps agent
|
||||
// state: %ProgramData%\Pulse on Windows (see scripts/install.ps1), the
|
||||
// systemd/launchd convention /var/lib/pulse-agent everywhere else.
|
||||
func defaultAgentStateDir() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
if pd := strings.TrimSpace(os.Getenv("ProgramData")); pd != "" {
|
||||
return filepath.Join(pd, "Pulse")
|
||||
}
|
||||
return `C:\ProgramData\Pulse`
|
||||
}
|
||||
return "/var/lib/pulse-agent"
|
||||
}
|
||||
|
||||
func defaultTokenFilePath() string {
|
||||
return filepath.Join(defaultAgentStateDir(), "token")
|
||||
}
|
||||
|
||||
func resolveTokenInternal(tokenFlag, tokenFileFlag, envToken string, readFile func(string) ([]byte, error)) string {
|
||||
// 1. Direct token from --token flag
|
||||
if t := strings.TrimSpace(tokenFlag); t != "" {
|
||||
@@ -1321,8 +1338,8 @@ func resolveTokenInternal(tokenFlag, tokenFileFlag, envToken string, readFile fu
|
||||
return t
|
||||
}
|
||||
|
||||
// 4. Default token file (most secure method for systemd services)
|
||||
defaultTokenFile := "/var/lib/pulse-agent/token"
|
||||
// 4. Default token file (most secure method for service installs)
|
||||
defaultTokenFile := defaultTokenFilePath()
|
||||
if content, err := readFile(defaultTokenFile); err == nil {
|
||||
if t := strings.TrimSpace(string(content)); t != "" {
|
||||
return t
|
||||
|
||||
@@ -921,6 +921,9 @@ func TestLoadConfig(t *testing.T) {
|
||||
if cfg.EnableHost != true {
|
||||
t.Errorf("expected host enabled by default")
|
||||
}
|
||||
if cfg.StateDir != defaultAgentStateDir() {
|
||||
t.Errorf("expected platform state directory %q, got %q", defaultAgentStateDir(), cfg.StateDir)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("env overrides", func(t *testing.T) {
|
||||
@@ -985,6 +988,16 @@ func TestLoadConfig(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("state directory flag overrides platform default", func(t *testing.T) {
|
||||
cfg, err := loadConfig([]string{"-token", "test-token", "-state-dir", "/custom/pulse-state"}, func(s string) string { return "" })
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cfg.StateDir != "/custom/pulse-state" {
|
||||
t.Errorf("expected explicit state directory, got %q", cfg.StateDir)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("token optional when enrollment disabled", func(t *testing.T) {
|
||||
cfg, err := loadConfig([]string{"-url", "http://token-optional.example.com", "-enable-host"}, func(s string) string { return "" })
|
||||
if err != nil {
|
||||
|
||||
+2
-1
@@ -947,7 +947,8 @@ Save-ConnectionState
|
||||
# Build command line args (properly escaped)
|
||||
$ServiceArgs = @(
|
||||
"--url", "`"$Url`"",
|
||||
"--interval", "`"$Interval`""
|
||||
"--interval", "`"$Interval`"",
|
||||
"--state-dir", "`"$StateDir`""
|
||||
)
|
||||
if (-not [string]::IsNullOrWhiteSpace($Token)) { $ServiceArgs += @("--token-file", "`"$TokenFilePath`"") }
|
||||
if ($EnableHost) { $ServiceArgs += "--enable-host" } else { $ServiceArgs += "--enable-host=false" }
|
||||
|
||||
@@ -111,6 +111,7 @@ func TestInstallPS1OwnsWindowsServiceLoggingAndRecovery(t *testing.T) {
|
||||
script := string(content)
|
||||
required := []string{
|
||||
`$ServiceArgs += @("--log-file", "` + "`" + `"$LogFile` + "`" + `"")`,
|
||||
`"--state-dir", "` + "`" + `"$StateDir` + "`" + `"`,
|
||||
`$scOutput = sc.exe failure $AgentName reset= 86400 actions= restart/5000/restart/5000/restart/5000`,
|
||||
`Show-Error "Failed to configure service recovery actions: $scOutput"`,
|
||||
`$scOutput = sc.exe failureflag $AgentName 1`,
|
||||
|
||||
Reference in New Issue
Block a user