package installtests import ( "context" "crypto/sha256" "encoding/base64" "encoding/json" "encoding/pem" "errors" "fmt" "io" "net" "net/http" "net/http/httptest" "os" "os/exec" "path/filepath" "regexp" "runtime" "strconv" "strings" "sync" "syscall" "testing" "time" ) func TestContainerAgentCompatibilityWrapperDefaultsToUnifiedHostAndDockerMonitoring(t *testing.T) { wrapperBytes, err := os.ReadFile(repoFile("scripts", "install-container-agent.sh")) if err != nil { t.Fatalf("read container-agent compatibility wrapper: %v", err) } wrapper := string(wrapperBytes) if !strings.Contains(wrapper, `forward_args+=(--enable-docker --enable-host)`) { t.Fatal("container-agent compatibility wrapper must preserve host telemetry while enabling Docker") } if strings.Contains(wrapper, `forward_args+=(--enable-docker --disable-host)`) { t.Fatal("container-agent compatibility wrapper must not silently force workload-only mode") } if !strings.Contains(wrapper, `--enable-host=false for an intentional`) { t.Fatal("container-agent compatibility wrapper must document the explicit workload-only path") } } func TestInstallSHAllowsMissingTokenForOptionalAuth(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `build_exec_arg_items() {`, `RUNTIME_TOKEN_FILE=""`, `EXEC_ARG_ITEMS+=(--token-file "$RUNTIME_TOKEN_FILE")`, `fail "Internal installer error: runtime token file was not prepared before service rendering." "$EXIT_GENERAL"`, `build_exec_args_without_token() {`, `build_exec_arg_items "false"`, `build_exec_arg_items "true"`, `if [[ -n "$PULSE_TOKEN" && ! "$PULSE_TOKEN" =~ ^[a-fA-F0-9]+$ ]]; then`, `if [[ -n "$PULSE_TOKEN" ]]; then`, `log_info "No API token provided; installer will configure token-optional agent runtime."`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing optional-token handling: %s", needle) } } } func TestInstallSHPersistsAndVerifiesServerFingerprint(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `SERVER_FINGERPRINT="${PULSE_SERVER_FINGERPRINT:-}"`, `--server-fingerprint `, `verify_pinned_server_certificate() {`, `openssl s_client -connect "$target" -servername "$host"`, `EXEC_ARG_ITEMS+=(--server-fingerprint "$SERVER_FINGERPRINT")`, `write_connection_state_value "$conn_tmp" "PULSE_SERVER_FINGERPRINT" "$SERVER_FINGERPRINT"`, `SERVER_FINGERPRINT=$(read_connection_state_value "$file" "PULSE_SERVER_FINGERPRINT")`, `if [[ "$OS" == "linux" || "$OS" == "freebsd" ]]; then`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing server-fingerprint lifecycle contract: %s", needle) } } } func TestInstallSHAutoDetectProxmoxKeepsRuntimeTypeUnpinned(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `if detect_proxmox; then`, `log_info "Proxmox detected - enabling Proxmox integration"`, `log_info " Proxmox type: auto-detect all installed services"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing unpinned Proxmox auto-detect contract: %s", needle) } } forbidden := []string{ `detect_proxmox_type() {`, `auto_type="$(detect_proxmox_type || true)"`, `PROXMOX_TYPE="$auto_type"`, } for _, needle := range forbidden { if strings.Contains(script, needle) { t.Fatalf("install.sh preserved stale single-type Proxmox auto-detect contract: %s", needle) } } } func TestInstallSHExplainsCommandExecutionForProxmoxLXCDockerInventory(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `--enable-commands Enable Pulse command execution (disabled by default; required for Patrol actions and Proxmox LXC Docker inventory)`, `log_info " Pulse command execution: $ENABLE_COMMANDS"`, `log_info " Accepts Pulse-scoped command requests on this agent."`, `log_info " On Proxmox nodes this is required for opted-in LXC Docker inventory via pct exec."`, `log_info " The Pulse server must also be started with PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY=true."`, `log_info " Command execution is off; enable only when Patrol actions or Proxmox LXC Docker inventory are needed."`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing command-execution disclosure: %s", needle) } } if strings.Contains(script, "Enable AI command execution") { t.Fatal("install.sh must not describe --enable-commands as AI command execution") } } func TestInstallSHAcceptsLegacyBooleanFlagValues(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `--enable-host=true) ENABLE_HOST="true"; HOST_EXPLICIT="true"; shift ;;`, `--enable-host=false) ENABLE_HOST="false"; HOST_EXPLICIT="true"; shift ;;`, `--enable-docker=true) ENABLE_DOCKER="true"; DOCKER_EXPLICIT="true"; shift ;;`, `--enable-docker=false) ENABLE_DOCKER="false"; DOCKER_EXPLICIT="true"; shift ;;`, `--enable-kubernetes=true) ENABLE_KUBERNETES="true"; KUBERNETES_EXPLICIT="true"; shift ;;`, `--enable-kubernetes=false) ENABLE_KUBERNETES="false"; KUBERNETES_EXPLICIT="true"; shift ;;`, `--enable-proxmox=true) ENABLE_PROXMOX="true"; PROXMOX_EXPLICIT="true"; shift ;;`, `--enable-proxmox=false) ENABLE_PROXMOX="false"; PROXMOX_EXPLICIT="true"; shift ;;`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing legacy boolean flag alias: %s", needle) } } } func TestInstallSHAgentDownloadIsServerVersionAware(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `"${PULSE_URL}/api/version"`, `SERVER_VERSION="$(printf '%s' "$server_version_json" | sed -n 's/.*"version"`, `DOWNLOAD_QUERY="${DOWNLOAD_QUERY}&serverVersion=${SERVER_VERSION}"`, `log_info "Pulse server version: ${SERVER_VERSION}"`, `NEW_VERSION_NORMALIZED="${NEW_VERSION#v}"`, `SERVER_VERSION_NORMALIZED="${SERVER_VERSION#v}"`, `"$NEW_VERSION_NORMALIZED" != "$SERVER_VERSION_NORMALIZED"`, `Downloaded agent version (${NEW_VERSION}) does not match Pulse server version (${SERVER_VERSION})`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing version-aware agent download behavior: %s", needle) } } } // Regression test for #1527: the agent binary reports its version as "v6.0.4" // while the server /api/version reports "6.0.4", so the mismatch check must // strip a leading "v" before comparing and only warn on a genuine difference. func TestInstallSHAgentVersionMismatchIgnoresVPrefix(t *testing.T) { harness := ` set -euo pipefail log_warn() { echo "WARN:$*"; } SERVER_VERSION="$1" NEW_VERSION="$2" NEW_VERSION_NORMALIZED="${NEW_VERSION#v}" SERVER_VERSION_NORMALIZED="${SERVER_VERSION#v}" if [[ -n "$SERVER_VERSION" && -n "$NEW_VERSION" && "$NEW_VERSION" != "unknown" && "$NEW_VERSION_NORMALIZED" != "$SERVER_VERSION_NORMALIZED" ]]; then log_warn "Downloaded agent version (${NEW_VERSION}) does not match Pulse server version (${SERVER_VERSION})." fi echo DONE ` run := func(server, agent string) string { out, err := exec.Command("bash", "-c", harness, "_", server, agent).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } return string(out) } if got := run("6.0.4", "v6.0.4"); strings.Contains(got, "WARN:") { t.Fatalf("equal versions differing only by a leading v warned:\n%s", got) } if got := run("6.0.4", "6.0.4"); strings.Contains(got, "WARN:") { t.Fatalf("identical versions warned:\n%s", got) } if got := run("6.0.4", "v6.0.3"); !strings.Contains(got, "WARN:") { t.Fatalf("genuine version mismatch did not warn:\n%s", got) } } func TestInstallSHAgentServiceSecurityDefaults(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `HEALTH_ADDR="${PULSE_HEALTH_ADDR:-}"`, `if [[ -n "${PULSE_HEALTH_ADDR+x}" ]]; then`, `--health-addr Health/metrics listener address (default: 127.0.0.1:9191; use "" to disable)`, `if [[ "$HEALTH_ADDR_SET" == "true" ]]; then EXEC_ARG_ITEMS+=(--health-addr "$HEALTH_ADDR"); fi`, `--health-addr) HEALTH_ADDR="$2"; HEALTH_ADDR_SET="true"; shift 2 ;;`, `UMask=0077`, `local no_new_privileges="true"`, `NoNewPrivileges=${no_new_privileges}`, `PrivateTmp=true`, `ProtectKernelTunables=true`, `ProtectKernelModules=true`, `ProtectControlGroups=true`, `LockPersonality=true`, `local restrict_suidsgid="true"`, `RestrictSUIDSGID=${restrict_suidsgid}`, `SystemCallArchitectures=native`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing agent service security default: %s", needle) } } } func TestInstallSHAllowsProxmoxCommandAgentLXCAttach(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `systemd_agent_requires_lxc_attach() {`, `if [[ "$ENABLE_COMMANDS" != "true" ]]; then`, `""|pve|all)`, `no_new_privileges="false"`, `restrict_suidsgid="false"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing Proxmox command-agent LXC attach service handling: %s", needle) } } } // Only a PVE agent explicitly installed with --enable-commands may receive the // lxc-attach capability grant. A monitoring-only unit must not carry dormant // CAP_SETUID/CAP_SETGID authority for a future remote toggle. func TestInstallSHGrantsLXCAttachCapabilitiesOnlyForCommandPVEAgent(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `systemd_agent_may_attach_lxc() {`, `if [[ "$ENABLE_COMMANDS" != "true" ]]; then`, `if [[ "$ENABLE_PROXMOX" != "true" ]]; then`, `if systemd_agent_may_attach_lxc; then`, `AmbientCapabilities=CAP_SETUID CAP_SETGID`, `SystemCallArchitectures=native${ambient_line}`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing unprivileged-LXC attach capability grant: %s", needle) } } if strings.Contains(script, "command execution can be turned on later") { t.Fatal("install.sh still provisions dormant attach capabilities for remote command promotion") } } func TestRenderedSystemdUnitDoesNotPregrantCommandCapabilities(t *testing.T) { for _, tc := range []struct { name string enableCommands bool enableProxmox bool proxmoxType string leastPrivilege bool wantCaps bool }{ {name: "monitoring PVE", enableProxmox: true, proxmoxType: "pve"}, {name: "explicit command PVE", enableCommands: true, enableProxmox: true, proxmoxType: "pve", wantCaps: true}, {name: "PBS command profile", enableCommands: true, enableProxmox: true, proxmoxType: "pbs"}, {name: "least privilege PVE", enableCommands: true, enableProxmox: true, proxmoxType: "pve", leastPrivilege: true}, } { t.Run(tc.name, func(t *testing.T) { unitPath := filepath.Join(t.TempDir(), "pulse-agent.service") script := ` set -euo pipefail ENABLE_COMMANDS="` + strconv.FormatBool(tc.enableCommands) + `" ENABLE_PROXMOX="` + strconv.FormatBool(tc.enableProxmox) + `" PROXMOX_TYPE="` + tc.proxmoxType + `" LEAST_PRIVILEGE="` + strconv.FormatBool(tc.leastPrivilege) + `" GRANT_SMART="false" GRANT_PCT="false" SYSTEMD_ENV_LINES="" ` + extractInstallShellFunction(t, "systemd_agent_requires_lxc_attach") + ` ` + extractInstallShellFunction(t, "systemd_agent_may_attach_lxc") + ` ` + extractInstallShellFunction(t, "render_systemd_agent_unit") + ` render_systemd_agent_unit "` + unitPath + `" "/usr/local/bin/pulse-agent" "--url https://pulse" "network-online.target" "" "root" "" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("render systemd unit: %v\n%s", err, out) } unit, err := os.ReadFile(unitPath) if err != nil { t.Fatalf("read rendered unit: %v", err) } text := string(unit) hasCaps := strings.Contains(text, "AmbientCapabilities=CAP_SETUID CAP_SETGID") if hasCaps != tc.wantCaps { t.Fatalf("rendered capability grant = %v, want %v:\n%s", hasCaps, tc.wantCaps, text) } if tc.wantCaps { if !strings.Contains(text, "NoNewPrivileges=false") || !strings.Contains(text, "RestrictSUIDSGID=false") { t.Fatalf("command-capable PVE unit omitted required attach relaxations:\n%s", text) } } else if !strings.Contains(text, "NoNewPrivileges=true") || !strings.Contains(text, "RestrictSUIDSGID=true") { t.Fatalf("monitoring unit did not retain hardening:\n%s", text) } }) } } func TestInstallSHPreflightChecksAgentDownloadArtifact(t *testing.T) { var requestedDownloadPath string var requestedDownloadArch string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/api/health": w.WriteHeader(http.StatusOK) case "/download/pulse-agent": requestedDownloadPath = r.URL.Path requestedDownloadArch = r.URL.Query().Get("arch") w.Header().Set("X-Checksum-Sha256", strings.Repeat("a", 64)) w.WriteHeader(http.StatusOK) default: http.NotFound(w, r) } })) defer server.Close() cmd := exec.Command( "bash", repoFile("scripts", "install.sh"), "--url", server.URL, "--preflight-only", "--output", "json", "--non-interactive", ) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("preflight failed: %v\n%s", err, out) } got := string(out) if !strings.Contains(got, `"code":"agent_download_available"`) { t.Fatalf("preflight did not report agent download availability:\n%s", got) } if requestedDownloadPath != "/download/pulse-agent" { t.Fatalf("download path = %q, want /download/pulse-agent", requestedDownloadPath) } if requestedDownloadArch == "" { t.Fatalf("download arch query was empty") } } func TestInstallSHPreflightFollowsAgentDownloadRedirect(t *testing.T) { var requestedArtifactMethod string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/api/health": w.WriteHeader(http.StatusOK) case "/download/pulse-agent": w.Header().Set("X-Checksum-Sha256", strings.Repeat("a", 64)) http.Redirect(w, r, "/artifacts/pulse-agent?"+r.URL.RawQuery, http.StatusTemporaryRedirect) case "/artifacts/pulse-agent": requestedArtifactMethod = r.Method w.Header().Set("X-Checksum-Sha256", strings.Repeat("b", 64)) w.WriteHeader(http.StatusOK) default: http.NotFound(w, r) } })) defer server.Close() cmd := exec.Command( "bash", repoFile("scripts", "install.sh"), "--url", server.URL, "--preflight-only", "--output", "json", "--non-interactive", ) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("redirected preflight failed: %v\n%s", err, out) } if got := string(out); !strings.Contains(got, `"code":"agent_download_available"`) { t.Fatalf("redirected preflight did not report agent download availability:\n%s", got) } if requestedArtifactMethod != http.MethodHead { t.Fatalf("redirected artifact method = %q, want HEAD", requestedArtifactMethod) } } func TestInstallSHPreflightRejectsChecksumOnlyOnRedirectResponse(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/api/health": w.WriteHeader(http.StatusOK) case "/download/pulse-agent": w.Header().Set("X-Checksum-Sha256", strings.Repeat("a", 64)) http.Redirect(w, r, "/artifacts/pulse-agent?"+r.URL.RawQuery, http.StatusTemporaryRedirect) case "/artifacts/pulse-agent": w.WriteHeader(http.StatusOK) default: http.NotFound(w, r) } })) defer server.Close() cmd := exec.Command( "bash", repoFile("scripts", "install.sh"), "--url", server.URL, "--preflight-only", "--output", "json", "--non-interactive", ) out, err := cmd.CombinedOutput() if err == nil { t.Fatalf("preflight accepted checksum metadata from an intermediate redirect:\n%s", out) } if exitErr, ok := err.(*exec.ExitError); !ok || exitErr.ExitCode() != 12 { t.Fatalf("redirect-only checksum exit = %v, want 12\n%s", err, out) } if got := string(out); !strings.Contains(got, `"code":"agent_download_checksum_missing"`) { t.Fatalf("preflight did not report missing final-response checksum metadata:\n%s", got) } } func TestInstallSHPreflightDoesNotRequireRoot(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `if [[ $EUID -ne 0 && "$PREFLIGHT_ONLY" != "true" ]]; then`, `DOWNLOAD_CHECK_URL="${PULSE_URL}/download/${BINARY_NAME}?arch=${PF_ARCH_PARAM}"`, `CURL_DOWNLOAD_CHECK_ARGS=(-fsSIL --connect-timeout 5 --max-time 30 -D "$PREFLIGHT_HEADERS" -o /dev/null)`, `final_response_header_value "$PREFLIGHT_HEADERS" "X-Checksum-Sha256"`, `"agent_download_available"`, `"agent_download_unavailable"`, `"agent_download_checksum_missing"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing non-root preflight download check: %s", needle) } } } func TestBuildPlistProgramArgumentsUsesSharedExecArgs(t *testing.T) { script := ` ` + extractInstallShellFunction(t, "build_exec_arg_items") + ` ` + extractInstallShellFunction(t, "xml_escape") + ` ` + extractInstallShellFunction(t, "append_plist_arg") + ` ` + extractInstallShellFunction(t, "build_plist_program_arguments") + ` PULSE_URL="https://pulse.example/a&b" PULSE_TOKEN="deadbeef" INTERVAL="30s" ENABLE_HOST="true" ENABLE_DOCKER="false" DOCKER_EXPLICIT="true" ENABLE_KUBERNETES="true" KUBECONFIG_PATH="/etc/kube config" ENABLE_PROXMOX="true" PROXMOX_TYPE="pbs" INSECURE="true" RUNTIME_TOKEN_FILE="/var/lib/pulse-agent/token" ENABLE_COMMANDS="true" HEALTH_ADDR_SET="true" HEALTH_ADDR="" ENROLL="true" KUBE_INCLUDE_ALL_PODS="true" KUBE_INCLUDE_ALL_DEPLOYMENTS="true" AGENT_ID="agent-1" HOSTNAME_OVERRIDE="Richard's Mac & Mini" STATE_DIR="/var/lib/pulse-agent" DISK_EXCLUDES=("Time Machine") DISK_INCLUDES=("/var/log") build_plist_program_arguments "/usr/local/bin/pulse-agent" printf '%s\n' "$PLIST_ARGS" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) required := []string{ `/usr/local/bin/pulse-agent`, `https://pulse.example/a&b`, `--token-file`, `/var/lib/pulse-agent/token`, `--enable-docker=false`, `--enable-proxmox`, `--proxmox-type`, `pbs`, `--health-addr`, `--hostname`, `Richard's Mac & Mini`, `--state-dir`, `/var/lib/pulse-agent`, `--disk-exclude`, `Time Machine`, `--disk-include`, `/var/log`, } for _, needle := range required { if !strings.Contains(got, needle) { t.Fatalf("plist args missing %s:\n%s", needle, got) } } if strings.Contains(got, "--token") || strings.Contains(got, "deadbeef") { t.Fatalf("plist args leaked raw token:\n%s", got) } } // TestConnectionEnvRecovery verifies the canonical helper logic that parses // connection.env without using shell source (to prevent injection). func TestConnectionEnvRecovery(t *testing.T) { cases := []struct { name string content string wantURL string wantTok string wantID string wantHost string wantInsecure string wantCACert string }{ { name: "single-quoted values", content: "PULSE_URL='http://192.168.0.98:7655'\nPULSE_TOKEN='abc123def'\nPULSE_AGENT_ID='agent-123'\nPULSE_HOSTNAME='node.local'\nPULSE_INSECURE_SKIP_VERIFY='true'\nPULSE_CACERT='/etc/pulse/ca.pem'\n", wantURL: "http://192.168.0.98:7655", wantTok: "abc123def", wantID: "agent-123", wantHost: "node.local", wantInsecure: "true", wantCACert: "/etc/pulse/ca.pem", }, { name: "unquoted values", content: "PULSE_URL=http://10.0.0.1:7655\nPULSE_TOKEN=deadbeef\nPULSE_AGENT_ID=agent-456\nPULSE_HOSTNAME=node-two.local\nPULSE_INSECURE_SKIP_VERIFY=true\nPULSE_CACERT=/opt/pulse/ca.pem\n", wantURL: "http://10.0.0.1:7655", wantTok: "deadbeef", wantID: "agent-456", wantHost: "node-two.local", wantInsecure: "true", wantCACert: "/opt/pulse/ca.pem", }, { name: "https URL", content: "PULSE_URL='https://pulse.example.com'\nPULSE_TOKEN='aabbccdd'\nPULSE_AGENT_ID='agent-https'\nPULSE_HOSTNAME='https-host'\nPULSE_INSECURE_SKIP_VERIFY='false'\nPULSE_CACERT='/usr/local/share/ca.pem'\n", wantURL: "https://pulse.example.com", wantTok: "aabbccdd", wantID: "agent-https", wantHost: "https-host", wantInsecure: "false", wantCACert: "/usr/local/share/ca.pem", }, { name: "extra whitespace lines", content: "\nPULSE_URL='http://host:7655'\n\nPULSE_TOKEN='tok123'\n\nPULSE_AGENT_ID='agent-spaced'\nPULSE_HOSTNAME='spaced.local'\n\nPULSE_INSECURE_SKIP_VERIFY='true'\n\nPULSE_CACERT='/tmp/ca.pem'\n\n", wantURL: "http://host:7655", wantTok: "tok123", wantID: "agent-spaced", wantHost: "spaced.local", wantInsecure: "true", wantCACert: "/tmp/ca.pem", }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { dir := t.TempDir() connFile := filepath.Join(dir, "connection.env") if err := os.WriteFile(connFile, []byte(tc.content), 0600); err != nil { t.Fatal(err) } // Run the same helper logic used by install.sh script := ` CONN_ENV="` + connFile + `" read_connection_state_value() { local file="$1" local key="$2" awk -F= -v key="$key" ' $1 == key { value = substr($0, index($0, "=") + 1) sub(/^'\''/, "", value) sub(/'\''$/, "", value) print value exit } ' "$file" 2>/dev/null || true } PULSE_URL=$(read_connection_state_value "$CONN_ENV" "PULSE_URL") PULSE_TOKEN=$(read_connection_state_value "$CONN_ENV" "PULSE_TOKEN") PULSE_AGENT_ID=$(read_connection_state_value "$CONN_ENV" "PULSE_AGENT_ID") PULSE_HOSTNAME=$(read_connection_state_value "$CONN_ENV" "PULSE_HOSTNAME") PULSE_INSECURE_SKIP_VERIFY=$(read_connection_state_value "$CONN_ENV" "PULSE_INSECURE_SKIP_VERIFY") PULSE_CACERT=$(read_connection_state_value "$CONN_ENV" "PULSE_CACERT") echo "URL=${PULSE_URL}" echo "TOKEN=${PULSE_TOKEN}" echo "AGENT_ID=${PULSE_AGENT_ID}" echo "HOSTNAME=${PULSE_HOSTNAME}" echo "INSECURE=${PULSE_INSECURE_SKIP_VERIFY}" echo "CACERT=${PULSE_CACERT}" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } lines := strings.Split(strings.TrimSpace(string(out)), "\n") gotURL, gotTok, gotID, gotHost, gotInsecure, gotCACert := "", "", "", "", "", "" for _, line := range lines { if strings.HasPrefix(line, "URL=") { gotURL = strings.TrimPrefix(line, "URL=") } if strings.HasPrefix(line, "TOKEN=") { gotTok = strings.TrimPrefix(line, "TOKEN=") } if strings.HasPrefix(line, "AGENT_ID=") { gotID = strings.TrimPrefix(line, "AGENT_ID=") } if strings.HasPrefix(line, "HOSTNAME=") { gotHost = strings.TrimPrefix(line, "HOSTNAME=") } if strings.HasPrefix(line, "INSECURE=") { gotInsecure = strings.TrimPrefix(line, "INSECURE=") } if strings.HasPrefix(line, "CACERT=") { gotCACert = strings.TrimPrefix(line, "CACERT=") } } if gotURL != tc.wantURL { t.Errorf("URL = %q, want %q", gotURL, tc.wantURL) } if gotTok != tc.wantTok { t.Errorf("TOKEN = %q, want %q", gotTok, tc.wantTok) } if gotID != tc.wantID { t.Errorf("AGENT_ID = %q, want %q", gotID, tc.wantID) } if gotHost != tc.wantHost { t.Errorf("HOSTNAME = %q, want %q", gotHost, tc.wantHost) } if gotInsecure != tc.wantInsecure { t.Errorf("INSECURE = %q, want %q", gotInsecure, tc.wantInsecure) } if gotCACert != tc.wantCACert { t.Errorf("CACERT = %q, want %q", gotCACert, tc.wantCACert) } }) } } // TestAgentIDFileRecovery verifies the agent-id file lookup priority: // /var/lib/pulse-agent/agent-id > /boot/config/plugins/pulse-agent/agent-id func TestAgentIDFileRecovery(t *testing.T) { cases := []struct { name string files map[string]string // relative path -> content wantID string }{ { name: "primary location", files: map[string]string{ "var/lib/pulse-agent/agent-id": "uuid-primary", }, wantID: "uuid-primary", }, { name: "secondary location (Unraid)", files: map[string]string{ "boot/config/plugins/pulse-agent/agent-id": "uuid-unraid", }, wantID: "uuid-unraid", }, { name: "primary takes precedence", files: map[string]string{ "var/lib/pulse-agent/agent-id": "uuid-primary", "boot/config/plugins/pulse-agent/agent-id": "uuid-unraid", }, wantID: "uuid-primary", }, { name: "no file found", files: map[string]string{}, wantID: "", }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { root := t.TempDir() for relPath, content := range tc.files { fullPath := filepath.Join(root, relPath) if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil { t.Fatal(err) } if err := os.WriteFile(fullPath, []byte(content), 0644); err != nil { t.Fatal(err) } } // Replicate the install.sh agent-id recovery loop script := ` AGENT_ID="" for aid_path in "` + root + `/var/lib/pulse-agent/agent-id" "` + root + `/boot/config/plugins/pulse-agent/agent-id"; do if [[ -f "$aid_path" ]]; then AGENT_ID=$(cat "$aid_path") break fi done echo "$AGENT_ID" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := strings.TrimSpace(string(out)) if got != tc.wantID { t.Errorf("agent-id = %q, want %q", got, tc.wantID) } }) } } func TestInstallSHUsesHostnameOverrideForUninstallLookup(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `local uninstall_hostname="${HOSTNAME_OVERRIDE:-}"`, `if [[ -z "$uninstall_hostname" ]]; then`, `uninstall_hostname=$(hostname 2>/dev/null || true)`, `uninstall_args+=(--hostname "$uninstall_hostname")`, `removed_agent_id=$(run_collector_lifecycle_command "${uninstall_args[@]}"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing uninstall hostname override lookup handling: %s", needle) } } } func TestInstallSHUrlEncodesHostnameLookupQuery(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `collector-uninstall`, `uninstall_args+=(--hostname "$uninstall_hostname")`, `run_collector_lifecycle_command "${uninstall_args[@]}"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing encoded hostname lookup transport: %s", needle) } } } func TestInstallSHPersistsIdentityInConnectionEnv(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `write_connection_state_value() {`, `read_connection_state_value() {`, `recover_connection_state() {`, `find_connection_state_file() {`, `write_connection_state_value "$conn_tmp" "PULSE_STATE_DIR" "$state_dir"`, `write_connection_state_value "$conn_tmp" "PULSE_TOKEN_FILE" "$RUNTIME_TOKEN_FILE"`, `write_connection_state_value "$conn_tmp" "PULSE_AGENT_ID" "$AGENT_ID"`, `write_connection_state_value "$conn_tmp" "PULSE_HOSTNAME" "$HOSTNAME_OVERRIDE"`, `write_connection_state_value "$conn_tmp" "PULSE_INSECURE_SKIP_VERIFY" "true"`, `write_connection_state_value "$conn_tmp" "PULSE_CACERT" "$CURL_CA_BUNDLE"`, `recover_connection_state "$lifecycle_conn_env"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing persisted identity recovery: %s", needle) } } } func TestInstallSHRecoversSavedStateForPartialUninstallContext(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) needles := []string{ `if [[ "$UPDATE_ONLY" == "true" || "$UNINSTALL" == "true" ]]; then`, `# An explicit state directory is authoritative.`, `lifecycle_conn_env=$(find_connection_state_file || true)`, `recover_connection_state "$lifecycle_conn_env"`, } for _, needle := range needles { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing partial uninstall saved-state recovery guard: %s", needle) } } } func TestInstallSHSupportsSavedStateUpdateMode(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `--update Update an existing agent using saved connection state`, `--retarget Point an existing agent at --url using saved identity and token`, `UPDATE_ONLY="false"`, `RETARGET_ONLY="false"`, `--update) UPDATE_ONLY="true"; shift ;;`, `--retarget) RETARGET_ONLY="true"; UPDATE_ONLY="true"; shift ;;`, `--retarget requires the new Pulse endpoint in --url`, `if [[ "$UPDATE_ONLY" == "true" || "$UNINSTALL" == "true" ]]; then`, `lifecycle_conn_env=$(find_connection_state_file || true)`, `recover_connection_state "$lifecycle_conn_env"`, `recover_connection_state_from_existing_agent() {`, `recover_connection_state_from_running_agent`, `recover_connection_state_from_systemd_unit`, `recover_connection_state_from_launchd_plist`, `recover_connection_state_from_service_scripts`, `running_agent_arg_stream() {`, `running_agent_env_stream() {`, `recover_connection_state_from_arg_stream`, `recover_token_from_default_agent_token_file() {`, `normalize_recovered_agent_arg_key() {`, `-url|-pulse-url|-token|-token-file|-interval|-agent-id|-hostname|-report-ip|-cacert|-server-fingerprint|-observers-file|-command-authority|-health-addr|-state-dir|-kubeconfig|-proxmox-type|-disk-exclude|-disk-include)`, `--enable-host|-enable-host|--enable-host=true|-enable-host=true)`, `recover_connection_state_from_env_stream`, `recovered_connection_state_ready() {`, `update_connection_state_incomplete() {`, `[[ "$RECOVERED_AGENT_ARG_STATE" == "true" ]] && recovered_connection_state_ready`, `[[ "$RECOVERED_AGENT_ENV_STATE" == "true" ]] && recovered_connection_state_ready`, `if update_connection_state_incomplete; then`, `recover_connection_state_from_existing_agent || true`, `if [[ "$UPDATE_ONLY" == "true" && ( -z "$PULSE_URL" || -z "$PULSE_TOKEN" ) ]]; then`, `recover_agent_id_from_state_file() {`, `AGENT_ID=$(recover_agent_id_from_state_file || true)`, `No existing Pulse Agent connection state found. Use the install command instead.`, `if [[ "$UPDATE_ONLY" == "true" && "$UPGRADE_MODE" != "true" ]]; then`, `No existing Pulse Agent installation found to update. Use the install command instead.`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing saved-state update mode contract: %s", needle) } } } func TestInstallSHRetargetPreservesIdentityWithoutOldEndpointTrust(t *testing.T) { stateDir := t.TempDir() tokenPath := filepath.Join(stateDir, "token") if err := os.WriteFile(tokenPath, []byte("deadbeef\n"), 0600); err != nil { t.Fatalf("write token fixture: %v", err) } connectionPath := filepath.Join(stateDir, "connection.env") connection := strings.Join([]string{ "PULSE_STATE_DIR='" + stateDir + "'", "PULSE_URL='https://old-pulse.example.test:7655'", "PULSE_TOKEN_FILE='" + tokenPath + "'", "PULSE_AGENT_ID='agent-123'", "PULSE_HOSTNAME='pve-one'", "PULSE_INSECURE_SKIP_VERIFY='true'", "PULSE_SERVER_FINGERPRINT='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'", "PULSE_CACERT='/etc/pulse/old-ca.pem'", }, "\n") + "\n" if err := os.WriteFile(connectionPath, []byte(connection), 0600); err != nil { t.Fatalf("write connection fixture: %v", err) } script := ` PULSE_URL="https://new-pulse.example.test:7655" PULSE_TOKEN="" RETARGET_ONLY="true" INSECURE="false" SERVER_FINGERPRINT="" CURL_CA_BUNDLE="" AGENT_ID="" HOSTNAME_OVERRIDE="" REPORT_IP="" STATE_DIR="/var/lib/pulse-agent" STATE_DIR_SOURCE="default" DEFAULT_STATE_DIR="/var/lib/pulse-agent" TRUENAS_STATE_DIR="/data/pulse-agent" ` + extractLifecycleTrustShellFunctions(t) + ` ` + extractInstallShellFunction(t, "read_connection_state_value") + ` ` + extractTokenRecoveryShellFunctions(t) + ` ` + extractInstallShellFunction(t, "recover_connection_state") + ` recover_connection_state "${PULSE_TEST_CONNECTION:?}" printf 'URL=%s\nTOKEN=%s\nAGENT_ID=%s\nHOSTNAME=%s\nINSECURE=%s\nFINGERPRINT=%s\nCACERT=%s\n' \ "$PULSE_URL" "$PULSE_TOKEN" "$AGENT_ID" "$HOSTNAME_OVERRIDE" "$INSECURE" "$SERVER_FINGERPRINT" "$CURL_CA_BUNDLE" ` cmd := exec.Command("bash", "-c", script) cmd.Env = append(os.Environ(), "PULSE_TEST_CONNECTION="+connectionPath) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) for _, needle := range []string{ "URL=https://new-pulse.example.test:7655", "TOKEN=deadbeef", "AGENT_ID=agent-123", "HOSTNAME=pve-one", "INSECURE=false", "FINGERPRINT=", "CACERT=", } { if !strings.Contains(got, needle) { t.Fatalf("retarget state missing %q:\n%s", needle, got) } } if strings.Contains(got, "old-pulse") || strings.Contains(got, "old-ca") || strings.Contains(got, "aaaaaaaa") { t.Fatalf("retarget carried old endpoint trust or URL:\n%s", got) } } func TestInstallSHRetargetDoesNotRecoverLegacyServiceTrust(t *testing.T) { script := ` PULSE_URL="https://new-pulse.example.test:7655" PULSE_TOKEN="" RETARGET_ONLY="true" INSECURE="false" INSECURE_EXPLICIT="false" SERVER_FINGERPRINT="" CURL_CA_BUNDLE="" INTERVAL="30s" INTERVAL_EXPLICIT="false" ENABLE_HOST="true" HOST_EXPLICIT="false" ENABLE_DOCKER="" DOCKER_EXPLICIT="false" ENABLE_KUBERNETES="" KUBERNETES_EXPLICIT="false" KUBECONFIG_PATH="" ENABLE_PROXMOX="" PROXMOX_EXPLICIT="false" PROXMOX_TYPE="" ENABLE_COMMANDS="false" ENROLL="false" COMMAND_AUTHORITY_SOURCE="" HEALTH_ADDR="" HEALTH_ADDR_SET="false" AGENT_ID="" HOSTNAME_OVERRIDE="" REPORT_IP="" STATE_DIR="/var/lib/pulse-agent" STATE_DIR_SOURCE="default" OBSERVERS_FILE="" KUBE_INCLUDE_ALL_PODS="false" KUBE_INCLUDE_ALL_DEPLOYMENTS="false" DISK_EXCLUDES=() DISK_INCLUDES=() ` + extractInstallShellFunction(t, "strip_recovered_arg_quotes") + ` ` + extractInstallShellFunction(t, "normalize_recovered_agent_arg_key") + ` ` + extractInstallShellFunction(t, "apply_recovered_agent_arg_value") + ` ` + extractInstallShellFunction(t, "recovered_connection_state_ready") + ` ` + extractTokenRecoveryShellFunctions(t) + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_arg_stream") + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_env_stream") + ` recover_connection_state_from_arg_stream <<'ARGS' /usr/local/bin/pulse-agent --url=https://old-pulse.example.test:7655 --token=deadbeef --agent-id=agent-123 --hostname=pve-one --insecure --cacert=/etc/pulse/old-ca.pem --server-fingerprint=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ARGS recover_connection_state_from_env_stream <<'ENV' PULSE_INSECURE_SKIP_VERIFY=true PULSE_CACERT=/etc/pulse/older-ca.pem PULSE_SERVER_FINGERPRINT=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ENV printf 'URL=%s\nTOKEN=%s\nAGENT_ID=%s\nHOSTNAME=%s\nINSECURE=%s\nFINGERPRINT=%s\nCACERT=%s\n' \ "$PULSE_URL" "$PULSE_TOKEN" "$AGENT_ID" "$HOSTNAME_OVERRIDE" "$INSECURE" "$SERVER_FINGERPRINT" "$CURL_CA_BUNDLE" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) for _, needle := range []string{ "URL=https://new-pulse.example.test:7655", "TOKEN=deadbeef", "AGENT_ID=agent-123", "HOSTNAME=pve-one", "INSECURE=false", "FINGERPRINT=", "CACERT=", } { if !strings.Contains(got, needle) { t.Fatalf("retarget service recovery missing %q:\n%s", needle, got) } } if strings.Contains(got, "old-pulse") || strings.Contains(got, "old-ca") || strings.Contains(got, "aaaaaaaa") || strings.Contains(got, "bbbbbbbb") { t.Fatalf("retarget carried old service trust or URL:\n%s", got) } } func TestInstallSHRecoversV5ProcessArgsForSavedStateUpdate(t *testing.T) { script := ` fail() { echo "FAIL:$1"; exit 99; } PULSE_URL="" PULSE_TOKEN="" INTERVAL="30s" INTERVAL_EXPLICIT="false" ENABLE_HOST="true" HOST_EXPLICIT="false" ENABLE_DOCKER="" DOCKER_EXPLICIT="false" ENABLE_KUBERNETES="" KUBERNETES_EXPLICIT="false" KUBECONFIG_PATH="" ENABLE_PROXMOX="" PROXMOX_EXPLICIT="false" PROXMOX_TYPE="" INSECURE="false" ENABLE_COMMANDS="false" ENROLL="false" HEALTH_ADDR="" HEALTH_ADDR_SET="false" AGENT_ID="" HOSTNAME_OVERRIDE="" STATE_DIR="/var/lib/pulse-agent" CURL_CA_BUNDLE="" KUBE_INCLUDE_ALL_PODS="false" KUBE_INCLUDE_ALL_DEPLOYMENTS="false" DISK_EXCLUDES=() DISK_INCLUDES=() RUNTIME_TOKEN_FILE="/var/lib/pulse-agent/token" ` + extractInstallShellFunction(t, "strip_recovered_arg_quotes") + ` ` + extractInstallShellFunction(t, "normalize_recovered_agent_arg_key") + ` ` + extractInstallShellFunction(t, "apply_recovered_agent_arg_value") + ` ` + extractInstallShellFunction(t, "recovered_connection_state_ready") + ` ` + extractTokenRecoveryShellFunctions(t) + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_arg_stream") + ` ` + extractInstallShellFunction(t, "build_exec_arg_items") + ` ` + extractInstallShellFunction(t, "join_exec_arg_items") + ` ` + extractInstallShellFunction(t, "build_exec_args") + ` recover_connection_state_from_arg_stream <<'ARGS' /usr/local/bin/pulse-agent --url http://192.168.2.96:7655 --token deadbeef --interval 30s --enable-host --enable-docker --insecure --agent-id agent-123 --hostname pve-one --disk-exclude /dev/sda --disk-exclude=/var/run/samba/fd --disk-include /var/log ARGS build_exec_args printf 'URL=%s\nTOKEN=%s\nDOCKER=%s\nDOCKER_EXPLICIT=%s\nINSECURE=%s\nAGENT_ID=%s\nHOSTNAME=%s\nEXEC_ARGS=%s\n' \ "$PULSE_URL" "$PULSE_TOKEN" "$ENABLE_DOCKER" "$DOCKER_EXPLICIT" "$INSECURE" "$AGENT_ID" "$HOSTNAME_OVERRIDE" "$EXEC_ARGS" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) required := []string{ "URL=http://192.168.2.96:7655", "TOKEN=deadbeef", "DOCKER=true", "DOCKER_EXPLICIT=true", "INSECURE=true", "AGENT_ID=agent-123", "HOSTNAME=pve-one", "--token-file /var/lib/pulse-agent/token", "--enable-docker", "--disk-exclude /dev/sda", "--disk-exclude /var/run/samba/fd", "--disk-include /var/log", } for _, needle := range required { if !strings.Contains(got, needle) { t.Fatalf("recovered update state missing %q:\n%s", needle, got) } } if strings.Contains(got, "--token deadbeef") { t.Fatalf("recovered service args leaked raw token:\n%s", got) } } func TestInstallSHRecoversV5ProcessArgsWithoutProcfs(t *testing.T) { // running_agent_arg_stream prefers //cmdline and only falls // back to ps when that entry is missing. Point it at an empty proc root so // the stubbed pid never resolves to an unrelated live process on a Linux // host, which made this test fail whenever pid 4242 happened to exist. emptyProcRoot := t.TempDir() script := ` PULSE_URL="" PULSE_TOKEN="" INTERVAL="30s" INTERVAL_EXPLICIT="false" ENABLE_HOST="true" HOST_EXPLICIT="false" ENABLE_DOCKER="" DOCKER_EXPLICIT="false" ENABLE_KUBERNETES="" KUBERNETES_EXPLICIT="false" KUBECONFIG_PATH="" ENABLE_PROXMOX="" PROXMOX_EXPLICIT="false" PROXMOX_TYPE="" INSECURE="false" ENABLE_COMMANDS="false" ENROLL="false" HEALTH_ADDR="" HEALTH_ADDR_SET="false" AGENT_ID="" HOSTNAME_OVERRIDE="" STATE_DIR="/var/lib/pulse-agent" TRUENAS_STATE_DIR="/data/pulse-agent" CURL_CA_BUNDLE="" KUBE_INCLUDE_ALL_PODS="false" KUBE_INCLUDE_ALL_DEPLOYMENTS="false" DISK_EXCLUDES=() RUNTIME_TOKEN_FILE="/var/lib/pulse-agent/token" BINARY_NAME="pulse-agent" ` + extractInstallShellFunction(t, "strip_recovered_arg_quotes") + ` ` + extractInstallShellFunction(t, "normalize_recovered_agent_arg_key") + ` ` + extractInstallShellFunction(t, "apply_recovered_agent_arg_value") + ` ` + extractInstallShellFunction(t, "recovered_connection_state_ready") + ` ` + extractTokenRecoveryShellFunctions(t) + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_arg_stream") + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_env_stream") + ` ` + extractInstallShellFunction(t, "split_recovered_shell_words") + ` ` + extractInstallShellFunction(t, "running_agent_arg_stream") + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_running_agent") + ` eval "installed_running_agent_arg_stream() $(declare -f running_agent_arg_stream | tail -n +2)" running_agent_arg_stream() { installed_running_agent_arg_stream "$1" "` + emptyProcRoot + `"; } ` + extractInstallShellFunction(t, "build_exec_arg_items") + ` ` + extractInstallShellFunction(t, "join_exec_arg_items") + ` ` + extractInstallShellFunction(t, "build_exec_args") + ` collect_running_agent_pids() { printf '%s\n' 4242; } running_agent_env_stream() { return 1; } ps() { printf '%s\n' "/usr/local/bin/pulse-agent -url http://192.168.2.96:7655 -token deadbeef -enable-host -enable-docker -insecure -agent-id firewall-1 -hostname 'edge firewall'" } if recover_connection_state_from_running_agent; then echo "READY" else echo "NOT_READY" fi build_exec_args printf 'URL=%s\nTOKEN=%s\nDOCKER=%s\nINSECURE=%s\nAGENT_ID=%s\nHOSTNAME=%s\nEXEC_ARGS=%s\n' \ "$PULSE_URL" "$PULSE_TOKEN" "$ENABLE_DOCKER" "$INSECURE" "$AGENT_ID" "$HOSTNAME_OVERRIDE" "$EXEC_ARGS" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) // "READY" is a substring of "NOT_READY", so check the failure marker. if strings.Contains(got, "NOT_READY") || strings.Contains(got, "command not found") { t.Fatalf("non-procfs process recovery did not recover the running agent:\n%s", got) } for _, needle := range []string{ "URL=http://192.168.2.96:7655", "TOKEN=deadbeef", "DOCKER=true", "INSECURE=true", "AGENT_ID=firewall-1", "HOSTNAME=edge firewall", "--token-file /var/lib/pulse-agent/token", } { if !strings.Contains(got, needle) { t.Fatalf("non-procfs process recovery missing %q:\n%s", needle, got) } } if strings.Contains(got, "--token deadbeef") { t.Fatalf("non-procfs process recovery leaked raw token into service args:\n%s", got) } } func TestInstallSHRecoversV5FreeBSDRCServiceState(t *testing.T) { rcScript := filepath.Join(t.TempDir(), "pulse-agent") if err := os.WriteFile(rcScript, []byte(`#!/bin/sh command="/usr/local/bin/pulse-agent" command_args="-url http://192.168.2.96:7655 -token deadbeef -enable-host -insecure -agent-id firewall-1 -hostname 'edge firewall'" export PULSE_CACERT='/conf/pulse-ca.pem' `), 0600); err != nil { t.Fatalf("write rc.d fixture: %v", err) } script := ` PULSE_URL="" PULSE_TOKEN="" INTERVAL="30s" INTERVAL_EXPLICIT="false" ENABLE_HOST="true" HOST_EXPLICIT="false" ENABLE_DOCKER="" DOCKER_EXPLICIT="false" ENABLE_KUBERNETES="" KUBERNETES_EXPLICIT="false" KUBECONFIG_PATH="" ENABLE_PROXMOX="" PROXMOX_EXPLICIT="false" PROXMOX_TYPE="" INSECURE="false" ENABLE_COMMANDS="false" ENROLL="false" HEALTH_ADDR="" HEALTH_ADDR_SET="false" AGENT_ID="" HOSTNAME_OVERRIDE="" STATE_DIR="/var/lib/pulse-agent" TRUENAS_STATE_DIR="/data/pulse-agent" CURL_CA_BUNDLE="" KUBE_INCLUDE_ALL_PODS="false" KUBE_INCLUDE_ALL_DEPLOYMENTS="false" DISK_EXCLUDES=() RUNTIME_TOKEN_FILE="/var/lib/pulse-agent/token" AGENT_NAME="pulse-agent" ` + extractInstallShellFunction(t, "strip_recovered_arg_quotes") + ` ` + extractInstallShellFunction(t, "normalize_recovered_agent_arg_key") + ` ` + extractInstallShellFunction(t, "apply_recovered_agent_arg_value") + ` ` + extractInstallShellFunction(t, "recovered_connection_state_ready") + ` ` + extractTokenRecoveryShellFunctions(t) + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_arg_stream") + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_env_stream") + ` ` + extractInstallShellFunction(t, "split_recovered_shell_words") + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_service_scripts") + ` ` + extractInstallShellFunction(t, "build_exec_arg_items") + ` ` + extractInstallShellFunction(t, "join_exec_arg_items") + ` ` + extractInstallShellFunction(t, "build_exec_args") + ` if recover_connection_state_from_service_scripts "${PULSE_TEST_RC_SCRIPT:?}"; then echo "READY" else echo "NOT_READY" fi build_exec_args printf 'URL=%s\nTOKEN=%s\nINSECURE=%s\nAGENT_ID=%s\nHOSTNAME=%s\nCACERT=%s\nEXEC_ARGS=%s\n' \ "$PULSE_URL" "$PULSE_TOKEN" "$INSECURE" "$AGENT_ID" "$HOSTNAME_OVERRIDE" "$CURL_CA_BUNDLE" "$EXEC_ARGS" ` cmd := exec.Command("bash", "-c", script) cmd.Env = append(os.Environ(), "PULSE_TEST_RC_SCRIPT="+rcScript) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) for _, needle := range []string{ "READY", "URL=http://192.168.2.96:7655", "TOKEN=deadbeef", "INSECURE=true", "AGENT_ID=firewall-1", "HOSTNAME=edge firewall", "CACERT=/conf/pulse-ca.pem", "--token-file /var/lib/pulse-agent/token", } { if !strings.Contains(got, needle) { t.Fatalf("FreeBSD rc.d recovery missing %q:\n%s", needle, got) } } if strings.Contains(got, "--token deadbeef") { t.Fatalf("FreeBSD rc.d recovery leaked raw token into service args:\n%s", got) } } func TestInstallSHRejectsPartialRecoveredProcessConnectionState(t *testing.T) { script := ` PULSE_URL="" PULSE_TOKEN="" INTERVAL="30s" INTERVAL_EXPLICIT="false" ENABLE_HOST="true" HOST_EXPLICIT="false" ENABLE_DOCKER="" DOCKER_EXPLICIT="false" ENABLE_KUBERNETES="" KUBERNETES_EXPLICIT="false" KUBECONFIG_PATH="" ENABLE_PROXMOX="" PROXMOX_EXPLICIT="false" PROXMOX_TYPE="" INSECURE="false" ENABLE_COMMANDS="false" ENROLL="false" HEALTH_ADDR="" HEALTH_ADDR_SET="false" AGENT_ID="" HOSTNAME_OVERRIDE="" STATE_DIR="/var/lib/pulse-agent" CURL_CA_BUNDLE="" KUBE_INCLUDE_ALL_PODS="false" KUBE_INCLUDE_ALL_DEPLOYMENTS="false" DISK_EXCLUDES=() ` + extractInstallShellFunction(t, "strip_recovered_arg_quotes") + ` ` + extractInstallShellFunction(t, "normalize_recovered_agent_arg_key") + ` ` + extractInstallShellFunction(t, "apply_recovered_agent_arg_value") + ` ` + extractInstallShellFunction(t, "recovered_connection_state_ready") + ` ` + extractTokenRecoveryShellFunctions(t) + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_arg_stream") + ` if recover_connection_state_from_arg_stream <<'ARGS' /usr/local/bin/pulse-agent --url http://192.168.2.96:7655 --enable-host --agent-id agent-123 ARGS then echo "UNEXPECTED_SUCCESS" else echo "EXPECTED_FAILURE" fi printf 'URL=%s\nTOKEN=%s\nAGENT_ID=%s\n' "$PULSE_URL" "$PULSE_TOKEN" "$AGENT_ID" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) if !strings.Contains(got, "EXPECTED_FAILURE") { t.Fatalf("partial recovered state should fail closed:\n%s", got) } if strings.Contains(got, "UNEXPECTED_SUCCESS") { t.Fatalf("partial recovered state reported success:\n%s", got) } if !strings.Contains(got, "URL=http://192.168.2.96:7655") || !strings.Contains(got, "AGENT_ID=agent-123") { t.Fatalf("partial recovery should still preserve non-secret fields for later sources:\n%s", got) } if !strings.Contains(got, "TOKEN=") { t.Fatalf("expected token to remain empty:\n%s", got) } } func TestInstallSHRecoversLegacyDefaultTokenFileForSavedStateUpdate(t *testing.T) { stateDir := t.TempDir() if err := os.WriteFile(filepath.Join(stateDir, "token"), []byte("deadbeef\n"), 0600); err != nil { t.Fatalf("write legacy token file: %v", err) } script := ` fail() { echo "FAIL:$1"; exit 99; } PULSE_URL="http://192.168.2.96:7655" PULSE_TOKEN="" INTERVAL="30s" INTERVAL_EXPLICIT="false" ENABLE_HOST="true" HOST_EXPLICIT="false" ENABLE_DOCKER="" DOCKER_EXPLICIT="false" ENABLE_KUBERNETES="" KUBERNETES_EXPLICIT="false" KUBECONFIG_PATH="" ENABLE_PROXMOX="" PROXMOX_EXPLICIT="false" PROXMOX_TYPE="" INSECURE="true" ENABLE_COMMANDS="false" ENROLL="false" HEALTH_ADDR="" HEALTH_ADDR_SET="false" AGENT_ID="" HOSTNAME_OVERRIDE="" STATE_DIR="${PULSE_TEST_STATE_DIR:?}" TRUENAS_STATE_DIR="${PULSE_TEST_STATE_DIR:?}/truenas" CURL_CA_BUNDLE="" KUBE_INCLUDE_ALL_PODS="false" KUBE_INCLUDE_ALL_DEPLOYMENTS="false" DISK_EXCLUDES=() RUNTIME_TOKEN_FILE="${STATE_DIR}/token" ` + extractInstallShellFunction(t, "strip_recovered_arg_quotes") + ` ` + extractInstallShellFunction(t, "normalize_recovered_agent_arg_key") + ` ` + extractInstallShellFunction(t, "apply_recovered_agent_arg_value") + ` ` + extractInstallShellFunction(t, "recovered_connection_state_ready") + ` ` + extractTokenRecoveryShellFunctions(t) + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_arg_stream") + ` ` + extractInstallShellFunction(t, "build_exec_arg_items") + ` ` + extractInstallShellFunction(t, "join_exec_arg_items") + ` ` + extractInstallShellFunction(t, "build_exec_args") + ` if recover_connection_state_from_arg_stream <<'ARGS' /usr/local/bin/pulse-agent -url http://192.168.2.96:7655 -interval 30s -enable-host -enable-docker -agent-id machine-1 -hostname docker1 ARGS then echo "READY" else echo "NOT_READY" fi build_exec_args printf 'URL=%s\nTOKEN=%s\nDOCKER=%s\nAGENT_ID=%s\nHOSTNAME=%s\nEXEC_ARGS=%s\n' \ "$PULSE_URL" "$PULSE_TOKEN" "$ENABLE_DOCKER" "$AGENT_ID" "$HOSTNAME_OVERRIDE" "$EXEC_ARGS" ` cmd := exec.Command("bash", "-c", script) cmd.Env = append(os.Environ(), "PULSE_TEST_STATE_DIR="+stateDir) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) for _, needle := range []string{ "READY", "URL=http://192.168.2.96:7655", "TOKEN=deadbeef", "DOCKER=true", "AGENT_ID=machine-1", "HOSTNAME=docker1", "--token-file", stateDir + "/token", } { if !strings.Contains(got, needle) { t.Fatalf("legacy default token recovery missing %q:\n%s", needle, got) } } if strings.Contains(got, "--token deadbeef") { t.Fatalf("legacy default token recovery leaked raw token into service args:\n%s", got) } } func TestInstallSHCombinesRecoveredProcessArgsAndEnvConnectionState(t *testing.T) { script := ` PULSE_URL="" PULSE_TOKEN="" INTERVAL="30s" INTERVAL_EXPLICIT="false" ENABLE_HOST="true" HOST_EXPLICIT="false" ENABLE_DOCKER="" DOCKER_EXPLICIT="false" ENABLE_KUBERNETES="" KUBERNETES_EXPLICIT="false" KUBECONFIG_PATH="" ENABLE_PROXMOX="" PROXMOX_EXPLICIT="false" PROXMOX_TYPE="" INSECURE="false" ENABLE_COMMANDS="false" ENROLL="false" HEALTH_ADDR="" HEALTH_ADDR_SET="false" AGENT_ID="" HOSTNAME_OVERRIDE="" STATE_DIR="/var/lib/pulse-agent" CURL_CA_BUNDLE="" KUBE_INCLUDE_ALL_PODS="false" KUBE_INCLUDE_ALL_DEPLOYMENTS="false" DISK_EXCLUDES=() ` + extractInstallShellFunction(t, "strip_recovered_arg_quotes") + ` ` + extractInstallShellFunction(t, "normalize_recovered_agent_arg_key") + ` ` + extractInstallShellFunction(t, "apply_recovered_agent_arg_value") + ` ` + extractInstallShellFunction(t, "recovered_connection_state_ready") + ` ` + extractTokenRecoveryShellFunctions(t) + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_arg_stream") + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_env_stream") + ` if recover_connection_state_from_arg_stream <<'ARGS' /usr/local/bin/pulse-agent --url http://192.168.2.96:7655 ARGS then echo "ARGS_READY" else echo "ARGS_PARTIAL" fi if recover_connection_state_from_env_stream <<'ENV' PULSE_TOKEN=deadbeef ENV then echo "ENV_READY" else echo "ENV_PARTIAL" fi printf 'URL=%s\nTOKEN=%s\n' "$PULSE_URL" "$PULSE_TOKEN" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) for _, needle := range []string{ "ARGS_PARTIAL", "ENV_READY", "URL=http://192.168.2.96:7655", "TOKEN=deadbeef", } { if !strings.Contains(got, needle) { t.Fatalf("combined recovery missing %q:\n%s", needle, got) } } } func TestInstallSHUpdateModeMergesExplicitURLWithRunningV5ProcessState(t *testing.T) { script := ` fail() { echo "FAIL:$1"; exit 99; } log_info() { :; } UPDATE_ONLY="true" PULSE_URL="http://192.168.2.96:7655" PULSE_TOKEN="" INTERVAL="30s" INTERVAL_EXPLICIT="false" ENABLE_HOST="true" HOST_EXPLICIT="false" ENABLE_DOCKER="" DOCKER_EXPLICIT="false" ENABLE_KUBERNETES="" KUBERNETES_EXPLICIT="false" KUBECONFIG_PATH="" ENABLE_PROXMOX="" PROXMOX_EXPLICIT="false" PROXMOX_TYPE="" INSECURE="true" ENABLE_COMMANDS="false" ENROLL="false" HEALTH_ADDR="" HEALTH_ADDR_SET="false" AGENT_ID="" HOSTNAME_OVERRIDE="" STATE_DIR="/var/lib/pulse-agent" CURL_CA_BUNDLE="" KUBE_INCLUDE_ALL_PODS="false" KUBE_INCLUDE_ALL_DEPLOYMENTS="false" DISK_EXCLUDES=() RUNTIME_TOKEN_FILE="/var/lib/pulse-agent/token" ` + extractInstallShellFunction(t, "strip_recovered_arg_quotes") + ` ` + extractInstallShellFunction(t, "normalize_recovered_agent_arg_key") + ` ` + extractInstallShellFunction(t, "apply_recovered_agent_arg_value") + ` ` + extractInstallShellFunction(t, "recovered_connection_state_ready") + ` ` + extractInstallShellFunction(t, "update_connection_state_incomplete") + ` ` + extractTokenRecoveryShellFunctions(t) + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_arg_stream") + ` ` + extractInstallShellFunction(t, "build_exec_arg_items") + ` ` + extractInstallShellFunction(t, "join_exec_arg_items") + ` ` + extractInstallShellFunction(t, "build_exec_args") + ` find_connection_state_file() { return 1; } recover_agent_id_from_state_file() { return 1; } recover_connection_state_from_existing_agent() { recover_connection_state_from_arg_stream <<'ARGS' /usr/local/bin/pulse-agent -url http://192.168.2.96:7655 -token deadbeef -interval 30s -enable-host -enable-docker -agent-id machine-1 -hostname docker1 ARGS } if [[ "$UPDATE_ONLY" == "true" ]]; then if update_connection_state_incomplete; then update_conn_env=$(find_connection_state_file || true) if [[ -n "$update_conn_env" ]]; then recover_connection_state "$update_conn_env" fi if update_connection_state_incomplete; then recover_connection_state_from_existing_agent || true fi if [[ -n "$PULSE_URL" && -n "$PULSE_TOKEN" ]]; then echo "READY" elif [[ -z "$PULSE_URL" || -z "$PULSE_TOKEN" ]]; then fail "No existing Pulse Agent connection state found. Use the install command instead." fi if [[ -z "$AGENT_ID" ]]; then AGENT_ID=$(recover_agent_id_from_state_file || true) fi fi fi build_exec_args printf 'URL=%s\nTOKEN=%s\nDOCKER=%s\nDOCKER_EXPLICIT=%s\nAGENT_ID=%s\nHOSTNAME=%s\nEXEC_ARGS=%s\n' \ "$PULSE_URL" "$PULSE_TOKEN" "$ENABLE_DOCKER" "$DOCKER_EXPLICIT" "$AGENT_ID" "$HOSTNAME_OVERRIDE" "$EXEC_ARGS" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) required := []string{ "READY", "URL=http://192.168.2.96:7655", "TOKEN=deadbeef", "DOCKER=true", "DOCKER_EXPLICIT=true", "AGENT_ID=machine-1", "HOSTNAME=docker1", "--token-file /var/lib/pulse-agent/token", } for _, needle := range required { if !strings.Contains(got, needle) { t.Fatalf("reported v5 update recovery missing %q:\n%s", needle, got) } } if strings.Contains(got, "--token deadbeef") { t.Fatalf("reported v5 update recovery leaked raw token into service args:\n%s", got) } } func TestInstallSHUsesCanonicalServiceLifecycleHelpers(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `stop_existing_agent_service() {`, `restart_systemd_agent_service() {`, `restart_openrc_agent_service() {`, `restart_service_command_agent() {`, `restart_sysv_agent_service() {`, `stop_existing_agent_service || true`, `restart_systemd_agent_service`, `restart_openrc_agent_service`, `restart_service_command_agent`, `restart_sysv_agent_service "$RCSCRIPT"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing canonical service lifecycle helper usage: %s", needle) } } } func TestInstallSHUsesCanonicalServiceTeardownHelpers(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `teardown_systemd_agent_service() {`, `teardown_openrc_agent_service() {`, `teardown_freebsd_agent_service() {`, `teardown_sysv_agent_service() {`, `teardown_systemd_agent_service`, `teardown_freebsd_agent_service "/usr/local/etc/rc.d/${AGENT_NAME}"`, `teardown_openrc_agent_service`, `teardown_sysv_agent_service "/etc/init.d/${AGENT_NAME}"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing canonical service teardown helper usage: %s", needle) } } } func TestWriteTrueNASBootstrapScriptUsesCanonicalRenderer(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `write_truenas_bootstrap_script() {`, `require_bootstrap_file() {`, `sync_runtime_binary() {`, `link_service_artifact() {`, `start_agent_service() {`, `ensure_freebsd_agent_enabled() {`, `service_link="/etc/systemd/system/${AGENT_NAME}.service"`, `service_link="/usr/local/etc/rc.d/${AGENT_NAME}"`, `write_truenas_bootstrap_script "$(uname -s)"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing canonical TrueNAS bootstrap renderer content: %s", needle) } } if strings.Count(script, `cat > "$TRUENAS_BOOTSTRAP_SCRIPT"`) != 1 { t.Fatalf("expected one canonical TrueNAS bootstrap writer, found %d", strings.Count(script, `cat > "$TRUENAS_BOOTSTRAP_SCRIPT"`)) } } func TestInstallSHUsesCanonicalQNAPBootstrapRenderer(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `detect_qnap_data_volume() {`, `find_qnap_state_dir() {`, `remove_qnap_autorun_block() {`, `write_qnap_wrapper_script() {`, `append_qnap_autorun_block() {`, `select_platform_state_dir "${QNAP_VOL}/.pulse-agent"`, `write_qnap_wrapper_script "$WRAPPER_SCRIPT" "$RUNTIME_BINARY" "$QNAP_STORED_BINARY" "$QNAP_LOG_DIR" "$STATE_DIR"`, `append_qnap_autorun_block "$AUTORUN_PATH" "$WRAPPER_SCRIPT" "$STATE_DIR"`, `complete_installation_flow "$STATE_DIR" "Installation complete! Agent is running." "Upgrade complete! Agent is running." "tail -f ${AGENT_LOG_FILE}"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing canonical QNAP bootstrap ownership: %s", needle) } } } func TestInstallSHUsesQNAPStateForUninstallRecovery(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `qnap_state_dir=$(find_qnap_state_dir || true)`, `aid_paths+=("$qnap_state_dir/agent-id")`, `if [[ -n "$qnap_state_dir" ]] && trusted_connection_state_file "$qnap_state_dir/connection.env"; then`, `uninstall_nas_platform=$(nas_agent_removal_platform || true)`, `teardown_nas_agent_boot_owner "$uninstall_nas_platform"`, `nas_agent_boot_operation "$platform" observe`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing QNAP uninstall continuity handling: %s", needle) } } } func TestInstallSHAgentDiskHeadroomRejectsSharedLowSpaceFilesystem(t *testing.T) { script := ` set -euo pipefail log_error() { :; } log_info() { :; } log_warn() { :; } INSTALL_DIR="/usr/local/bin" AGENT_MIN_TEMP_FREE_BYTES=$((100 * 1024)) AGENT_MIN_INSTALL_FREE_BYTES=$((80 * 1024)) ` + extractInstallShellFunction(t, "bytes_to_human") + ` ` + extractInstallShellFunction(t, "nearest_existing_dir") + ` ` + extractInstallShellFunction(t, "get_available_bytes_for_path") + ` ` + extractInstallShellFunction(t, "get_filesystem_device_for_path") + ` ` + extractInstallShellFunction(t, "ensure_agent_disk_headroom") + ` df() { if [[ "$1" == "-Pk" ]]; then printf 'Filesystem 1024-blocks Used Available Capacity Mounted on\n' printf '/dev/shared 1000 0 150 0%% /\n' return 0 fi command df "$@" } if ensure_agent_disk_headroom /tmp /usr/local/bin; then echo "ensure_agent_disk_headroom unexpectedly passed on a shared full filesystem" >&2 exit 1 fi ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } } func TestInstallSHAgentDiskHeadroomAcceptsSeparateFilesystems(t *testing.T) { script := ` set -euo pipefail log_error() { :; } log_info() { :; } log_warn() { :; } INSTALL_DIR="/usr/local/bin" AGENT_MIN_TEMP_FREE_BYTES=$((100 * 1024)) AGENT_MIN_INSTALL_FREE_BYTES=$((80 * 1024)) ` + extractInstallShellFunction(t, "bytes_to_human") + ` ` + extractInstallShellFunction(t, "nearest_existing_dir") + ` ` + extractInstallShellFunction(t, "get_available_bytes_for_path") + ` ` + extractInstallShellFunction(t, "get_filesystem_device_for_path") + ` ` + extractInstallShellFunction(t, "ensure_agent_disk_headroom") + ` df() { if [[ "$1" == "-Pk" ]]; then case "$2" in /tmp) printf 'Filesystem 1024-blocks Used Available Capacity Mounted on\n' printf '/dev/tmp 1000 0 120 0%% /tmp\n' return 0 ;; *) printf 'Filesystem 1024-blocks Used Available Capacity Mounted on\n' printf '/dev/root 1000 0 90 0%% /\n' return 0 ;; esac fi command df "$@" } ensure_agent_disk_headroom /tmp /usr/local/bin ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } } func TestInstallSHAgentDiskHeadroomResolvesMissingDirectories(t *testing.T) { script := ` set -euo pipefail log_error() { :; } log_info() { :; } log_warn() { :; } INSTALL_DIR="/usr/local/bin" AGENT_MIN_TEMP_FREE_BYTES=$((100 * 1024)) AGENT_MIN_INSTALL_FREE_BYTES=$((80 * 1024)) ` + extractInstallShellFunction(t, "nearest_existing_dir") + ` resolved=$(nearest_existing_dir /definitely/not/a/real/path/anywhere) if [[ "$resolved" != "/" ]]; then echo "nearest_existing_dir resolved to $resolved, want /" >&2 exit 1 fi resolved=$(nearest_existing_dir /tmp) if [[ "$resolved" != "/tmp" ]]; then echo "nearest_existing_dir resolved to $resolved, want /tmp" >&2 exit 1 fi ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } } // TestInstallSHChecksDiskHeadroomBeforeDownload pins the ENOSPC preflight from // issue #1617: the installer must verify temp and install-dir headroom before // the agent binary download stages anything, and the --preflight-only mode must // report the same check. func TestInstallSHChecksDiskHeadroomBeforeDownload(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) headroomCall := `if ! ensure_agent_disk_headroom "${TMPDIR:-/tmp}" "$INSTALL_DIR"; then` stagingCall := `TMP_BIN=$(mktemp)` headroomPos := strings.Index(script, headroomCall) if headroomPos < 0 { t.Fatalf("install.sh does not gate the download on ensure_agent_disk_headroom") } stagingPos := strings.Index(script, stagingCall) if stagingPos < 0 { t.Fatalf("install.sh no longer stages the download via mktemp; update this test") } if headroomPos > stagingPos { t.Fatalf("disk headroom check happens after the download staging mktemp") } for _, needle := range []string{ `json_event "preflight" "disk_ok" "Sufficient disk space for agent install"`, `json_event "preflight" "disk_low"`, } { if !strings.Contains(script, needle) { t.Fatalf("install.sh preflight-only mode missing disk headroom reporting: %s", needle) } } } func TestInstallSHSetsAgentBinaryModeExplicitly(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) for _, want := range []string{ `chmod 0755 "$TMP_BIN"`, `chmod 0755 "${INSTALL_DIR}/${BINARY_NAME}"`, } { if !strings.Contains(script, want) { t.Fatalf("install.sh does not pin executable mode with %q", want) } } } // TestInstallSHWatchdogPathsUseRotatingAgentLog pins the logging half of issue // #1617: the QNAP and Unraid watchdog loops must not shell-append agent stdout // to an unrotated file on the RAM-backed root; the agent's own rotating writer // (--log-file) owns the agent log instead. func TestInstallSHWatchdogPathsUseRotatingAgentLog(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `if [[ -n "${AGENT_LOG_FILE:-}" ]]; then EXEC_ARG_ITEMS+=(--log-file "$AGENT_LOG_FILE"); fi`, `QNAP_LOG_DIR="${STATE_DIR}/logs"`, `AGENT_LOG_FILE="${QNAP_LOG_DIR}/${AGENT_NAME}.log"`, `UNRAID_LOG_DIR="/var/log/${AGENT_NAME}"`, `AGENT_LOG_FILE="${UNRAID_LOG_DIR}/${AGENT_NAME}.log"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing rotating agent log wiring: %s", needle) } } if got := strings.Count(script, `${EXEC_ARGS} > /dev/null 2>> "\$WATCHDOG_LOG"`); got != 2 { t.Fatalf("expected both watchdog loops (QNAP, Unraid) to discard the stdout mirror and capture stderr in the watchdog log, found %d", got) } if strings.Contains(script, `${EXEC_ARGS} >> /var/log/${AGENT_NAME}.log`) { t.Fatalf("a watchdog loop still shell-appends agent output to the unrotated /var/log/${AGENT_NAME}.log") } } // TestInstallSHQNAPWatchdogIsSingleton pins the process-ownership half of issue // #1617. QNAP can launch the persistent wrapper both from autorun.sh and from an // install/upgrade, so the wrapper must elect one supervisor before killing or // starting an agent and must clean up only state that it owns. func TestInstallSHQNAPWatchdogIsSingleton(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `WATCHDOG_PIDFILE="${state_dir}/${AGENT_NAME}.watchdog.pid"`, `AGENT_PIDFILE="${state_dir}/${AGENT_NAME}.pid"`, `LOCK_DIR="${state_dir}/${AGENT_NAME}.watchdog.lock"`, `while ! mkdir "\$LOCK_DIR" 2>/dev/null; do`, `if pid_is_live "\$_owner"; then`, `if ! acquire_watchdog_lock; then`, `[ "\$(cat "\$WATCHDOG_PIDFILE" 2>/dev/null)" = "\$\$" ]`, `trap cleanup_watchdog EXIT`, `trap shutdown_watchdog INT TERM HUP`, `CURRENT_AGENT_PID=\$!`, `echo "\$CURRENT_AGENT_PID" > "\$AGENT_PIDFILE"`, `wait "\$CURRENT_AGENT_PID"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing QNAP watchdog singleton contract: %s", needle) } } lockPos := strings.Index(script, `if ! acquire_watchdog_lock; then`) killPos := strings.Index(script, `pkill -x "pulse-agent" 2>/dev/null || true`) if lockPos < 0 || killPos < 0 || lockPos > killPos { t.Fatalf("QNAP wrapper must acquire singleton ownership before killing an existing agent") } } func TestRenderedQNAPWatchdogRejectsASecondSupervisor(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) start := strings.Index(script, "write_qnap_wrapper_script() {") if start < 0 { t.Fatal("install.sh missing QNAP wrapper renderer") } endOffset := strings.Index(script[start:], "\nappend_qnap_autorun_block() {") if endOffset < 0 { t.Fatal("could not isolate QNAP wrapper renderer") } renderer := script[start : start+endOffset] tempDir := t.TempDir() stateDir := filepath.Join(tempDir, "state") logDir := filepath.Join(stateDir, "logs") wrapperPath := filepath.Join(stateDir, "start-pulse-agent.sh") storedPath := filepath.Join(stateDir, "stored-agent") runtimePath := filepath.Join(tempDir, "runtime", "pulse-agent") startsPath := filepath.Join(tempDir, "agent-starts") mockBinDir := filepath.Join(tempDir, "bin") for _, dir := range []string{stateDir, mockBinDir} { if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatalf("create test directory: %v", err) } } agentScript := "#!/bin/sh\n" + "echo \"$$\" >> " + strconv.Quote(startsPath) + "\n" + "trap 'exit 0' INT TERM HUP\n" + "while :; do sleep 1; done\n" if err := os.WriteFile(storedPath, []byte(agentScript), 0o755); err != nil { t.Fatalf("write fake agent: %v", err) } if err := os.WriteFile(filepath.Join(mockBinDir, "pkill"), []byte("#!/bin/sh\nexit 0\n"), 0o755); err != nil { t.Fatalf("write fake pkill: %v", err) } harness := renderer + ` AGENT_NAME=pulse-agent SHELL_EXPORT_LINES="" EXEC_ARGS="" write_qnap_wrapper_script "$1" "$2" "$3" "$4" "$5" ` render := exec.Command("bash", "-c", harness, "_", wrapperPath, runtimePath, storedPath, logDir, stateDir) if output, err := render.CombinedOutput(); err != nil { t.Fatalf("render QNAP wrapper: %v\n%s", err, output) } env := []string{"PATH=" + mockBinDir + ":/usr/bin:/bin"} first := exec.Command("sh", wrapperPath) first.Env = env if err := first.Start(); err != nil { t.Fatalf("start first watchdog: %v", err) } t.Cleanup(func() { if first.Process != nil { _ = first.Process.Signal(syscall.SIGTERM) _, _ = first.Process.Wait() } }) watchdogPIDFile := filepath.Join(stateDir, "pulse-agent.watchdog.pid") agentPIDFile := filepath.Join(stateDir, "pulse-agent.pid") waitForFile := func(path string) { t.Helper() deadline := time.Now().Add(5 * time.Second) for time.Now().Before(deadline) { if _, err := os.Stat(path); err == nil { return } time.Sleep(25 * time.Millisecond) } t.Fatalf("timed out waiting for %s", path) } waitForFile(watchdogPIDFile) waitForFile(agentPIDFile) waitForFile(startsPath) second := exec.Command("sh", wrapperPath) second.Env = env if output, err := second.CombinedOutput(); err != nil { t.Fatalf("second watchdog should exit cleanly: %v\n%s", err, output) } starts, err := os.ReadFile(startsPath) if err != nil { t.Fatalf("read fake-agent starts: %v", err) } if got := len(strings.Fields(string(starts))); got != 1 { t.Fatalf("expected one supervised agent after two watchdog launches, got %d", got) } agentPIDBytes, err := os.ReadFile(agentPIDFile) if err != nil { t.Fatalf("read agent pid: %v", err) } agentPID, err := strconv.Atoi(strings.TrimSpace(string(agentPIDBytes))) if err != nil { t.Fatalf("parse agent pid: %v", err) } if err := first.Process.Signal(syscall.SIGTERM); err != nil { t.Fatalf("stop first watchdog: %v", err) } if err := first.Wait(); err != nil { t.Fatalf("wait for first watchdog shutdown: %v", err) } first.Process = nil if err := exec.Command("kill", "-0", strconv.Itoa(agentPID)).Run(); err == nil { t.Fatalf("watchdog shutdown left agent pid %d alive", agentPID) } for _, path := range []string{watchdogPIDFile, agentPIDFile, filepath.Join(stateDir, "pulse-agent.watchdog.lock")} { if _, err := os.Stat(path); !errors.Is(err, os.ErrNotExist) { t.Fatalf("watchdog shutdown left singleton state %s (stat: %v)", path, err) } } } func TestInstallSHUsesSharedServiceRenderers(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `render_systemd_agent_unit() {`, `render_freebsd_rc_agent_script() {`, `render_systemd_agent_unit "$UNIT" "${INSTALL_DIR}/${BINARY_NAME}" "${EXEC_ARGS}" "network.target" "" "" ""`, `render_systemd_agent_unit "$TRUENAS_SERVICE_STORAGE" "${TRUENAS_RUNTIME_BINARY}" "${EXEC_ARGS}" "network-online.target docker.service" "network-online.target" "root" "${TRUENAS_LOG_TARGET}"`, // The Linux systemd unit takes the resolved service user so the // least-privilege profile can swap root for pulse-agent. `render_systemd_agent_unit "$UNIT" "${INSTALL_DIR}/${BINARY_NAME}" "${EXEC_ARGS}" "network-online.target docker.service" "network-online.target" "$SERVICE_USER" ""`, `render_freebsd_rc_agent_script "$TRUENAS_SERVICE_STORAGE" "${TRUENAS_RUNTIME_BINARY}" "${EXEC_ARGS}"`, `render_freebsd_rc_agent_script "$RCSCRIPT" "${INSTALL_DIR}/${BINARY_NAME}" "${EXEC_ARGS}"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing shared service renderer usage: %s", needle) } } } func TestInstallSHPersistsRootlessContainerRuntimeServiceEnvironment(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `ROOTLESS_RUNTIME_SOCKET_URI=""`, `system_docker_runtime_is_active() {`, `discover_rootless_container_runtime() {`, `discover_safe_profile_rootless_container_runtime() {`, `recover_safe_profile_rootless_runtime_pin() {`, `resolve_initial_container_monitoring_detection() {`, `resolve_safe_profile_container_runtime() {`, `configure_discovered_rootless_runtime_environment() {`, // Issue #1647: a live rootful Docker must win before any rootless // socket discovery pins podman variables into the service environment. `if system_docker_runtime_is_active; then`, `discover_single_socket_match "${rootless_root}/*/docker.sock"`, `discover_single_socket_match "${rootless_root}/*/podman/podman.sock"`, `append_service_env "DOCKER_HOST" "$ROOTLESS_RUNTIME_SOCKET_URI"`, `append_service_env "PULSE_DOCKER_RUNTIME" "podman"`, `append_service_env "CONTAINER_HOST" "$ROOTLESS_RUNTIME_SOCKET_URI"`, `append_service_env "PODMAN_HOST" "$ROOTLESS_RUNTIME_SOCKET_URI"`, `append_service_env "XDG_RUNTIME_DIR" "$ROOTLESS_RUNTIME_XDG_DIR"`, `Multiple collector-owned rootless container-runtime sockets are usable`, `provision_least_privilege_user`, `discover_safe_profile_rootless_container_runtime`, `env_line="$SYSTEMD_ENV_LINES"`, `local service_env_lines="$SHELL_EXPORT_LINES"`, `${PLIST_ENV_BLOCK}`, `respawn limit 5 10${UPSTART_ENV_LINES}`, `sed -i "s|SSL_CERT_FILE_PLACEHOLDER|${SED_EXPORT_LINES}|g" "$INITSCRIPT"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing rootless service environment persistence contract: %s", needle) } } } func TestInstallSHSafeProfileRootlessDiscoveryIsCollectorOwnedAndUnambiguous(t *testing.T) { tmpDir, err := os.MkdirTemp("/tmp", "pulse-safe-rootless-*") if err != nil { t.Fatalf("mktemp dir: %v", err) } defer os.RemoveAll(tmpDir) dockerDir := filepath.Join(tmpDir, "run", "user", "991") podmanDir := filepath.Join(tmpDir, "run", "user", "991", "podman") if err := os.MkdirAll(dockerDir, 0o755); err != nil { t.Fatalf("mkdir Docker runtime dir: %v", err) } if err := os.MkdirAll(podmanDir, 0o755); err != nil { t.Fatalf("mkdir Podman runtime dir: %v", err) } dockerPath := filepath.Join(dockerDir, "docker.sock") dockerListener, err := net.Listen("unix", dockerPath) if err != nil { t.Fatalf("listen Docker socket: %v", err) } defer dockerListener.Close() podmanPath := filepath.Join(podmanDir, "podman.sock") podmanListener, err := net.Listen("unix", podmanPath) if err != nil { t.Fatalf("listen Podman socket: %v", err) } defer podmanListener.Close() common := ` set -euo pipefail LEAST_PRIVILEGE_USER=pulse-agent PULSE_ROOTLESS_RUNTIME_ROOT="` + filepath.Join(tmpDir, "run", "user") + `" log_warn() { :; } uname() { echo Linux; } id() { [[ "$1" == "-u" && "$2" == "pulse-agent" ]] && echo 991; } runuser() { return 0; } ` + extractInstallShellFunction(t, "discover_safe_profile_rootless_container_runtime") + ` ` oneOwned := common + ` stat() { [[ "$2" == "%a" ]] && { echo 600; return; } case "$3" in "` + dockerPath + `") echo 991 ;; "` + podmanPath + `") echo 1000 ;; *) return 1 ;; esac } discover_safe_profile_rootless_container_runtime [[ "$ROOTLESS_RUNTIME_KIND" == docker ]] [[ "$ROOTLESS_RUNTIME_SOCKET_PATH" == "` + dockerPath + `" ]] ` if out, err := exec.Command("bash", "-c", oneOwned).CombinedOutput(); err != nil { t.Fatalf("single collector-owned socket: %v\n%s", err, out) } ambiguous := common + ` stat() { [[ "$2" == "%a" ]] && echo 600 || echo 991; } if discover_safe_profile_rootless_container_runtime; then echo "ambiguous sockets were accepted" >&2 exit 1 fi [[ -z "$ROOTLESS_RUNTIME_SOCKET_PATH" ]] ` if out, err := exec.Command("bash", "-c", ambiguous).CombinedOutput(); err != nil { t.Fatalf("ambiguous collector-owned sockets: %v\n%s", err, out) } crossUser := common + ` stat() { echo 1000; } if discover_safe_profile_rootless_container_runtime; then echo "cross-user sockets were accepted" >&2 exit 1 fi ` if out, err := exec.Command("bash", "-c", crossUser).CombinedOutput(); err != nil { t.Fatalf("cross-user sockets: %v\n%s", err, out) } unreadable := common + ` stat() { [[ "$2" == "%a" ]] && { echo 600; return; } case "$3" in "` + dockerPath + `") echo 991 ;; *) echo 1000 ;; esac } runuser() { return 1; } if discover_safe_profile_rootless_container_runtime; then echo "collector-unreadable socket was accepted" >&2 exit 1 fi ` if out, err := exec.Command("bash", "-c", unreadable).CombinedOutput(); err != nil { t.Fatalf("collector-unreadable socket: %v\n%s", err, out) } } func TestInstallSHSafeProfileRejectsSymlinkedRootlessSocket(t *testing.T) { tmpDir, err := os.MkdirTemp("/tmp", "psr-") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpDir) runtimeRoot := filepath.Join(tmpDir, "run", "user") runtimeDir := filepath.Join(runtimeRoot, "991") if err := os.MkdirAll(runtimeDir, 0o755); err != nil { t.Fatal(err) } realPath := filepath.Join(runtimeDir, "real.sock") listener, err := net.Listen("unix", realPath) if err != nil { t.Fatal(err) } defer listener.Close() if err := os.Symlink(realPath, filepath.Join(runtimeDir, "docker.sock")); err != nil { t.Fatal(err) } script := ` set -euo pipefail LEAST_PRIVILEGE_USER=pulse-agent PULSE_ROOTLESS_RUNTIME_ROOT="` + runtimeRoot + `" log_warn() { :; } uname() { echo Linux; } id() { [[ "$1" == -u && "$2" == pulse-agent ]] && echo 991; } stat() { echo 991; } runuser() { return 0; } ` + extractInstallShellFunction(t, "discover_safe_profile_rootless_container_runtime") + ` if discover_safe_profile_rootless_container_runtime; then echo "symlinked runtime socket was accepted" >&2 exit 1 fi ` if out, err := exec.Command("bash", "-c", script).CombinedOutput(); err != nil { t.Fatalf("symlinked safe rootless socket: %v\n%s", err, out) } } func TestInstallSHSafeProfileDefersDetectionWithoutDaemonProbe(t *testing.T) { script := ` set -euo pipefail DOCKER_EXPLICIT=false LEAST_PRIVILEGE=true PRIVILEGED_HELPER_ENABLED=true SAFE_PROFILE_DOCKER_DETECTION_DEFERRED=false ENABLE_DOCKER=true probe_log="${TMPDIR}/probe-log" log_info() { :; } detect_docker() { echo daemon-probe >> "$probe_log"; return 0; } ` + extractInstallShellFunction(t, "resolve_initial_container_monitoring_detection") + ` resolve_initial_container_monitoring_detection [[ "$SAFE_PROFILE_DOCKER_DETECTION_DEFERRED" == true ]] [[ ! -e "$probe_log" ]] ` cmd := exec.Command("bash", "-c", script) cmd.Env = append(os.Environ(), "TMPDIR="+t.TempDir()) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("deferred safe detection: %v\n%s", err, out) } } func TestInstallSHSafeProfileResolvesRuntimeOnlyAfterCollectorProvisioning(t *testing.T) { script := ` set -euo pipefail SAFE_PROFILE_DOCKER_DETECTION_DEFERRED=true ENABLE_DOCKER=false configured=false log_info() { :; } recover_safe_profile_rootless_runtime_pin() { return 1; } discover_safe_profile_rootless_container_runtime() { ROOTLESS_RUNTIME_KIND=docker ROOTLESS_RUNTIME_SOCKET_PATH=/run/user/991/docker.sock ROOTLESS_RUNTIME_SOCKET_URI=unix:///run/user/991/docker.sock ROOTLESS_RUNTIME_XDG_DIR=/run/user/991 return 0 } safe_profile_fixed_container_runtime_socket_present() { echo unexpected-fixed-probe >&2; return 1; } configure_discovered_rootless_runtime_environment() { configured=true; } safe_profile_apply_docker_degradation() { :; } ` + extractInstallShellFunction(t, "resolve_safe_profile_container_runtime") + ` resolve_safe_profile_container_runtime [[ "$ENABLE_DOCKER" == true && "$configured" == true ]] ` if out, err := exec.Command("bash", "-c", script).CombinedOutput(); err != nil { t.Fatalf("post-provision safe runtime resolution: %v\n%s", err, out) } } func TestInstallSHRecoversExactRootlessPinWhileSocketIsOffline(t *testing.T) { tmpDir := t.TempDir() unitPath := filepath.Join(tmpDir, "pulse-agent.service") rootlessRoot := filepath.Join(tmpDir, "run", "user") expectedPath := filepath.Join(rootlessRoot, "991", "docker.sock") unit := "[Service]\nUser=pulse-agent\nEnvironment=\"DOCKER_HOST=unix://" + expectedPath + "\"\n" if err := os.WriteFile(unitPath, []byte(unit), 0o644); err != nil { t.Fatal(err) } script := ` set -euo pipefail AGENT_NAME=pulse-agent LEAST_PRIVILEGE_USER=pulse-agent PULSE_AGENT_SYSTEMD_UNIT_PATH="` + unitPath + `" PULSE_ROOTLESS_RUNTIME_ROOT="` + rootlessRoot + `" ROOTLESS_RUNTIME_KIND="" ROOTLESS_RUNTIME_SOCKET_PATH="" ROOTLESS_RUNTIME_SOCKET_URI="" ROOTLESS_RUNTIME_XDG_DIR="" id() { [[ "$1" == -u && "$2" == pulse-agent ]] && echo 991; } stat() { case "$2" in %u) echo 0 ;; %a) echo 644 ;; *) return 1 ;; esac } ` + extractInstallShellFunction(t, "recover_safe_profile_rootless_runtime_pin") + ` if [[ "${EXPECT_REJECT:-false}" == true ]]; then if recover_safe_profile_rootless_runtime_pin; then exit 9; fi exit 0 fi recover_safe_profile_rootless_runtime_pin [[ "$ROOTLESS_RUNTIME_KIND" == docker ]] [[ "$ROOTLESS_RUNTIME_SOCKET_PATH" == "` + expectedPath + `" ]] [[ "$ROOTLESS_RUNTIME_SOCKET_URI" == "unix://` + expectedPath + `" ]] [[ ! -e "$ROOTLESS_RUNTIME_SOCKET_PATH" ]] ` if out, err := exec.Command("bash", "-c", script).CombinedOutput(); err != nil { t.Fatalf("recover offline rootless pin: %v\n%s", err, out) } conflicting := strings.Replace(unit, "\n", "\nEnvironment=\"PODMAN_HOST=unix://"+filepath.Join(rootlessRoot, "991", "podman", "podman.sock")+"\"\n", 1) if err := os.WriteFile(unitPath, []byte(conflicting), 0o644); err != nil { t.Fatal(err) } cmd := exec.Command("bash", "-c", script) cmd.Env = append(os.Environ(), "EXPECT_REJECT=true") if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("conflicting recovered pin was not rejected: %v\n%s", err, out) } } func TestInstallSHServiceEnvAccumulatorRendersRootlessSocketVariables(t *testing.T) { script := ` APPLIED_SERVICE_ENV_KEYS="|" SYSTEMD_ENV_LINES="" SHELL_EXPORT_LINES="" UPSTART_ENV_LINES="" SED_EXPORT_LINES="" PLIST_ENV_ENTRIES="" PLIST_ENV_BLOCK="" ` + extractInstallShellFunction(t, "xml_escape") + ` ` + extractInstallShellFunction(t, "service_env_has_key") + ` ` + extractInstallShellFunction(t, "shell_export_value") + ` ` + extractInstallShellFunction(t, "append_service_env") + ` ` + extractInstallShellFunction(t, "finalize_plist_env_block") + ` append_service_env "DOCKER_HOST" "unix:///run/user/1000/docker.sock" append_service_env "XDG_RUNTIME_DIR" "/run/user/1000" append_service_env "DOCKER_HOST" "unix:///run/user/2000/docker.sock" append_service_env "PULSE_DOCKER_RUNTIME" "podman" append_service_env "CONTAINER_HOST" "unix:///run/user/1000/podman/podman.sock" append_service_env "PODMAN_HOST" "unix:///run/user/1000/podman/podman.sock" finalize_plist_env_block printf '%s\n---shell---\n%s\n---sed---\n%s\n---plist---\n%s\n' "$SYSTEMD_ENV_LINES" "$SHELL_EXPORT_LINES" "$SED_EXPORT_LINES" "$PLIST_ENV_BLOCK" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) required := []string{ `Environment="DOCKER_HOST=unix:///run/user/1000/docker.sock"`, `Environment="XDG_RUNTIME_DIR=/run/user/1000"`, `export DOCKER_HOST="unix:///run/user/1000/docker.sock"`, `export XDG_RUNTIME_DIR="/run/user/1000"`, `export DOCKER_HOST="unix:///run/user/1000/docker.sock"; export XDG_RUNTIME_DIR="/run/user/1000"`, `DOCKER_HOST`, `unix:///run/user/1000/docker.sock`, `Environment="PULSE_DOCKER_RUNTIME=podman"`, `Environment="CONTAINER_HOST=unix:///run/user/1000/podman/podman.sock"`, `Environment="PODMAN_HOST=unix:///run/user/1000/podman/podman.sock"`, } for _, needle := range required { if !strings.Contains(got, needle) { t.Fatalf("service env output missing %s:\n%s", needle, got) } } if strings.Contains(got, "unix:///run/user/2000/docker.sock") { t.Fatalf("service env accumulator did not ignore duplicate key:\n%s", got) } } // TestInstallSHIssue1647RootfulDockerOutranksRootlessPodmanSocket pins the fix // for issue #1647: a transient rootless Podman API socket (socket-activated for // a login session) must not win runtime discovery while a rootful Docker daemon // is alive, or the agent service gets podman variables pinned to a socket that // disappears when the session ends. func TestInstallSHIssue1647RootfulDockerOutranksRootlessPodmanSocket(t *testing.T) { tmpDir, err := os.MkdirTemp("/tmp", "pulse-1647-*") if err != nil { t.Fatalf("mktemp dir: %v", err) } defer os.RemoveAll(tmpDir) podmanDir := filepath.Join(tmpDir, "run", "user", "1000", "podman") if err := os.MkdirAll(podmanDir, 0o755); err != nil { t.Fatalf("mkdir podman socket dir: %v", err) } socketPath := filepath.Join(podmanDir, "podman.sock") listener, err := net.Listen("unix", socketPath) if err != nil { t.Fatalf("listen unix socket: %v", err) } defer listener.Close() common := ` set -uo pipefail log_warn() { :; } log_info() { :; } uname() { echo Linux; } PULSE_ROOTLESS_RUNTIME_ROOT="` + filepath.Join(tmpDir, "run", "user") + `" PULSE_SYSTEM_DOCKER_SOCKET="` + filepath.Join(tmpDir, "absent", "docker.sock") + `" ` + extractInstallShellFunction(t, "system_docker_runtime_is_active") + ` ` + extractInstallShellFunction(t, "discover_single_socket_match") + ` ` + extractInstallShellFunction(t, "discover_rootless_container_runtime") + ` ` liveDocker := common + ` docker() { return 0; } if discover_rootless_container_runtime; then echo "rootless podman socket must not outrank a live rootful docker" >&2 exit 1 fi ` out, err := exec.Command("bash", "-c", liveDocker).CombinedOutput() if err != nil { t.Fatalf("live rootful docker case: %v\n%s", err, out) } deadDocker := common + ` docker() { return 1; } if ! discover_rootless_container_runtime; then echo "expected rootless podman discovery when no rootful docker answers" >&2 exit 1 fi if [[ "$ROOTLESS_RUNTIME_KIND" != "podman" ]]; then echo "kind=$ROOTLESS_RUNTIME_KIND, want podman" >&2 exit 1 fi if [[ "$ROOTLESS_RUNTIME_SOCKET_URI" != "unix://` + socketPath + `" ]]; then echo "uri=$ROOTLESS_RUNTIME_SOCKET_URI" >&2 exit 1 fi ` out, err = exec.Command("bash", "-c", deadDocker).CombinedOutput() if err != nil { t.Fatalf("no rootful docker case: %v\n%s", err, out) } } func TestInstallSHDiscoverSingleSocketMatch(t *testing.T) { tmpDir, err := os.MkdirTemp("/tmp", "pulse-sock-*") if err != nil { t.Fatalf("mktemp socket dir: %v", err) } defer os.RemoveAll(tmpDir) socketPath := filepath.Join(tmpDir, "docker.sock") listener, err := net.Listen("unix", socketPath) if err != nil { t.Fatalf("listen unix socket: %v", err) } defer listener.Close() script := ` ` + extractInstallShellFunction(t, "discover_single_socket_match") + ` discover_single_socket_match "` + filepath.ToSlash(tmpDir) + `/*.sock" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if strings.TrimSpace(string(out)) != filepath.ToSlash(socketPath) { t.Fatalf("socket match = %q, want %q", strings.TrimSpace(string(out)), filepath.ToSlash(socketPath)) } } func TestInstallSHFreeBSDRendererUsesDaemonSupervisorPidfile(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `child_pidfile="/var/run/\${name}.child.pid"`, `pulse_agent_supervisor_pid()`, `parent_pid=\$(ps -o ppid= -p "\${agent_pid}" 2>/dev/null | tr -d '[:space:]')`, `daemon:*)`, `/usr/sbin/daemon -r -P \${pidfile} -p \${child_pidfile} -f "\${command}" \${command_args}`, `kill -KILL "\${supervisor_pid}" 2>/dev/null || return 1`, `pulse_agent_wait_stopped "\${supervisor_pid}" || return 1`, `rm -f \${pidfile} \${child_pidfile}`, `legacy child pid \${legacy_pid} supervised by pid \${legacy_supervisor}`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing FreeBSD daemon supervisor contract: %s", needle) } } if strings.Contains(script, `/usr/sbin/daemon -r -p \${pidfile}`) { t.Fatal("install.sh still writes the child pid to the service pidfile under daemon -r") } } // TestInstallSHFreeBSDAgentLogsUseRotatingWriter pins the FreeBSD half of the // log-capture fix for issue #2123: rc.d has no journal and daemon(8) does not // capture the child's stdout, so the agent must own a rotating --log-file and // the completion hint must point at it. pfSense has no /var/log/messages, so // the old system-log hint is removed entirely rather than repointed. func TestInstallSHFreeBSDAgentLogsUseRotatingWriter(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `AGENT_LOG_FILE="${STATE_DIR}/logs/${AGENT_NAME}.log"`, `AGENT_LOG_FILE="${TRUENAS_LOG_DIR}/${AGENT_NAME}.log"`, `complete_installation_flow "$STATE_DIR" "Installation complete! Agent is running." "Upgrade complete! Agent restarted with new configuration." "tail -f ${AGENT_LOG_FILE}"`, `log_info "To view logs: tail -f ${AGENT_LOG_FILE}"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing FreeBSD rotating agent log wiring: %s", needle) } } if strings.Contains(script, "tail -f /var/log/messages") { t.Fatal("install.sh still points FreeBSD/pfSense users at /var/log/messages, which does not exist on pfSense") } } // TestInstallSHFreeBSDBuildArgsPassesLogFile verifies the FreeBSD rc.d service // args actually carry --log-file once AGENT_LOG_FILE is set. func TestInstallSHFreeBSDBuildArgsPassesLogFile(t *testing.T) { script := ` PULSE_URL="http://pulse.local:7655" PULSE_TOKEN="" INTERVAL="30s" INTERVAL_EXPLICIT="false" ENABLE_HOST="true" HOST_EXPLICIT="false" ENABLE_DOCKER="" DOCKER_EXPLICIT="false" ENABLE_KUBERNETES="" KUBERNETES_EXPLICIT="false" KUBECONFIG_PATH="" ENABLE_PROXMOX="" PROXMOX_EXPLICIT="false" PROXMOX_TYPE="" INSECURE="false" ENABLE_COMMANDS="false" ENROLL="false" HEALTH_ADDR="" HEALTH_ADDR_SET="false" AGENT_ID="" HOSTNAME_OVERRIDE="" STATE_DIR="/var/lib/pulse-agent" CURL_CA_BUNDLE="" KUBE_INCLUDE_ALL_PODS="false" KUBE_INCLUDE_ALL_DEPLOYMENTS="false" DISK_EXCLUDES=() DISK_INCLUDES=() RUNTIME_TOKEN_FILE="" AGENT_LOG_FILE="/var/lib/pulse-agent/logs/pulse-agent.log" ` + extractInstallShellFunction(t, "build_exec_arg_items") + ` ` + extractInstallShellFunction(t, "join_exec_arg_items") + ` ` + extractInstallShellFunction(t, "build_exec_args") + ` build_exec_args printf 'EXEC_ARGS=%s\n' "$EXEC_ARGS" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) if !strings.Contains(got, "--log-file /var/lib/pulse-agent/logs/pulse-agent.log") { t.Fatalf("FreeBSD service args missing rotating --log-file:\n%s", got) } } func TestInstallSHUsesCanonicalCompletionHelper(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `complete_installation_flow() {`, `save_connection_info "$state_dir"`, `json_event "complete" "updated" "Installation updated"`, `json_event "complete" "installed" "Installation installed"`, `json_event "complete" "updated_unhealthy" "Agent updated but not responding"`, `json_event "complete" "installed_unhealthy" "Agent installed but not responding"`, `complete_installation_flow "$STATE_DIR" "Installation complete! Agent is running." "Upgrade complete! Agent restarted with new configuration." "tail -f $LOG_FILE"`, `complete_installation_flow "$UNRAID_STORAGE_DIR" "Installation complete! Agent is running." "Upgrade complete! Agent is running." "tail -f ${AGENT_LOG_FILE}"`, `complete_installation_flow "$STATE_DIR" "Installation complete! Agent is running." "Upgrade complete! Agent is running." "tail -f ${AGENT_LOG_FILE}"`, `complete_installation_flow "$TRUENAS_STATE_DIR" "Installation complete! Agent is running." "Upgrade complete! Agent is running." ""`, `complete_installation_flow "$STATE_DIR" "Installation complete! Agent is running." "Upgrade complete! Agent restarted with new configuration." "tail -f ${AGENT_LOG_FILE}"`, `complete_installation_flow "$STATE_DIR" "Installation complete! Agent is running." "Upgrade complete! Agent restarted with new configuration." "journalctl -u ${AGENT_NAME} --no-pager -n 20"`, `complete_installation_flow "$STATE_DIR" "Installation complete! Agent is running." "Upgrade complete! Agent restarted with new configuration." "tail -f /var/log/${AGENT_NAME}.log"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing canonical completion helper usage: %s", needle) } } } func TestInstallSHUsesCanonicalFreeBSDAgentEnablement(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `freebsd_enable_snippet() {`, `ensure_freebsd_agent_enabled() {`, `service_management_functions="$(freebsd_enable_snippet)`, `ensure_freebsd_agent_enabled`, `apply_freebsd_agent_enablement() {`, `eval "$(freebsd_enable_snippet)"`, `apply_freebsd_agent_enablement`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing canonical FreeBSD enablement ownership: %s", needle) } } if strings.Count(script, `grep -q "pulse_agent_enable" /etc/rc.conf`) != 1 { t.Fatalf("expected one canonical FreeBSD enablement definition, found %d", strings.Count(script, `grep -q "pulse_agent_enable" /etc/rc.conf`)) } } func TestInstallSHUsesCanonicalFreeBSDAgentTeardown(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `teardown_freebsd_agent_service() {`, `quiesce_freebsd_agent_for_removal "$service_path" ||`, `timeout -k 1 15 "$service_path" onestop >/dev/null 2>&1 || return 1`, `timeout -k 1 5 sysrc pulse_agent_enable=NO >/dev/null 2>&1 || return 1`, `rm -f /usr/local/etc/rc.d/pulse_agent.sh`, `rm -f /var/run/pulse_agent.pid /var/run/pulse_agent.child.pid`, `uninstall_legacy_manager=FreeBSD`, `teardown_freebsd_agent_service "/usr/local/etc/rc.d/${AGENT_NAME}"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing canonical FreeBSD teardown contract: %s", needle) } } } func TestInstallSHUsesCanonicalSysVEnablementHelper(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `enable_sysv_agent_service() {`, `update-rc.d "${AGENT_NAME}" defaults >/dev/null 2>&1 || true`, `chkconfig --add "${AGENT_NAME}" >/dev/null 2>&1 || true`, `chkconfig "${AGENT_NAME}" on >/dev/null 2>&1 || true`, `enable_sysv_agent_service "$INITSCRIPT"`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing canonical SysV enablement ownership: %s", needle) } } if strings.Count(script, `update-rc.d "${AGENT_NAME}" defaults >/dev/null 2>&1 || true`) != 1 { t.Fatalf("expected one canonical SysV update-rc.d enable path, found %d", strings.Count(script, `update-rc.d "${AGENT_NAME}" defaults >/dev/null 2>&1 || true`)) } } // TestUnraidGoScriptCleanup verifies the sed commands that remove pulse entries // from /boot/config/go without disturbing other entries. func TestUnraidGoScriptCleanup(t *testing.T) { cases := []struct { name string before string after string }{ { name: "pulse entry with trailing blank line", before: `#!/bin/bash /boot/config/pulse/telegraf/start_telegraf.sh # Pulse Agent bash /boot/config/plugins/pulse-agent/start-pulse-agent.sh # Other stuff echo hello `, // The comment and command lines are removed; the trailing blank line remains. // This is harmless in /boot/config/go. after: `#!/bin/bash /boot/config/pulse/telegraf/start_telegraf.sh # Other stuff echo hello `, }, { name: "pulse entry without trailing blank line", before: `#!/bin/bash # Pulse Agent bash /boot/config/plugins/pulse-agent/start-pulse-agent.sh # Other stuff echo hello `, after: `#!/bin/bash # Other stuff echo hello `, }, { name: "no pulse entries - unchanged", before: `#!/bin/bash echo hello echo world `, after: `#!/bin/bash echo hello echo world `, }, { name: "telegraf line containing pulse is kept", before: `#!/bin/bash /boot/config/pulse/telegraf/start_telegraf.sh # Pulse Agent bash /boot/config/plugins/pulse-agent/start-pulse-agent.sh echo done `, // The telegraf line is NOT removed (no "pulse-agent" in it). // Comment and command lines are deleted individually. after: `#!/bin/bash /boot/config/pulse/telegraf/start_telegraf.sh echo done `, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { dir := t.TempDir() goScript := filepath.Join(dir, "go") if err := os.WriteFile(goScript, []byte(tc.before), 0755); err != nil { t.Fatal(err) } // Run the exact same sed commands from install.sh (line-by-line, not range-based) script := ` GO_SCRIPT="` + goScript + `" # Remove unified agent entries sed -i '' '/^# Pulse Agent$/d' "$GO_SCRIPT" 2>/dev/null || sed -i '/^# Pulse Agent$/d' "$GO_SCRIPT" 2>/dev/null || true sed -i '' '/pulse-agent/d' "$GO_SCRIPT" 2>/dev/null || sed -i '/pulse-agent/d' "$GO_SCRIPT" 2>/dev/null || true ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got, err := os.ReadFile(goScript) if err != nil { t.Fatal(err) } if string(got) != tc.after { t.Errorf("go script mismatch:\n--- got ---\n%s\n--- want ---\n%s", got, tc.after) } }) } } // TestAPIDeregistrationCurl verifies the curl command sends the correct // JSON payload and headers to the canonical uninstall endpoint. func TestAPIDeregistrationCurl(t *testing.T) { var ( mu sync.Mutex gotMethod string gotPath string gotBody map[string]string gotHeaders http.Header ) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mu.Lock() defer mu.Unlock() gotMethod = r.Method gotPath = r.URL.Path gotHeaders = r.Header.Clone() body, _ := io.ReadAll(r.Body) _ = json.Unmarshal(body, &gotBody) w.WriteHeader(200) w.Write([]byte(`{"success":true}`)) })) defer srv.Close() agentID := "test-uuid-1234" token := "deadbeef0123456789" script := ` PULSE_URL="` + srv.URL + `" PULSE_TOKEN="` + token + `" AGENT_ID="` + agentID + `" CURL_ARGS=(-fsSL --connect-timeout 5 -X POST -H "Content-Type: application/json") if [[ -n "$PULSE_TOKEN" ]]; then CURL_ARGS+=(-H "X-API-Token: ${PULSE_TOKEN}"); fi curl "${CURL_ARGS[@]}" -d "{\"agentId\": \"${AGENT_ID}\"}" "${PULSE_URL}/api/agents/agent/uninstall" >/dev/null 2>&1 || true ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } mu.Lock() defer mu.Unlock() if gotMethod != "POST" { t.Errorf("method = %q, want POST", gotMethod) } if gotPath != "/api/agents/agent/uninstall" { t.Errorf("path = %q, want /api/agents/agent/uninstall", gotPath) } if gotBody["agentId"] != agentID { t.Errorf("body agentId = %q, want %q", gotBody["agentId"], agentID) } if got := gotHeaders.Get("X-API-Token"); got != token { t.Errorf("X-API-Token = %q, want %q", got, token) } if got := gotHeaders.Get("Content-Type"); got != "application/json" { t.Errorf("Content-Type = %q, want application/json", got) } } func TestAPIDeregistrationCurlWithoutToken(t *testing.T) { var ( mu sync.Mutex gotMethod string gotPath string gotBody map[string]string gotHeaders http.Header ) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mu.Lock() defer mu.Unlock() gotMethod = r.Method gotPath = r.URL.Path gotHeaders = r.Header.Clone() body, _ := io.ReadAll(r.Body) _ = json.Unmarshal(body, &gotBody) w.WriteHeader(200) w.Write([]byte(`{"success":true}`)) })) defer srv.Close() agentID := "test-uuid-5678" script := ` PULSE_URL="` + srv.URL + `" PULSE_TOKEN="" AGENT_ID="` + agentID + `" CURL_ARGS=(-fsSL --connect-timeout 5 -X POST -H "Content-Type: application/json") if [[ -n "$PULSE_TOKEN" ]]; then CURL_ARGS+=(-H "X-API-Token: ${PULSE_TOKEN}"); fi curl "${CURL_ARGS[@]}" -d "{\"agentId\": \"${AGENT_ID}\"}" "${PULSE_URL}/api/agents/agent/uninstall" >/dev/null 2>&1 || true ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } mu.Lock() defer mu.Unlock() if gotMethod != "POST" { t.Errorf("method = %q, want POST", gotMethod) } if gotPath != "/api/agents/agent/uninstall" { t.Errorf("path = %q, want /api/agents/agent/uninstall", gotPath) } if gotBody["agentId"] != agentID { t.Errorf("body agentId = %q, want %q", gotBody["agentId"], agentID) } if got := gotHeaders.Get("X-API-Token"); got != "" { t.Errorf("X-API-Token = %q, want empty", got) } if got := gotHeaders.Get("Content-Type"); got != "application/json" { t.Errorf("Content-Type = %q, want application/json", got) } } func extractInstallShellFunction(t *testing.T, name string) string { t.Helper() content, err := os.ReadFile(filepath.Join("..", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } pattern := regexp.MustCompile(`(?ms)^` + regexp.QuoteMeta(name) + `\(\) \{\n.*?^\}`) match := pattern.Find(content) if match == nil { t.Fatalf("could not find %s in install.sh", name) } return string(match) } func extractLifecycleTrustShellFunctions(t *testing.T) string { t.Helper() names := []string{ "portable_path_uid", "portable_path_mode", "trusted_lifecycle_regular_file", "trusted_connection_state_file", } var functions strings.Builder for _, name := range names { functions.WriteString(extractInstallShellFunction(t, name)) functions.WriteByte('\n') } return functions.String() } func extractLifecyclePersistenceShellFunctions(t *testing.T) string { t.Helper() names := []string{ "installer_file_sha256", "sync_lifecycle_path", "prepare_installer_lifecycle_dir", "install_lifecycle_file_atomically", "has_pinned_installer_signature_key", } var functions strings.Builder functions.WriteString(extractLifecycleTrustShellFunctions(t)) for _, name := range names { functions.WriteString(extractInstallShellFunction(t, name)) functions.WriteByte('\n') } return functions.String() } func extractTokenRecoveryShellFunctions(t *testing.T) string { t.Helper() return extractLifecycleTrustShellFunctions(t) + extractInstallShellFunction(t, "trusted_private_lifecycle_regular_file") + "\n" + extractInstallShellFunction(t, "collector_lifecycle_binary") + "\n" + extractInstallShellFunction(t, "read_collector_token_file_safely") + "\n" + extractInstallShellFunction(t, "recover_token_from_default_agent_token_file") } func extractCollectorLifecycleShellFunctions(t *testing.T, includeVerify bool) string { t.Helper() functions := extractLifecycleTrustShellFunctions(t) + extractInstallShellFunction(t, "collector_lifecycle_binary") + "\n" + extractInstallShellFunction(t, "prepare_collector_lifecycle_token_file") + "\n" + extractInstallShellFunction(t, "run_collector_lifecycle_command") if includeVerify { functions += "\n" + extractInstallShellFunction(t, "verify_agent_server_registration") } return functions } func buildPulseAgentLifecycleBinary(t *testing.T) string { t.Helper() binary := filepath.Join(t.TempDir(), "pulse-agent") build := exec.Command("go", "build", "-o", binary, "./cmd/pulse-agent") build.Dir = filepath.Dir(repoFile("go.mod")) if output, err := build.CombinedOutput(); err != nil { t.Fatalf("build pulse-agent lifecycle binary: %v\n%s", err, output) } return binary } func extractRootInstallShellFunction(t *testing.T, name string) string { t.Helper() content, err := os.ReadFile(filepath.Join("..", "..", "install.sh")) if err != nil { t.Fatalf("read root install.sh: %v", err) } pattern := regexp.MustCompile(`(?ms)^` + regexp.QuoteMeta(name) + `\(\) \{\n.*?^\}`) match := pattern.Find(content) if match == nil { t.Fatalf("could not find %s in root install.sh", name) } return string(match) } func extractSelectedUpdateChannelShellFunctions(t *testing.T) string { t.Helper() return extractRootInstallShellFunction(t, "read_configured_update_channel") + "\n" + extractRootInstallShellFunction(t, "selected_update_channel") } func extractSetupAutoUpdatesShellFunctions(t *testing.T) string { t.Helper() return extractSelectedUpdateChannelShellFunctions(t) + "\n" + extractRootInstallShellFunction(t, "repo_web_url") + "\n" + extractRootInstallShellFunction(t, "configure_auto_update_script_repo") + "\n" + extractInstallAutoUpdateAssetsShellFunctions(t) + "\n" + extractRootInstallShellFunction(t, "setup_auto_updates") } // install_auto_update_assets delegates its writability probe, its staged-helper // sanity check and the sandbox-escape migration to sibling functions, so every // harness that executes it has to pull those in alongside it. These isolated // asset-rendering tests admit an unmasked inventory; the whole-installer Python // controls below exercise the actual effective-mask guard and all its callers. func extractInstallAutoUpdateAssetsShellFunctions(t *testing.T) string { t.Helper() return "auto_update_units_refreshable() { return 0; }\n" + extractRootInstallShellFunction(t, "auto_update_dir_writable") + "\n" + extractRootInstallShellFunction(t, "auto_update_helper_is_sane") + "\n" + extractRootInstallShellFunction(t, "migrate_auto_update_assets_outside_sandbox") + "\n" + extractRootInstallShellFunction(t, "install_auto_update_assets") } func prepareAutoUpdatePaths(t *testing.T, tmpDir string) (string, string, string) { t.Helper() autoUpdateDest := filepath.Join(tmpDir, "bin", "pulse-auto-update.sh") servicePath := filepath.Join(tmpDir, "systemd", "pulse-update.service") timerPath := filepath.Join(tmpDir, "systemd", "pulse-update.timer") if err := os.MkdirAll(filepath.Dir(autoUpdateDest), 0755); err != nil { t.Fatalf("mkdir auto-update dest dir: %v", err) } if err := os.MkdirAll(filepath.Dir(servicePath), 0755); err != nil { t.Fatalf("mkdir systemd dir: %v", err) } return autoUpdateDest, servicePath, timerPath } func extractAutoUpdateFunction(t *testing.T, name string) string { t.Helper() content, err := os.ReadFile(filepath.Join("..", "pulse-auto-update.sh")) if err != nil { t.Fatalf("read pulse-auto-update.sh: %v", err) } pattern := regexp.MustCompile(`(?ms)^` + regexp.QuoteMeta(name) + `\(\) \{\n.*?^\}`) match := pattern.Find(content) if match == nil { t.Fatalf("could not find %s in pulse-auto-update.sh", name) } return string(match) } func extractInstallShellSection(t *testing.T, startMarker string, endMarker string) string { t.Helper() content, err := os.ReadFile(filepath.Join("..", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } text := string(content) start := strings.Index(text, startMarker) if start == -1 { t.Fatalf("could not find start marker %q in install.sh", startMarker) } end := strings.Index(text[start:], endMarker) if end == -1 { t.Fatalf("could not find end marker %q in install.sh", endMarker) } return text[start : start+end] } func TestPlainHTTPInstallAutoEnablesInsecure(t *testing.T) { script := ` log_info() { :; } ` + extractInstallShellFunction(t, "pulse_url_uses_plain_http") + ` ` + extractInstallShellFunction(t, "auto_enable_insecure_for_plain_http_url") + ` PULSE_URL="http://192.168.0.98:7655" INSECURE="false" auto_enable_insecure_for_plain_http_url echo "$INSECURE" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if got := strings.TrimSpace(string(out)); got != "true" { t.Fatalf("INSECURE = %q, want true", got) } } func TestHTTPSInstallKeepsInsecureDisabledByDefault(t *testing.T) { script := ` log_info() { :; } ` + extractInstallShellFunction(t, "pulse_url_uses_plain_http") + ` ` + extractInstallShellFunction(t, "auto_enable_insecure_for_plain_http_url") + ` PULSE_URL="https://pulse.example.com" INSECURE="false" auto_enable_insecure_for_plain_http_url echo "$INSECURE" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if got := strings.TrimSpace(string(out)); got != "false" { t.Fatalf("INSECURE = %q, want false", got) } } func TestBuildExecArgsArrayPersistsInsecureForPlainHTTPInstall(t *testing.T) { script := ` log_info() { :; } ` + extractInstallShellFunction(t, "pulse_url_uses_plain_http") + ` ` + extractInstallShellFunction(t, "auto_enable_insecure_for_plain_http_url") + ` ` + extractInstallShellFunction(t, "build_exec_arg_items") + ` ` + extractInstallShellFunction(t, "build_exec_args_array") + ` PULSE_URL="http://pulse.local:7655" PULSE_TOKEN="deadbeef" INTERVAL="30s" ENABLE_HOST="true" ENABLE_DOCKER="" DOCKER_EXPLICIT="false" ENABLE_KUBERNETES="" KUBECONFIG_PATH="" ENABLE_PROXMOX="" PROXMOX_TYPE="" INSECURE="false" RUNTIME_TOKEN_FILE="/var/lib/pulse-agent/token" ENABLE_COMMANDS="" ENROLL="" KUBE_INCLUDE_ALL_PODS="" KUBE_INCLUDE_ALL_DEPLOYMENTS="" AGENT_ID="" HOSTNAME_OVERRIDE="" STATE_DIR="" DISK_EXCLUDES=() auto_enable_insecure_for_plain_http_url build_exec_args_array printf '%s\n' "${EXEC_ARGS_ARRAY[*]}" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := strings.TrimSpace(string(out)) if !strings.Contains(got, "--insecure") { t.Fatalf("EXEC_ARGS_ARRAY missing --insecure: %s", got) } if !strings.Contains(got, "--token-file /var/lib/pulse-agent/token") { t.Fatalf("EXEC_ARGS_ARRAY missing runtime token file: %s", got) } if strings.Contains(got, "--token deadbeef") { t.Fatalf("EXEC_ARGS_ARRAY preserved raw token: %s", got) } } func TestBuildExecArgsWithoutTokenOmitsPersistedToken(t *testing.T) { script := ` ` + extractInstallShellFunction(t, "build_exec_arg_items") + ` ` + extractInstallShellFunction(t, "join_exec_arg_items") + ` ` + extractInstallShellFunction(t, "build_exec_args_without_token") + ` PULSE_URL="https://pulse.example.com" PULSE_TOKEN="deadbeef" INTERVAL="30s" ENABLE_HOST="true" ENABLE_DOCKER="" DOCKER_EXPLICIT="false" ENABLE_KUBERNETES="" KUBECONFIG_PATH="" ENABLE_PROXMOX="true" PROXMOX_TYPE="pbs" INSECURE="false" ENABLE_COMMANDS="" ENROLL="" KUBE_INCLUDE_ALL_PODS="" KUBE_INCLUDE_ALL_DEPLOYMENTS="" AGENT_ID="" HOSTNAME_OVERRIDE="" STATE_DIR="/var/lib/pulse-agent" DISK_EXCLUDES=("/boot pool") build_exec_args_without_token printf '%s\n' "$EXEC_ARGS" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := strings.TrimSpace(string(out)) if strings.Contains(got, "--token") { t.Fatalf("EXEC_ARGS unexpectedly preserved token: %s", got) } if !strings.Contains(got, "--proxmox-type pbs") { t.Fatalf("EXEC_ARGS missing proxmox type: %s", got) } if !strings.Contains(got, `--disk-exclude /boot\ pool`) { t.Fatalf("EXEC_ARGS missing quoted disk exclude: %s", got) } } func TestStateDirFlagIsAcceptedByInstallerParser(t *testing.T) { script := ` fail() { echo "FAIL:$1"; exit 99; } PULSE_URL="" PULSE_TOKEN="" INTERVAL="30s" ENABLE_HOST="true" ENABLE_DOCKER="" DOCKER_EXPLICIT="false" ENABLE_KUBERNETES="" KUBERNETES_EXPLICIT="false" ENABLE_PROXMOX="" PROXMOX_EXPLICIT="false" PROXMOX_TYPE="" UNINSTALL="false" INSECURE="false" AGENT_ID="" HOSTNAME_OVERRIDE="" ENABLE_COMMANDS="false" ENROLL="false" KUBECONFIG_PATH="" KUBE_INCLUDE_ALL_PODS="false" KUBE_INCLUDE_ALL_DEPLOYMENTS="false" DISK_EXCLUDES=() STATE_DIR="/var/lib/pulse-agent" STATE_DIR_SOURCE="default" STATE_DIR_REMOVAL_AUTHORITY="$STATE_DIR" CURL_CA_BUNDLE="" NON_INTERACTIVE="false" TOKEN_FILE_PATH="" OUTPUT_FORMAT="text" PREFLIGHT_ONLY="false" verify_saved_installer_self_integrity() { return 0; } discover_state_dir_from_saved_installer() { return 1; } set -- --state-dir /tmp/pulse-agent-state --non-interactive --url https://pulse.example.com --token deadbeef ` + extractInstallShellSection(t, "# --- Parse Arguments ---", "# Read token from file if --token-file was provided") + ` printf 'STATE_DIR=%s\nNON_INTERACTIVE=%s\nPULSE_URL=%s\n' "$STATE_DIR" "$NON_INTERACTIVE" "$PULSE_URL" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) if !strings.Contains(got, "STATE_DIR=/tmp/pulse-agent-state") { t.Fatalf("STATE_DIR not parsed correctly:\n%s", got) } if !strings.Contains(got, "NON_INTERACTIVE=true") { t.Fatalf("NON_INTERACTIVE not parsed correctly:\n%s", got) } if !strings.Contains(got, "PULSE_URL=https://pulse.example.com") { t.Fatalf("PULSE_URL not parsed correctly:\n%s", got) } } func TestInstallSHCustomStateDirOwnsTokenAndEnrollmentContinuity(t *testing.T) { stateDir := filepath.Join(t.TempDir(), "custom-state") if err := os.MkdirAll(stateDir, 0755); err != nil { t.Fatal(err) } tokenPath := filepath.Join(stateDir, "token") runtimePath := filepath.Join(stateDir, "runtime.token") if err := os.WriteFile(tokenPath, []byte("aaa111"), 0600); err != nil { t.Fatal(err) } if err := os.WriteFile(runtimePath, []byte("runtime-one"), 0600); err != nil { t.Fatal(err) } ensureToken := extractInstallShellFunction(t, "ensure_runtime_token_file") run := func(token string) string { t.Helper() script := ` set -euo pipefail PULSE_TOKEN="` + token + `" STATE_DIR="` + stateDir + `" ENROLL="true" RUNTIME_TOKEN_FILE="" RUNTIME_TOKEN_CHANGED="false" log_info() { :; } ` + ensureToken + ` ensure_runtime_token_file "$STATE_DIR" printf 'changed=%s runtime=%s token_file=%s\n' \ "$RUNTIME_TOKEN_CHANGED" "$([[ -f "$STATE_DIR/runtime.token" ]] && echo present || echo missing)" "$RUNTIME_TOKEN_FILE" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } return string(out) } if got := run("aaa111"); !strings.Contains(got, "changed=false runtime=present") { t.Fatalf("unchanged bootstrap token did not preserve enrollment runtime state:\n%s", got) } if got := run("bbb222"); !strings.Contains(got, "changed=true runtime=missing") { t.Fatalf("changed bootstrap token did not force re-enrollment:\n%s", got) } for path, want := range map[string]os.FileMode{ stateDir: 0700, tokenPath: 0600, } { info, err := os.Stat(path) if err != nil { t.Fatal(err) } if got := info.Mode().Perm(); got != want { t.Fatalf("%s mode = %o, want %o", path, got, want) } } } func TestInstallSHExplicitCustomStateNeverFallsBackToDefaultInstance(t *testing.T) { root := t.TempDir() customState := filepath.Join(root, "custom") defaultState := filepath.Join(root, "default") for _, dir := range []string{customState, defaultState} { if err := os.MkdirAll(dir, 0700); err != nil { t.Fatal(err) } } customConn := filepath.Join(customState, "connection.env") if err := os.WriteFile(customConn, []byte("PULSE_URL='https://custom.example'\n"), 0600); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(defaultState, "connection.env"), []byte("PULSE_URL='https://default.example'\n"), 0600); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(defaultState, "agent-id"), []byte("default-agent"), 0600); err != nil { t.Fatal(err) } script := ` set -euo pipefail STATE_DIR="` + customState + `" STATE_DIR_SOURCE="explicit" DEFAULT_STATE_DIR="` + defaultState + `" TRUENAS_STATE_DIR="` + filepath.Join(root, "truenas") + `" INSTALLER_LIFECYCLE_DIR="` + filepath.Join(root, "lifecycle") + `" LEAST_PRIVILEGE_USER="pulse-agent" ` + extractLifecycleTrustShellFunctions(t) + ` ` + extractInstallShellFunction(t, "find_connection_state_file") + ` ` + extractInstallShellFunction(t, "read_agent_id_file_safely") + ` ` + extractInstallShellFunction(t, "recover_agent_id_from_state_file") + ` printf 'connection=%s\n' "$(find_connection_state_file)" rm -f "$STATE_DIR/connection.env" printf 'fallback_connection=%s\n' "$(find_connection_state_file || true)" printf 'fallback_agent=%s\n' "$(recover_agent_id_from_state_file || true)" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) if !strings.Contains(got, "connection="+customConn) { t.Fatalf("custom connection state was not preferred:\n%s", got) } if !strings.Contains(got, "fallback_connection=\n") || !strings.Contains(got, "fallback_agent=\n") { t.Fatalf("explicit custom state borrowed default instance state:\n%s", got) } } func TestInstallSHSavedInstallerDiscoversItsCustomStateDir(t *testing.T) { stateDir := filepath.Join(t.TempDir(), "saved custom state") if err := os.MkdirAll(stateDir, 0700); err != nil { t.Fatal(err) } installerPath := filepath.Join(stateDir, "install.sh") if err := os.WriteFile(installerPath, []byte("#!/usr/bin/env bash\n"), 0700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(stateDir, "connection.env"), []byte("PULSE_URL='https://pulse.example'\n"), 0600); err != nil { t.Fatal(err) } script := ` set -euo pipefail STATE_DIR="/var/lib/pulse-agent" STATE_DIR_SOURCE="default" STATE_DIR_REMOVAL_AUTHORITY="/var/lib/pulse-agent" INSTALLER_LIFECYCLE_DIR="` + filepath.Join(t.TempDir(), "lifecycle") + `" ` + extractLifecycleTrustShellFunctions(t) + ` ` + extractInstallShellFunction(t, "read_connection_state_value") + ` ` + extractInstallShellFunction(t, "discover_state_dir_from_saved_installer") + ` discover_state_dir_from_saved_installer "` + installerPath + `" printf 'state=%s source=%s\n' "$STATE_DIR" "$STATE_DIR_SOURCE" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) resolvedStateDir, err := filepath.EvalSymlinks(stateDir) if err != nil { t.Fatal(err) } if !strings.Contains(got, "state="+resolvedStateDir) || !strings.Contains(got, "source=recovered") { t.Fatalf("saved installer did not discover adjacent custom state:\n%s", got) } } func TestInstallSHDiscoversCustomStateDirFromGeneratedSystemdUnit(t *testing.T) { for _, commandsEnabled := range []bool{false, true} { t.Run(map[bool]string{false: "commands_disabled", true: "commands_enabled"}[commandsEnabled], func(t *testing.T) { root := t.TempDir() customState := filepath.Join(root, "custom state") defaultState := filepath.Join(root, "default") if err := os.MkdirAll(customState, 0700); err != nil { t.Fatal(err) } if err := os.MkdirAll(defaultState, 0700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(customState, "token"), []byte("custom123"), 0600); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(defaultState, "token"), []byte("default999"), 0600); err != nil { t.Fatal(err) } commandArg := "" if commandsEnabled { commandArg = " --enable-commands" } unitPath := filepath.Join(root, "pulse-agent.service") unit := `[Service] ExecStart=/usr/local/bin/pulse-agent --url https://custom.example --token-file ` + strings.ReplaceAll(filepath.Join(customState, "token"), " ", `\ `) + ` --state-dir ` + strings.ReplaceAll(customState, " ", `\ `) + commandArg + "\n" if err := os.WriteFile(unitPath, []byte(unit), 0644); err != nil { t.Fatal(err) } script := ` set -euo pipefail AGENT_NAME="pulse-agent" PULSE_URL="" PULSE_TOKEN="" INTERVAL="30s" INTERVAL_EXPLICIT="false" ENABLE_HOST="true" HOST_EXPLICIT="false" ENABLE_DOCKER="" DOCKER_EXPLICIT="false" ENABLE_KUBERNETES="" KUBERNETES_EXPLICIT="false" KUBECONFIG_PATH="" ENABLE_PROXMOX="" PROXMOX_EXPLICIT="false" PROXMOX_TYPE="" INSECURE="false" ENABLE_COMMANDS="false" ENROLL="false" HEALTH_ADDR="" HEALTH_ADDR_SET="false" AGENT_ID="" HOSTNAME_OVERRIDE="" STATE_DIR="` + defaultState + `" STATE_DIR_SOURCE="default" DEFAULT_STATE_DIR="` + defaultState + `" TRUENAS_STATE_DIR="` + filepath.Join(root, "truenas") + `" CURL_CA_BUNDLE="" SERVER_FINGERPRINT="" OBSERVERS_FILE="" KUBE_INCLUDE_ALL_PODS="false" KUBE_INCLUDE_ALL_DEPLOYMENTS="false" DISK_EXCLUDES=() log_warn() { :; } systemctl() { printf '%s\n' "` + unitPath + `"; } ` + extractInstallShellFunction(t, "strip_recovered_arg_quotes") + ` ` + extractInstallShellFunction(t, "normalize_recovered_agent_arg_key") + ` ` + extractInstallShellFunction(t, "apply_recovered_agent_arg_value") + ` ` + extractInstallShellFunction(t, "recovered_connection_state_ready") + ` ` + extractTokenRecoveryShellFunctions(t) + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_arg_stream") + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_env_stream") + ` ` + extractInstallShellFunction(t, "split_recovered_shell_words") + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_systemd_unit") + ` ` + extractInstallShellFunction(t, "remove_authorized_runtime_dir") + ` ` + extractInstallShellFunction(t, "remove_agent_state_dir") + ` recover_connection_state_from_systemd_unit printf 'state=%s source=%s url=%s token=%s commands=%s\n' \ "$STATE_DIR" "$STATE_DIR_SOURCE" "$PULSE_URL" "$PULSE_TOKEN" "$ENABLE_COMMANDS" remove_agent_state_dir "$STATE_DIR" || true ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) for _, want := range []string{ "state=" + customState, "source=recovered", "url=https://custom.example", "token=custom123", "commands=" + map[bool]string{false: "false", true: "true"}[commandsEnabled], } { if !strings.Contains(got, want) { t.Fatalf("systemd discovery missing %q:\n%s", want, got) } } if strings.Contains(got, "default999") { t.Fatalf("systemd discovery borrowed default token:\n%s", got) } if _, err := os.Stat(customState); err != nil { t.Fatalf("process-derived state path should be retained without protected removal authority: %v", err) } if _, err := os.Stat(defaultState); err != nil { t.Fatalf("uninstall removed the default instance instead of discovered custom state: %v", err) } }) } } func TestInstallSHDiscoversCustomStateDirFromGeneratedLaunchdPlist(t *testing.T) { root := t.TempDir() customState := filepath.Join(root, "custom & state") if err := os.MkdirAll(customState, 0700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(customState, "token"), []byte("launch123"), 0600); err != nil { t.Fatal(err) } plistPath := filepath.Join(root, "com.pulse.agent.plist") escapedState := strings.ReplaceAll(customState, "&", "&") plist := ` ProgramArguments /usr/local/bin/pulse-agent --url https://launch.example --token-file ` + escapedState + `/token --state-dir ` + escapedState + ` --enable-commands ` if err := os.WriteFile(plistPath, []byte(plist), 0644); err != nil { t.Fatal(err) } script := ` set -euo pipefail PULSE_URL="" PULSE_TOKEN="" INTERVAL="30s" INTERVAL_EXPLICIT="false" ENABLE_HOST="true" HOST_EXPLICIT="false" ENABLE_DOCKER="" DOCKER_EXPLICIT="false" ENABLE_KUBERNETES="" KUBERNETES_EXPLICIT="false" KUBECONFIG_PATH="" ENABLE_PROXMOX="" PROXMOX_EXPLICIT="false" PROXMOX_TYPE="" INSECURE="false" ENABLE_COMMANDS="false" ENROLL="false" HEALTH_ADDR="" HEALTH_ADDR_SET="false" AGENT_ID="" HOSTNAME_OVERRIDE="" STATE_DIR="/var/lib/pulse-agent" STATE_DIR_SOURCE="default" DEFAULT_STATE_DIR="/var/lib/pulse-agent" TRUENAS_STATE_DIR="/data/pulse-agent" CURL_CA_BUNDLE="" SERVER_FINGERPRINT="" OBSERVERS_FILE="" KUBE_INCLUDE_ALL_PODS="false" KUBE_INCLUDE_ALL_DEPLOYMENTS="false" DISK_EXCLUDES=() ` + extractInstallShellFunction(t, "strip_recovered_arg_quotes") + ` ` + extractInstallShellFunction(t, "normalize_recovered_agent_arg_key") + ` ` + extractInstallShellFunction(t, "apply_recovered_agent_arg_value") + ` ` + extractInstallShellFunction(t, "recovered_connection_state_ready") + ` ` + extractTokenRecoveryShellFunctions(t) + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_arg_stream") + ` ` + extractInstallShellFunction(t, "launchd_agent_arg_stream") + ` ` + extractInstallShellFunction(t, "recover_connection_state_from_launchd_plist") + ` recover_connection_state_from_launchd_plist "` + plistPath + `" printf 'state=%s url=%s token=%s commands=%s\n' "$STATE_DIR" "$PULSE_URL" "$PULSE_TOKEN" "$ENABLE_COMMANDS" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) for _, want := range []string{ "state=" + customState, "url=https://launch.example", "token=launch123", "commands=true", } { if !strings.Contains(got, want) { t.Fatalf("launchd discovery missing %q:\n%s", want, got) } } } func TestInstallSHConnectionEnvPersistsCanonicalStateDirWithoutTokenValue(t *testing.T) { stateDir := filepath.Join(t.TempDir(), "state") script := ` set -euo pipefail STATE_DIR="` + stateDir + `" PULSE_URL="https://pulse.example.com" PULSE_TOKEN="deadbeef" RUNTIME_TOKEN_FILE="$STATE_DIR/token" AGENT_ID="agent-custom" HOSTNAME_OVERRIDE="host-custom" REPORT_IP="192.168.1.9" INSECURE="false" SERVER_FINGERPRINT="" CURL_CA_BUNDLE="" SAVED_INSTALL_SCRIPT="" LEAST_PRIVILEGE="false" LEAST_PRIVILEGE_USER="pulse-agent" INSTALLER_LIFECYCLE_DIR="` + filepath.Join(t.TempDir(), "lifecycle") + `" EXIT_GENERAL=1 TMP_FILES=() fail() { printf 'FAIL:%s\n' "$1" >&2; return 99; } log_warn() { :; } ` + extractLifecyclePersistenceShellFunctions(t) + ` ` + extractInstallShellFunction(t, "write_connection_state_value") + ` ` + extractInstallShellFunction(t, "save_connection_info") + ` curl() { return 1; } save_connection_info "$STATE_DIR" cat "$STATE_DIR/connection.env" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) if !strings.Contains(got, "PULSE_STATE_DIR='"+stateDir+"'") || !strings.Contains(got, "PULSE_TOKEN_FILE='"+filepath.Join(stateDir, "token")+"'") { t.Fatalf("connection.env did not persist canonical state paths:\n%s", got) } if !strings.Contains(got, "PULSE_REPORT_IP='192.168.1.9'") { t.Fatalf("connection.env did not persist the report IP:\n%s", got) } if strings.Contains(got, "PULSE_TOKEN='") || strings.Contains(got, "deadbeef") { t.Fatalf("connection.env leaked the token value:\n%s", got) } info, err := os.Stat(filepath.Join(stateDir, "connection.env")) if err != nil { t.Fatal(err) } if gotMode := info.Mode().Perm(); gotMode != 0600 { t.Fatalf("connection.env mode = %o, want 600", gotMode) } } func TestInstallSHStateWritesReplaceSymlinksAtomically(t *testing.T) { stateDir := filepath.Join(t.TempDir(), "state") if err := os.MkdirAll(stateDir, 0700); err != nil { t.Fatal(err) } victimToken := filepath.Join(t.TempDir(), "victim-token") victimConnection := filepath.Join(t.TempDir(), "victim-connection") if err := os.WriteFile(victimToken, []byte("victim-token-content"), 0600); err != nil { t.Fatal(err) } if err := os.WriteFile(victimConnection, []byte("victim-connection-content"), 0600); err != nil { t.Fatal(err) } if err := os.Symlink(victimToken, filepath.Join(stateDir, "token")); err != nil { t.Fatal(err) } if err := os.Symlink(victimConnection, filepath.Join(stateDir, "connection.env")); err != nil { t.Fatal(err) } script := ` set -euo pipefail STATE_DIR="` + stateDir + `" PULSE_URL="https://pulse.example.com" IFS= read -r PULSE_TOKEN RUNTIME_TOKEN_FILE="" RUNTIME_TOKEN_CHANGED="false" ENROLL="false" AGENT_ID="agent-one" HOSTNAME_OVERRIDE="host-one" REPORT_IP="" INSECURE="false" SERVER_FINGERPRINT="" CURL_CA_BUNDLE="" SAVED_INSTALL_SCRIPT="" LEAST_PRIVILEGE="false" LEAST_PRIVILEGE_USER="pulse-agent" INSTALLER_LIFECYCLE_DIR="` + filepath.Join(t.TempDir(), "lifecycle") + `" EXIT_GENERAL=1 NON_INTERACTIVE="true" TMP_FILES=() log_info() { :; } log_warn() { :; } fail() { printf 'FAIL:%s\n' "$1" >&2; return 99; } curl() { return 1; } ` + extractLifecyclePersistenceShellFunctions(t) + ` ` + extractInstallShellFunction(t, "write_connection_state_value") + ` ` + extractInstallShellFunction(t, "ensure_runtime_token_file") + ` ` + extractInstallShellFunction(t, "save_connection_info") + ` ensure_runtime_token_file "$STATE_DIR" save_connection_info "$STATE_DIR" ` cmd := exec.Command("bash", "-c", script) cmd.Stdin = strings.NewReader("new-token-content\n") out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } for path, want := range map[string]string{ victimToken: "victim-token-content", victimConnection: "victim-connection-content", } { got, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if string(got) != want { t.Fatalf("state write followed symlink and modified %s: %q", path, got) } } for _, path := range []string{filepath.Join(stateDir, "token"), filepath.Join(stateDir, "connection.env")} { info, err := os.Lstat(path) if err != nil { t.Fatal(err) } if info.Mode()&os.ModeSymlink != 0 { t.Fatalf("%s remained a symlink after atomic replacement", path) } } } func TestInstallSHLeastPrivilegeLifecycleStateIsRootBoundarySeparated(t *testing.T) { root := t.TempDir() stateDir := filepath.Join(root, "collector-state") lifecycleDir := filepath.Join(root, "root-lifecycle") sourceInstaller := filepath.Join(root, "source-install.sh") if err := os.MkdirAll(stateDir, 0700); err != nil { t.Fatal(err) } if err := os.WriteFile(sourceInstaller, []byte("#!/usr/bin/env bash\necho lifecycle\n"), 0700); err != nil { t.Fatal(err) } for _, name := range []string{"connection.env", "install.sh", "install.sh.sha256"} { if err := os.WriteFile(filepath.Join(stateDir, name), []byte("collector-controlled\n"), 0600); err != nil { t.Fatal(err) } } script := ` set -euo pipefail STATE_DIR="` + stateDir + `" PULSE_URL="https://pulse.example.com" RUNTIME_TOKEN_FILE="$STATE_DIR/runtime.token" AGENT_ID="agent-safe" HOSTNAME_OVERRIDE="host-safe" REPORT_IP="" INSECURE="false" SERVER_FINGERPRINT="" CURL_CA_BUNDLE="" SAVED_INSTALL_SCRIPT="" LEAST_PRIVILEGE="true" LEAST_PRIVILEGE_USER="` + os.Getenv("USER") + `" INSTALLER_LIFECYCLE_DIR="` + lifecycleDir + `" EXIT_GENERAL=1 TMP_FILES=() fail() { printf 'FAIL:%s\n' "$1" >&2; return 99; } curl() { return 1; } ` + extractLifecyclePersistenceShellFunctions(t) + ` ` + extractInstallShellFunction(t, "write_connection_state_value") + ` ` + extractInstallShellFunction(t, "save_connection_info") + ` save_connection_info "$STATE_DIR" printf 'saved=%s\n' "$SAVED_INSTALL_SCRIPT" ` cmd := exec.Command("bash", "-c", script, sourceInstaller) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if !strings.Contains(string(out), "saved="+filepath.Join(lifecycleDir, "install.sh")) { t.Fatalf("saved installer did not move to lifecycle boundary:\n%s", out) } for _, name := range []string{"connection.env", "install.sh", "install.sh.sha256"} { if _, err := os.Lstat(filepath.Join(stateDir, name)); !os.IsNotExist(err) { t.Fatalf("collector state retained privileged lifecycle file %s: %v", name, err) } } for path, wantMode := range map[string]os.FileMode{ filepath.Join(lifecycleDir, "connection.env"): 0600, filepath.Join(lifecycleDir, "install.sh"): 0700, filepath.Join(lifecycleDir, "install.sh.sha256"): 0600, } { info, err := os.Stat(path) if err != nil { t.Fatal(err) } if info.Mode().Perm() != wantMode { t.Fatalf("%s mode = %o, want %o", path, info.Mode().Perm(), wantMode) } } connection, err := os.ReadFile(filepath.Join(lifecycleDir, "connection.env")) if err != nil { t.Fatal(err) } if !strings.Contains(string(connection), "PULSE_STATE_DIR='"+stateDir+"'") { t.Fatalf("protected lifecycle state omitted collector state path:\n%s", connection) } installer, err := os.ReadFile(filepath.Join(lifecycleDir, "install.sh")) if err != nil { t.Fatal(err) } wantHash := fmt.Sprintf("%x", sha256.Sum256(installer)) checksum, err := os.ReadFile(filepath.Join(lifecycleDir, "install.sh.sha256")) if err != nil { t.Fatal(err) } if !strings.HasPrefix(string(checksum), wantHash+" install.sh") { t.Fatalf("installer checksum = %q, want hash %s", checksum, wantHash) } } func TestInstallSHSavedInstallerTamperAndUntrustedStateFailClosed(t *testing.T) { root := t.TempDir() lifecycleDir := filepath.Join(root, "lifecycle") if err := os.MkdirAll(lifecycleDir, 0700); err != nil { t.Fatal(err) } installerPath := filepath.Join(lifecycleDir, "install.sh") if err := os.WriteFile(installerPath, []byte("#!/usr/bin/env bash\necho original\n"), 0700); err != nil { t.Fatal(err) } installer, err := os.ReadFile(installerPath) if err != nil { t.Fatal(err) } hash := fmt.Sprintf("%x", sha256.Sum256(installer)) if err := os.WriteFile(filepath.Join(lifecycleDir, "install.sh.sha256"), []byte(hash+" install.sh\n"), 0600); err != nil { t.Fatal(err) } verifyScript := ` set -euo pipefail INSTALLER_LIFECYCLE_DIR="` + lifecycleDir + `" ` + extractLifecycleTrustShellFunctions(t) + ` ` + extractInstallShellFunction(t, "installer_file_sha256") + ` ` + extractInstallShellFunction(t, "verify_saved_installer_self_integrity") + ` verify_saved_installer_self_integrity "` + installerPath + `" ` if out, err := exec.Command("bash", "-c", verifyScript).CombinedOutput(); err != nil { t.Fatalf("untampered installer failed verification: %v\n%s", err, out) } if err := os.WriteFile(installerPath, []byte("#!/usr/bin/env bash\necho replaced\n"), 0700); err != nil { t.Fatal(err) } if out, err := exec.Command("bash", "-c", verifyScript).CombinedOutput(); err == nil { t.Fatalf("tampered installer passed verification:\n%s", out) } attackerDir := filepath.Join(root, "collector-writable") sentinelDir := filepath.Join(root, "must-survive") if err := os.MkdirAll(attackerDir, 0777); err != nil { t.Fatal(err) } if err := os.Chmod(attackerDir, 0777); err != nil { t.Fatal(err) } if err := os.MkdirAll(sentinelDir, 0700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(sentinelDir, "sentinel"), []byte("keep"), 0600); err != nil { t.Fatal(err) } attackerInstaller := filepath.Join(attackerDir, "install.sh") if err := os.WriteFile(attackerInstaller, []byte("#!/usr/bin/env bash\n"), 0700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(attackerDir, "connection.env"), []byte("PULSE_STATE_DIR='"+sentinelDir+"'\n"), 0600); err != nil { t.Fatal(err) } recoveryScript := ` set -euo pipefail STATE_DIR="/var/lib/pulse-agent" STATE_DIR_SOURCE="default" STATE_DIR_REMOVAL_AUTHORITY="/var/lib/pulse-agent" INSTALLER_LIFECYCLE_DIR="` + lifecycleDir + `" log_warn() { :; } ` + extractLifecycleTrustShellFunctions(t) + ` ` + extractInstallShellFunction(t, "read_connection_state_value") + ` ` + extractInstallShellFunction(t, "discover_state_dir_from_saved_installer") + ` ` + extractInstallShellFunction(t, "remove_authorized_runtime_dir") + ` ` + extractInstallShellFunction(t, "remove_agent_state_dir") + ` discover_state_dir_from_saved_installer "` + attackerInstaller + `" || true STATE_DIR="` + sentinelDir + `" remove_agent_state_dir "$STATE_DIR" || true ` if out, err := exec.Command("bash", "-c", recoveryScript).CombinedOutput(); err != nil { t.Fatalf("fail-closed recovery harness: %v\n%s", err, out) } if _, err := os.Stat(filepath.Join(sentinelDir, "sentinel")); err != nil { t.Fatalf("untrusted lifecycle state authorized recursive deletion: %v", err) } } func TestInstallSHInstallerFileSHA256FallsBackToFreeBSDsha256(t *testing.T) { contents := []byte("pulse-freebsd-checksum-fixture") file := filepath.Join(t.TempDir(), "payload.bin") if err := os.WriteFile(file, contents, 0600); err != nil { t.Fatal(err) } want := fmt.Sprintf("%x", sha256.Sum256(contents)) // FreeBSD base provides sha256(1) but neither GNU sha256sum nor Perl's // shasum. Hide those two and confirm the helper still returns the digest. script := ` set -euo pipefail command() { if [[ "$1" == "-v" && ( "$2" == "sha256sum" || "$2" == "shasum" ) ]]; then return 1 fi builtin command "$@" } sha256() { printf '%s\n' "` + want + `" } ` + extractInstallShellFunction(t, "installer_file_sha256") + ` installer_file_sha256 "` + file + `" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if got := strings.TrimSpace(string(out)); got != want { t.Fatalf("FreeBSD sha256 fallback = %q, want %q", got, want) } } func TestInstallSHPrivilegedHelperStateRemovalRequiresExactLifecycleAuthority(t *testing.T) { for _, tc := range []struct { name string authority func(string) string wantRemove bool }{ {name: "exact fixed boundary", authority: func(path string) string { return path }, wantRemove: true}, {name: "mismatched boundary", authority: func(path string) string { return path + "-other" }, wantRemove: false}, } { t.Run(tc.name, func(t *testing.T) { helperState := filepath.Join(t.TempDir(), "pulse-agent-helper") if err := os.MkdirAll(helperState, 0o700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(helperState, "activation-state"), []byte("retain-or-remove"), 0o600); err != nil { t.Fatal(err) } script := ` set -u PRIVILEGED_HELPER_STATE_DIR="` + helperState + `" PRIVILEGED_HELPER_STATE_DIR_REMOVAL_AUTHORITY="` + tc.authority(helperState) + `" log_warn() { :; } ` + extractInstallShellFunction(t, "remove_authorized_runtime_dir") + ` ` + extractInstallShellFunction(t, "remove_privileged_helper_state_dir") + ` if remove_privileged_helper_state_dir; then printf 'removed\n' else printf 'retained\n' fi ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("helper-state cleanup harness: %v\n%s", err, out) } _, statErr := os.Stat(helperState) if tc.wantRemove { if !os.IsNotExist(statErr) || string(out) != "removed\n" { t.Fatalf("authorized helper state survived: stat=%v output=%q", statErr, out) } } else if statErr != nil || string(out) != "retained\n" { t.Fatalf("unauthorized helper state changed: stat=%v output=%q", statErr, out) } }) } } func TestInstallSHSafeProfileStateRemovalRequiresExactLifecycleAuthority(t *testing.T) { for _, tc := range []struct { name string authority func(string) string wantRemove bool }{ {name: "exact fixed boundary", authority: func(path string) string { return path }, wantRemove: true}, {name: "mismatched boundary", authority: func(path string) string { return path + "-other" }, wantRemove: false}, } { t.Run(tc.name, func(t *testing.T) { profileState := filepath.Join(t.TempDir(), "pulse-agent-profile") if err := os.MkdirAll(profileState, 0o700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(profileState, "current.env"), []byte("retained-or-removed"), 0o600); err != nil { t.Fatal(err) } script := ` set -u SAFE_PROFILE_STATE_DIR="` + profileState + `" SAFE_PROFILE_STATE_DIR_REMOVAL_AUTHORITY="` + tc.authority(profileState) + `" log_warn() { :; } ` + extractInstallShellFunction(t, "remove_authorized_runtime_dir") + ` ` + extractInstallShellFunction(t, "remove_safe_profile_state_dir") + ` if remove_safe_profile_state_dir; then printf 'removed\n' else printf 'retained\n' fi ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("safe-profile-state cleanup harness: %v\n%s", err, out) } _, statErr := os.Stat(profileState) if tc.wantRemove { if !os.IsNotExist(statErr) || string(out) != "removed\n" { t.Fatalf("authorized safe-profile state survived: stat=%v output=%q", statErr, out) } } else if statErr != nil || string(out) != "retained\n" { t.Fatalf("unauthorized safe-profile state changed: stat=%v output=%q", statErr, out) } }) } } func TestInstallSHFullUninstallRemovesRuntimeStateOnlyAfterServerConfirmation(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) confirmation := strings.LastIndex(script, `if ! uninstall_collector_registration; then`) helperCleanup := strings.LastIndex(script, `if ! remove_privileged_helper_state_dir; then`) profileCleanup := strings.LastIndex(script, `if ! remove_safe_profile_state_dir; then`) if confirmation < 0 || helperCleanup < 0 || profileCleanup < 0 { t.Fatalf("full uninstall lifecycle is missing server confirmation or fixed-state cleanup: confirmation=%d helper=%d profile=%d", confirmation, helperCleanup, profileCleanup) } if helperCleanup <= confirmation || profileCleanup <= confirmation { t.Fatal("fixed privileged runtime state cleanup can run before authenticated server removal") } } func TestInstallSHLegacyCollectorOwnedTokenReaderFailsClosed(t *testing.T) { root := t.TempDir() stateDir := filepath.Join(root, "collector-state") if err := os.MkdirAll(stateDir, 0700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(stateDir, "token"), []byte("legacy-monitoring-token\n"), 0600); err != nil { t.Fatal(err) } fakeBinary := filepath.Join(root, "collector-owned-pulse-agent") marker := filepath.Join(root, "executed") if err := os.WriteFile(fakeBinary, []byte("#!/usr/bin/env bash\ntouch '"+marker+"'\n"), 0755); err != nil { t.Fatal(err) } script := ` set -euo pipefail STATE_DIR="` + stateDir + `" STATE_DIR_SOURCE="explicit" DEFAULT_STATE_DIR="/var/lib/pulse-agent" TRUENAS_STATE_DIR="/data/pulse-agent" PULSE_TOKEN="" RUNTIME_TOKEN_FILE="" PRIVILEGED_HELPER_CREDENTIAL_DIR="` + filepath.Join(root, "protected") + `" LEAST_PRIVILEGE_USER="pulse-agent" collector_lifecycle_binary() { printf '%s\n' "` + fakeBinary + `"; } trusted_lifecycle_regular_file() { return 1; } trusted_private_lifecycle_regular_file() { return 1; } ` + extractInstallShellFunction(t, "read_collector_token_file_safely") + ` ` + extractInstallShellFunction(t, "recover_token_from_default_agent_token_file") + ` recover_token_from_default_agent_token_file || true printf 'token=%s\n' "$PULSE_TOKEN" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if strings.TrimSpace(string(out)) != "token=" { t.Fatalf("untrusted legacy reader recovered a token: %s", out) } if _, err := os.Stat(marker); !os.IsNotExist(err) { t.Fatalf("root installer executed collector-owned lifecycle binary: %v", err) } } func TestInstallSHCurlTokenTransportKeepsSecretOutOfArgv(t *testing.T) { recordDir := t.TempDir() argsPath := filepath.Join(recordDir, "args") configPath := filepath.Join(recordDir, "config") script := ` set -euo pipefail PULSE_TOKEN="feedface" curl() { printf '%s\n' "$@" > "` + argsPath + `" while [[ $# -gt 0 ]]; do if [[ "$1" == "--config" ]]; then cp "$2" "` + configPath + `" shift 2 continue fi shift done } ` + extractInstallShellFunction(t, "curl_with_pulse_token") + ` curl_with_pulse_token -sS https://pulse.example.com/api/agents/agent/lookup ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } args, err := os.ReadFile(argsPath) if err != nil { t.Fatal(err) } if strings.Contains(string(args), "feedface") || strings.Contains(string(args), "X-API-Token") { t.Fatalf("curl argv leaked token material:\n%s", args) } config, err := os.ReadFile(configPath) if err != nil { t.Fatal(err) } if !strings.Contains(string(config), "X-API-Token: feedface") { t.Fatalf("private curl config did not carry auth header:\n%s", config) } } func TestSetupUpdateCommandHonorsRCChannelAndCustomPaths(t *testing.T) { tmpDir := t.TempDir() updatePath := filepath.Join(tmpDir, "update") profilePath := filepath.Join(tmpDir, "profile") bashrcPath := filepath.Join(tmpDir, "bashrc") configDir := filepath.Join(tmpDir, "pulse-config") if err := os.MkdirAll(configDir, 0755); err != nil { t.Fatalf("mkdir config dir: %v", err) } if err := os.WriteFile(filepath.Join(configDir, "system.json"), []byte(`{"updateChannel":"rc"}`), 0644); err != nil { t.Fatalf("write system.json: %v", err) } if err := os.WriteFile(bashrcPath, []byte(""), 0644); err != nil { t.Fatalf("write bashrc: %v", err) } script := ` PULSE_UPDATE_HELPER_PATH="` + updatePath + `" PULSE_PROFILE_PATH="` + profilePath + `" PULSE_BASHRC_PATH="` + bashrcPath + `" GITHUB_REPO="example/pulse-fork" ` + extractRootInstallShellFunction(t, "setup_update_command") + ` setup_update_command ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } content, err := os.ReadFile(updatePath) if err != nil { t.Fatalf("read update helper: %v", err) } got := string(content) if !strings.Contains(got, `CONFIG_DIR=/etc/pulse`) { t.Fatalf("update helper missing config dir logic:\n%s", got) } if !strings.Contains(got, `helper_args=()`) || !strings.Contains(got, `helper_args=("$@")`) { t.Fatalf("update helper missing passthrough helper args:\n%s", got) } if !strings.Contains(got, `-h|--help|--uninstall|--version|--rc|--pre|--prerelease|--stable|--source|--from-source|--branch|--archive|--archive=*|--skip-upgrade-preflight)`) { t.Fatalf("update helper missing auto-selector guard for explicit flags:\n%s", got) } if !strings.Contains(got, `extra_args+=("${helper_args[@]}")`) { t.Fatalf("update helper missing forwarded helper args:\n%s", got) } if !strings.Contains(got, `extra_args+=(--rc)`) { t.Fatalf("update helper missing rc channel forwarding:\n%s", got) } if !strings.Contains(got, `extra_args+=(--stable)`) { t.Fatalf("update helper must bind the parsed stable channel before the installer rereads configuration:\n%s", got) } if !strings.Contains(got, `INSTALLER_URL="https://github.com/example/pulse-fork/releases/latest/download/install.sh"`) { t.Fatalf("update helper missing configured repo installer url:\n%s", got) } if !strings.Contains(got, `INSTALLER_SIG_URL="${INSTALLER_URL}.sshsig"`) { t.Fatalf("update helper missing installer signature url:\n%s", got) } if !strings.Contains(got, `verify_release_signature "$tmp_installer" "$tmp_signature" "downloaded Pulse installer"`) { t.Fatalf("update helper missing signed installer verification:\n%s", got) } if strings.Contains(got, `curl -fsSL "$INSTALLER_URL" |`) { t.Fatalf("update helper still pipes installer directly to bash:\n%s", got) } profileContent, err := os.ReadFile(profilePath) if err != nil { t.Fatalf("read profile: %v", err) } if !strings.Contains(string(profileContent), `/usr/local/bin`) { t.Fatalf("profile not updated with /usr/local/bin path:\n%s", profileContent) } } func TestSetupUpdateCommandUsesConfiguredInstallerRepo(t *testing.T) { tmpDir := t.TempDir() updatePath := filepath.Join(tmpDir, "update") profilePath := filepath.Join(tmpDir, "profile") bashrcPath := filepath.Join(tmpDir, "bashrc") if err := os.WriteFile(bashrcPath, []byte(""), 0644); err != nil { t.Fatalf("write bashrc: %v", err) } script := ` PULSE_UPDATE_HELPER_PATH="` + updatePath + `" PULSE_PROFILE_PATH="` + profilePath + `" PULSE_BASHRC_PATH="` + bashrcPath + `" GITHUB_REPO="example/pulse-fork" ` + extractRootInstallShellFunction(t, "setup_update_command") + ` setup_update_command ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } content, err := os.ReadFile(updatePath) if err != nil { t.Fatalf("read update helper: %v", err) } got := string(content) if strings.Contains(got, "https://github.com/rcourtman/Pulse/releases/latest/download/install.sh") { t.Fatalf("update helper still hardcodes upstream repo:\n%s", got) } if !strings.Contains(got, `INSTALLER_URL="https://github.com/example/pulse-fork/releases/latest/download/install.sh"`) { t.Fatalf("update helper missing configured installer repo:\n%s", got) } if !strings.Contains(got, `INSTALLER_SIG_URL="${INSTALLER_URL}.sshsig"`) { t.Fatalf("update helper missing signature sidecar url:\n%s", got) } } func TestSetupUpdateCommandFailsWhenInstallerDownloadFails(t *testing.T) { tmpDir := t.TempDir() updatePath := filepath.Join(tmpDir, "update") profilePath := filepath.Join(tmpDir, "profile") bashrcPath := filepath.Join(tmpDir, "bashrc") curlPath := filepath.Join(tmpDir, "curl") fakeBashPath := filepath.Join(tmpDir, "bash") if err := os.WriteFile(bashrcPath, []byte(""), 0644); err != nil { t.Fatalf("write bashrc: %v", err) } if err := os.WriteFile(curlPath, []byte("#!/bin/sh\nexit 22\n"), 0755); err != nil { t.Fatalf("write curl stub: %v", err) } if err := os.WriteFile(fakeBashPath, []byte("#!/bin/sh\ncat >/dev/null\nexit 0\n"), 0755); err != nil { t.Fatalf("write bash stub: %v", err) } script := ` PULSE_UPDATE_HELPER_PATH="` + updatePath + `" PULSE_PROFILE_PATH="` + profilePath + `" PULSE_BASHRC_PATH="` + bashrcPath + `" ` + extractRootInstallShellFunction(t, "setup_update_command") + ` setup_update_command ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } cmd := exec.Command("bash", updatePath) cmd.Env = append(os.Environ(), "PATH="+tmpDir+string(os.PathListSeparator)+os.Getenv("PATH")) out, err = cmd.CombinedOutput() if err == nil { t.Fatalf("expected generated update helper to fail when curl fails:\n%s", out) } if !strings.Contains(string(out), "Updating Pulse...") { t.Fatalf("expected helper output before failure, got:\n%s", out) } } func TestResolveInstallScriptDownloadURLUsesForcedVersion(t *testing.T) { script := ` GITHUB_REPO="rcourtman/Pulse" FORCE_VERSION="v1.2.3" FORCE_CHANNEL="" UPDATE_CHANNEL="" ` + extractRootInstallShellFunction(t, "resolve_install_script_download_url") + ` resolve_install_script_download_url ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := strings.TrimSpace(string(out)) want := "https://github.com/rcourtman/Pulse/releases/download/v1.2.3/install.sh" if got != want { t.Fatalf("download url = %q, want %q", got, want) } } // Run the production selectors with bounded, offline transport doubles. This // does not run installer main, mutate services or establish native acceptance. func TestRootInstallReleaseSelectionRejectsChartAndUnknownMetadata(t *testing.T) { out, err := exec.Command("python3", repoFile("scripts", "tests", "test_server_release_selection.py"), "-v").CombinedOutput() if err != nil { t.Fatalf("server release selection controls: %v\n%s", err, out) } } // Exercise installer main only with confined transport and mutation doubles. // In particular, a mistaken remove choice exits before any real removal code. func TestRootInstallExistingMenuRequiresExactAutomaticIntent(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() cmd := exec.CommandContext(ctx, "python3", repoFile("scripts", "tests", "test_server_update_menu.py"), "-v") cmd.Env = append(os.Environ(), "PULSE_INSTALLER_UNDER_TEST="+repoFile("install.sh")) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("existing installer menu consent/target controls: %v\n%s", err, out) } } // Execute the real staging and container-bootstrap functions with confined // transports/host commands and real fixture signatures, never a live LXC. func TestRootInstallTemporaryInputsRemainPrivateAndAuthenticated(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) defer cancel() cmd := exec.CommandContext(ctx, "python3", repoFile("scripts", "tests", "test_server_installer_temporary_inputs.py"), "-v") cmd.Env = append(os.Environ(), "PULSE_INSTALLER_UNDER_TEST="+repoFile("install.sh")) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("installer private/authenticated temporary-input controls: %v\n%s", err, out) } } // Execute discovery and every installer refresh consumer together. systemd // manager operations are confined doubles; real inventory uses a fixture root. func TestRootInstallTimerDiscoveryReachesConsentPreservingRefresh(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() cmd := exec.CommandContext(ctx, "python3", repoFile("scripts", "tests", "test_server_update_timer.py"), "-v") cmd.Env = append(os.Environ(), "PULSE_INSTALLER_UNDER_TEST="+repoFile("install.sh")) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("installer timer discovery/refresh controls: %v\n%s", err, out) } } // Execute both standalone discovery producers and every mutation-owning // consumer, including OR-list callers where Bash disables function errexit. func TestRootInstallServiceDiscoveryFailsClosedWithoutShortReads(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) defer cancel() cmd := exec.CommandContext(ctx, "python3", repoFile("scripts", "tests", "test_server_service_discovery.py"), "-v") cmd.Env = append(os.Environ(), "PULSE_INSTALLER_UNDER_TEST="+repoFile("install.sh"), "PULSE_AUTO_UPDATER_UNDER_TEST="+repoFile("scripts", "pulse-auto-update.sh")) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("installer service discovery/caller controls: %v\n%s", err, out) } // Do not let deleting a fresh-host or failure-retention control silently // leave this all-method fixture green. Real systemctl remains optional on // platforms without it; the portable production-function controls do not. for _, name := range []string{ "test_fresh_host_no_matching_unit_files_is_an_empty_inventory", "test_partial_failed_inventory_is_not_absence", "test_first_inventory_failure_is_not_replaced_by_silent_absence", "test_only_silent_exit_one_is_admitted_as_absence", "test_blank_line_output_is_not_a_silent_failed_inventory", } { if !strings.Contains(string(out), name+" (__main__.ServiceDiscovery."+name+") ... ok") { t.Fatalf("required service inventory control %s did not pass:\n%s", name, out) } } // The real-systemctl control needs systemctl: it must pass where systemctl exists // and be skipped, not silently absent, where it does not (macOS). realControl := "test_real_systemctl_on_a_host_without_pulse_units_selects_the_default" outcome := `\.\.\. ok` if _, lookErr := exec.LookPath("systemctl"); lookErr != nil { outcome = `\.\.\. skipped` } if !regexp.MustCompile(regexp.QuoteMeta(realControl) + ` \(.*\) ` + outcome).Match(out) { t.Fatalf("service discovery control %s did not report %q:\n%s", realControl, outcome, out) } } func TestRootInstallStableReleaseTagRejectsPrereleaseShapes(t *testing.T) { script := ` ` + extractRootInstallShellFunction(t, "is_stable_release_tag") + ` if ! is_stable_release_tag v5.1.29; then echo "rejected v-prefixed stable tag" >&2 exit 1 fi if ! is_stable_release_tag 5.1.29; then echo "rejected bare stable tag" >&2 exit 1 fi if is_stable_release_tag v6.0.0-rc.2; then echo "accepted rc tag" >&2 exit 1 fi if is_stable_release_tag v6.0.0-beta.1; then echo "accepted beta tag" >&2 exit 1 fi if is_stable_release_tag latest; then echo "accepted floating tag" >&2 exit 1 fi ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } } func TestResolveLatestReleaseTagForStableChannelSkipsPrereleaseShapedTags(t *testing.T) { tmpDir := t.TempDir() timeoutPath := filepath.Join(tmpDir, "timeout") curlPath := filepath.Join(tmpDir, "curl") if err := os.WriteFile(timeoutPath, []byte("#!/usr/bin/env bash\nshift\nexec \"$@\"\n"), 0755); err != nil { t.Fatalf("write timeout stub: %v", err) } curlStub := `#!/usr/bin/env bash for arg in "$@"; do if [[ "$arg" == "https://api.github.com/repos/rcourtman/Pulse/releases" ]]; then printf '%s\n' '[{"draft":false,"prerelease":false,"tag_name":"v6.0.0-rc.2"},{"draft":false,"prerelease":false,"tag_name":"v5.1.29"}]' exit 0 fi done echo "unexpected curl invocation: $*" >&2 exit 1 ` if err := os.WriteFile(curlPath, []byte(curlStub), 0755); err != nil { t.Fatalf("write curl stub: %v", err) } script := ` PATH="` + tmpDir + `:$PATH" GITHUB_REPO="rcourtman/Pulse" get_latest_release_from_redirect() { printf '%s\n' v6.0.0-rc.2 } ` + extractRootInstallShellFunction(t, "is_stable_release_tag") + ` ` + extractRootInstallShellFunction(t, "is_pulse_release_tag") + ` ` + extractRootInstallShellFunction(t, "latest_pulse_release_tag_from_json") + ` ` + extractRootInstallShellFunction(t, "latest_stable_release_tag_from_json") + ` ` + extractRootInstallShellFunction(t, "resolve_latest_release_tag_for_channel") + ` resolve_latest_release_tag_for_channel stable ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if got := strings.TrimSpace(string(out)); got != "v5.1.29" { t.Fatalf("stable release = %q, want v5.1.29", got) } } func TestResolveLatestReleaseTagForStableChannelRejectsPrereleaseRedirect(t *testing.T) { script := ` GITHUB_REPO="rcourtman/Pulse" curl() { return 1; } timeout() { shift; "$@"; } get_latest_release_from_redirect() { printf '%s\n' v6.0.0-rc.2 } ` + extractRootInstallShellFunction(t, "is_stable_release_tag") + ` ` + extractRootInstallShellFunction(t, "is_pulse_release_tag") + ` ` + extractRootInstallShellFunction(t, "latest_pulse_release_tag_from_json") + ` ` + extractRootInstallShellFunction(t, "latest_stable_release_tag_from_json") + ` ` + extractRootInstallShellFunction(t, "resolve_latest_release_tag_for_channel") + ` if resolve_latest_release_tag_for_channel stable; then echo "stable channel accepted prerelease redirect" >&2 exit 1 fi ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } } func TestResolveInstallScriptDownloadURLUsesStableReleaseTag(t *testing.T) { tmpDir := t.TempDir() timeoutPath := filepath.Join(tmpDir, "timeout") curlPath := filepath.Join(tmpDir, "curl") if err := os.WriteFile(timeoutPath, []byte("#!/usr/bin/env bash\nshift\nexec \"$@\"\n"), 0755); err != nil { t.Fatalf("write timeout stub: %v", err) } curlStub := `#!/usr/bin/env bash for arg in "$@"; do if [[ "$arg" == "https://api.github.com/repos/rcourtman/Pulse/releases" ]]; then printf '%s\n' '[{"draft":false,"prerelease":true,"tag_name":"v6.0.0-rc.2"},{"draft":false,"prerelease":false,"tag_name":"v5.1.29"}]' exit 0 fi done echo "unexpected curl invocation: $*" >&2 exit 1 ` if err := os.WriteFile(curlPath, []byte(curlStub), 0755); err != nil { t.Fatalf("write curl stub: %v", err) } script := ` PATH="` + tmpDir + `:$PATH" GITHUB_REPO="rcourtman/Pulse" FORCE_VERSION="" FORCE_CHANNEL="" UPDATE_CHANNEL="" get_latest_release_from_redirect() { printf '%s\n' v6.0.0-rc.2 } ` + extractRootInstallShellFunction(t, "is_stable_release_tag") + ` ` + extractRootInstallShellFunction(t, "is_pulse_release_tag") + ` ` + extractRootInstallShellFunction(t, "latest_pulse_release_tag_from_json") + ` ` + extractRootInstallShellFunction(t, "latest_stable_release_tag_from_json") + ` ` + extractRootInstallShellFunction(t, "resolve_latest_release_tag_for_channel") + ` ` + extractRootInstallShellFunction(t, "resolve_install_script_download_url") + ` resolve_install_script_download_url ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := strings.TrimSpace(string(out)) want := "https://github.com/rcourtman/Pulse/releases/download/v5.1.29/install.sh" if got != want { t.Fatalf("download url = %q, want %q", got, want) } } func TestResolveInstallScriptDownloadURLUsesRCReleaseTag(t *testing.T) { tmpDir := t.TempDir() timeoutPath := filepath.Join(tmpDir, "timeout") curlPath := filepath.Join(tmpDir, "curl") if err := os.WriteFile(timeoutPath, []byte("#!/usr/bin/env bash\nshift\nexec \"$@\"\n"), 0755); err != nil { t.Fatalf("write timeout stub: %v", err) } curlStub := `#!/usr/bin/env bash for arg in "$@"; do if [[ "$arg" == "https://api.github.com/repos/rcourtman/Pulse/releases" ]]; then printf '%s\n' '[{"draft":false,"prerelease":true,"tag_name":"v6.0.0-rc.2"},{"draft":false,"prerelease":false,"tag_name":"v5.9.0"}]' exit 0 fi done echo "unexpected curl invocation: $*" >&2 exit 1 ` if err := os.WriteFile(curlPath, []byte(curlStub), 0755); err != nil { t.Fatalf("write curl stub: %v", err) } script := ` PATH="` + tmpDir + `:$PATH" GITHUB_REPO="rcourtman/Pulse" FORCE_VERSION="" FORCE_CHANNEL="rc" UPDATE_CHANNEL="" ` + extractRootInstallShellFunction(t, "is_pulse_release_tag") + ` ` + extractRootInstallShellFunction(t, "latest_pulse_release_tag_from_json") + ` ` + extractRootInstallShellFunction(t, "resolve_latest_release_tag_for_channel") + ` ` + extractRootInstallShellFunction(t, "resolve_install_script_download_url") + ` resolve_install_script_download_url ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := strings.TrimSpace(string(out)) want := "https://github.com/rcourtman/Pulse/releases/download/v6.0.0-rc.2/install.sh" if got != want { t.Fatalf("download url = %q, want %q", got, want) } } func TestInstallAdditionalAgentBinariesCopiesLocalExtrasWithoutNetwork(t *testing.T) { tmpDir := t.TempDir() sourceDir := filepath.Join(tmpDir, "source") installDir := filepath.Join(tmpDir, "opt", "pulse") if err := os.MkdirAll(filepath.Join(sourceDir, "bin"), 0755); err != nil { t.Fatalf("mkdir source bin: %v", err) } if err := os.MkdirAll(filepath.Join(installDir, "bin"), 0755); err != nil { t.Fatalf("mkdir install bin: %v", err) } agentPath := filepath.Join(sourceDir, "bin", "pulse-agent-linux-arm64") if err := os.WriteFile(agentPath, []byte("unified-agent\n"), 0755); err != nil { t.Fatalf("write agent: %v", err) } script := ` INSTALL_DIR="` + installDir + `" GITHUB_REPO="rcourtman/Pulse" curl_calls=0 wget_calls=0 chown() { :; } print_info() { :; } print_warn() { :; } print_success() { :; } curl() { curl_calls=$((curl_calls + 1)); return 99; } wget() { wget_calls=$((wget_calls + 1)); return 99; } ` + extractRootInstallShellFunction(t, "copy_unified_agent_binaries_from_dir") + ` ` + extractRootInstallShellFunction(t, "install_additional_agent_binaries") + ` install_additional_agent_binaries "v6.0.0-rc.3" "` + sourceDir + `" printf 'curl=%s wget=%s\n' "$curl_calls" "$wget_calls" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if got := strings.TrimSpace(string(out)); got != "curl=0 wget=0" { t.Fatalf("expected no network fallback calls, got %q", got) } if _, err := os.Stat(filepath.Join(installDir, "bin", "pulse-agent-linux-arm64")); err != nil { t.Fatalf("expected local unified agent binary to be copied: %v", err) } } func TestInstallAdditionalAgentBinariesSkipsNetworkWhenLocalExtrasAreMissing(t *testing.T) { tmpDir := t.TempDir() sourceDir := filepath.Join(tmpDir, "source") installDir := filepath.Join(tmpDir, "opt", "pulse") if err := os.MkdirAll(filepath.Join(sourceDir, "bin"), 0755); err != nil { t.Fatalf("mkdir source bin: %v", err) } if err := os.MkdirAll(filepath.Join(installDir, "bin"), 0755); err != nil { t.Fatalf("mkdir install bin: %v", err) } script := ` INSTALL_DIR="` + installDir + `" GITHUB_REPO="rcourtman/Pulse" curl_calls=0 wget_calls=0 chown() { :; } print_info() { :; } print_warn() { :; } print_success() { :; } curl() { curl_calls=$((curl_calls + 1)); return 99; } wget() { wget_calls=$((wget_calls + 1)); return 99; } ` + extractRootInstallShellFunction(t, "copy_unified_agent_binaries_from_dir") + ` ` + extractRootInstallShellFunction(t, "install_additional_agent_binaries") + ` install_additional_agent_binaries "v6.0.0-rc.3" "` + sourceDir + `" printf 'curl=%s wget=%s\n' "$curl_calls" "$wget_calls" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if got := strings.TrimSpace(string(out)); got != "curl=0 wget=0" { t.Fatalf("expected missing local extras to skip network fallback, got %q", got) } } func TestInstallSHRequiresPinnedSignatureVerificationForReleaseDownloads(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `PINNED_INSTALLER_SSH_PUBLIC_KEY="__PULSE_INSTALLER_SSH_PUBLIC_KEY__"`, `has_pinned_installer_signature_key() {`, `final_response_header_value "$TMP_HEADERS" "X-Signature-SSHSIG"`, `Server did not provide checksum header; refusing install.`, `Server did not provide SSH signature header; refusing signed install.`, `ssh-keygen -Y verify`, `Binary signature verified`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing signed-download verification contract: %s", needle) } } } func TestInstallSHOfflineInstallerAndUninstallUseAuthenticatedLifecycleTransport(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatal(err) } script := string(content) save := extractInstallShellFunction(t, "save_connection_info") uninstall := extractInstallShellFunction(t, "uninstall_collector_registration") for _, required := range []string{ `collector-download-installer --url "$PULSE_URL" --output "$installer_tmp"`, `verify_download_signature "$installer_tmp" "$installer_signature"`, `collector-uninstall`, `run_collector_lifecycle_command "${uninstall_args[@]}"`, `Pulse did not durably confirm collector removal; local credentials and services were retained.`, } { if !strings.Contains(script, required) { t.Fatalf("installer lifecycle transport contract missing %q", required) } } if regexp.MustCompile(`(?m)^[ \t]*curl[ \t]`).MatchString(save) || strings.Contains(save, `-k`) { t.Fatal("offline installer persistence must not use curl or generic insecure TLS") } if regexp.MustCompile(`(?m)^[ \t]*curl[ \t]`).MatchString(uninstall) || strings.Contains(uninstall, `-k`) { t.Fatal("collector uninstall must not use curl or generic insecure TLS") } } func TestInstallSHOfflineInstallerRequiresValidServedSignature(t *testing.T) { if _, err := exec.LookPath("ssh-keygen"); err != nil { t.Skip("ssh-keygen is required for SSH signature verification") } root := t.TempDir() privateKey := filepath.Join(root, "signing-key") keygen := exec.Command("ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-f", privateKey) if out, err := keygen.CombinedOutput(); err != nil { t.Fatalf("ssh-keygen: %v\n%s", err, out) } publicKeyBytes, err := os.ReadFile(privateKey + ".pub") if err != nil { t.Fatal(err) } publicKeyFields := strings.Fields(string(publicKeyBytes)) if len(publicKeyFields) < 2 { t.Fatalf("invalid generated public key: %q", publicKeyBytes) } publicKey := publicKeyFields[0] + " " + publicKeyFields[1] validPayload := filepath.Join(root, "valid-install.sh") invalidPayload := filepath.Join(root, "invalid-install.sh") if err := os.WriteFile(validPayload, []byte("#!/usr/bin/env bash\necho signed\n"), 0600); err != nil { t.Fatal(err) } if err := os.WriteFile(invalidPayload, []byte("#!/usr/bin/env bash\necho forged\n"), 0600); err != nil { t.Fatal(err) } signer := exec.Command("ssh-keygen", "-Y", "sign", "-f", privateKey, "-n", "pulse-install", validPayload) if out, err := signer.CombinedOutput(); err != nil { t.Fatalf("sign installer: %v\n%s", err, out) } signatureBytes, err := os.ReadFile(validPayload + ".sig") if err != nil { t.Fatal(err) } encodedSignature := base64.StdEncoding.EncodeToString(signatureBytes) fakeLifecycle := filepath.Join(root, "pulse-agent") fakeScript := `#!/usr/bin/env bash set -euo pipefail output="" while [[ $# -gt 0 ]]; do if [[ "$1" == "--output" ]]; then output="$2"; shift 2; continue; fi shift done cp "${PULSE_TEST_INSTALLER_PAYLOAD:?}" "$output" printf '%s\n' "${PULSE_TEST_INSTALLER_SIGNATURE:?}" ` if err := os.WriteFile(fakeLifecycle, []byte(fakeScript), 0755); err != nil { t.Fatal(err) } run := func(t *testing.T, payload string, wantSuccess bool) { t.Helper() caseRoot := t.TempDir() stateDir := filepath.Join(caseRoot, "state") lifecycleDir := filepath.Join(caseRoot, "lifecycle") script := ` set -euo pipefail STATE_DIR="` + stateDir + `" PULSE_URL="https://pulse.example.test" RUNTIME_TOKEN_FILE="$STATE_DIR/token" AGENT_ID="agent-signed" HOSTNAME_OVERRIDE="host-signed" REPORT_IP="" INSECURE="true" SERVER_FINGERPRINT="` + strings.Repeat("a", 64) + `" CURL_CA_BUNDLE="" SAVED_INSTALL_SCRIPT="" LEAST_PRIVILEGE="true" LEAST_PRIVILEGE_USER="$(id -un)" INSTALLER_LIFECYCLE_DIR="` + lifecycleDir + `" COLLECTOR_LIFECYCLE_BINARY_PATH="` + fakeLifecycle + `" PINNED_INSTALLER_SSH_PUBLIC_KEY="` + publicKey + `" INSTALL_SIGNATURE_IDENTITY="pulse-installer" INSTALL_SIGNATURE_NAMESPACE="pulse-install" EXIT_GENERAL=1 EXIT_SIGNATURE_FAILED=17 OUTPUT_FORMAT=text TMP_FILES=() json_event() { :; } log_info() { :; } log_warn() { :; } fail() { printf 'FAIL:%s\n' "$1" >&2; return "${2:-1}"; } curl() { printf 'unexpected curl\n' >&2; return 98; } ` + extractLifecyclePersistenceShellFunctions(t) + ` ` + extractInstallShellFunction(t, "collector_lifecycle_binary") + ` ` + extractInstallShellFunction(t, "decode_base64_to_file") + ` ` + extractInstallShellFunction(t, "verify_download_signature") + ` ` + extractInstallShellFunction(t, "write_connection_state_value") + ` ` + extractInstallShellFunction(t, "save_connection_info") + ` save_connection_info "$STATE_DIR" ` cmd := exec.Command("bash", "-c", script) cmd.Env = append(os.Environ(), "PULSE_TEST_INSTALLER_PAYLOAD="+payload, "PULSE_TEST_INSTALLER_SIGNATURE="+encodedSignature) out, err := cmd.CombinedOutput() if wantSuccess { if err != nil { t.Fatalf("valid signature rejected: %v\n%s", err, out) } body, readErr := os.ReadFile(filepath.Join(lifecycleDir, "install.sh")) if readErr != nil || string(body) != "#!/usr/bin/env bash\necho signed\n" { t.Fatalf("saved installer body=%q err=%v", body, readErr) } return } if err == nil { t.Fatalf("invalid signature was accepted:\n%s", out) } if _, statErr := os.Stat(filepath.Join(lifecycleDir, "install.sh")); !os.IsNotExist(statErr) { t.Fatalf("invalidly signed installer was persisted: %v", statErr) } } run(t, validPayload, true) run(t, invalidPayload, false) } func TestBuildContainerInstallCommandPreservesForcedVersion(t *testing.T) { script := ` FORCE_VERSION="v1.2.3" FORCE_CHANNEL="" UPDATE_CHANNEL="" auto_updates_flag="--enable-auto-updates" BUILD_FROM_SOURCE="false" SOURCE_BRANCH="main" frontend_port="7655" CONFIG_DIR="` + t.TempDir() + `" ` + extractSelectedUpdateChannelShellFunctions(t) + ` ` + extractRootInstallShellFunction(t, "build_container_install_command") + ` build_container_install_command ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := strings.TrimSpace(string(out)) want := "bash /tmp/install.sh --in-container --version 'v1.2.3' --enable-auto-updates" if got != want { t.Fatalf("install cmd = %q, want %q", got, want) } } func TestBuildContainerInstallCommandPreservesExplicitAutoUpdateDisable(t *testing.T) { script := ` FORCE_VERSION="v1.2.3" FORCE_CHANNEL="" UPDATE_CHANNEL="" auto_updates_flag="--disable-auto-updates" BUILD_FROM_SOURCE="false" SOURCE_BRANCH="main" frontend_port="7655" CONFIG_DIR="` + t.TempDir() + `" ` + extractSelectedUpdateChannelShellFunctions(t) + ` ` + extractRootInstallShellFunction(t, "build_container_install_command") + ` build_container_install_command ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := strings.TrimSpace(string(out)) want := "bash /tmp/install.sh --in-container --version 'v1.2.3' --disable-auto-updates" if got != want { t.Fatalf("install cmd = %q, want %q", got, want) } } func TestBuildContainerInstallCommandPassesArchiveToContainer(t *testing.T) { script := ` FORCE_VERSION="" FORCE_CHANNEL="" UPDATE_CHANNEL="" auto_updates_flag="" BUILD_FROM_SOURCE="false" SOURCE_BRANCH="main" frontend_port="7655" container_archive_dest="/tmp/pulse-v6.0.0-rc.1-linux-amd64.tar.gz" CONFIG_DIR="` + t.TempDir() + `" ` + extractSelectedUpdateChannelShellFunctions(t) + ` ` + extractRootInstallShellFunction(t, "build_container_install_command") + ` build_container_install_command ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := strings.TrimSpace(string(out)) want := "bash /tmp/install.sh --in-container --archive /tmp/pulse-v6.0.0-rc.1-linux-amd64.tar.gz" if got != want { t.Fatalf("install cmd = %q, want %q", got, want) } } func TestBuildContainerInstallCommandQuotesArchivePath(t *testing.T) { script := ` FORCE_VERSION="" FORCE_CHANNEL="" UPDATE_CHANNEL="" auto_updates_flag="" BUILD_FROM_SOURCE="false" SOURCE_BRANCH="main" frontend_port="7655" container_archive_dest="/tmp/pulse archive-linux-amd64.tar.gz" CONFIG_DIR="` + t.TempDir() + `" ` + extractSelectedUpdateChannelShellFunctions(t) + ` ` + extractRootInstallShellFunction(t, "build_container_install_command") + ` build_container_install_command ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := strings.TrimSpace(string(out)) want := `bash /tmp/install.sh --in-container --archive /tmp/pulse\ archive-linux-amd64.tar.gz` if got != want { t.Fatalf("install cmd = %q, want %q", got, want) } } func TestBuildContainerInstallCommandPreservesRCChannel(t *testing.T) { script := ` FORCE_VERSION="" FORCE_CHANNEL="rc" UPDATE_CHANNEL="" auto_updates_flag="" BUILD_FROM_SOURCE="false" SOURCE_BRANCH="main" frontend_port="7766" CONFIG_DIR="` + t.TempDir() + `" ` + extractSelectedUpdateChannelShellFunctions(t) + ` ` + extractRootInstallShellFunction(t, "build_container_install_command") + ` build_container_install_command ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := strings.TrimSpace(string(out)) want := "FRONTEND_PORT=7766 bash /tmp/install.sh --in-container --rc" if got != want { t.Fatalf("install cmd = %q, want %q", got, want) } } func TestBuildContainerInstallCommandIgnoresHostConfiguredRCChannelForFreshLXCInstall(t *testing.T) { configDir := t.TempDir() if err := os.WriteFile(filepath.Join(configDir, "system.json"), []byte(`{"updateChannel":"rc"}`), 0644); err != nil { t.Fatalf("write system.json: %v", err) } script := ` FORCE_VERSION="" FORCE_CHANNEL="" UPDATE_CHANNEL="" IGNORE_CONFIGURED_UPDATE_CHANNEL="true" auto_updates_flag="" BUILD_FROM_SOURCE="false" SOURCE_BRANCH="main" frontend_port="7655" CONFIG_DIR="` + configDir + `" ` + extractSelectedUpdateChannelShellFunctions(t) + ` ` + extractRootInstallShellFunction(t, "build_container_install_command") + ` build_container_install_command ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := strings.TrimSpace(string(out)) want := "bash /tmp/install.sh --in-container" if got != want { t.Fatalf("install cmd = %q, want %q", got, want) } } func TestPrintContainerRecoveryCommandPreservesForcedVersion(t *testing.T) { script := ` FORCE_VERSION="v1.2.3" FORCE_CHANNEL="" UPDATE_CHANNEL="" auto_updates_flag="--enable-auto-updates" BUILD_FROM_SOURCE="false" SOURCE_BRANCH="main" frontend_port="7655" CONFIG_DIR="` + t.TempDir() + `" print_info() { printf '%s\n' "$1"; } ` + extractSelectedUpdateChannelShellFunctions(t) + ` ` + extractRootInstallShellFunction(t, "build_container_install_command") + ` ` + extractRootInstallShellFunction(t, "print_container_recovery_command") + ` print_container_recovery_command ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := strings.TrimSpace(string(out)) want := "bash /tmp/install.sh --in-container --version 'v1.2.3' --enable-auto-updates" if got != want { t.Fatalf("recovery command = %q, want %q", got, want) } } func TestPrintContainerRecoveryCommandPreservesExplicitAutoUpdateDisable(t *testing.T) { script := ` FORCE_VERSION="v1.2.3" FORCE_CHANNEL="" UPDATE_CHANNEL="" auto_updates_flag="--disable-auto-updates" BUILD_FROM_SOURCE="false" SOURCE_BRANCH="main" frontend_port="7655" CONFIG_DIR="` + t.TempDir() + `" print_info() { printf '%s\n' "$1"; } ` + extractSelectedUpdateChannelShellFunctions(t) + ` ` + extractRootInstallShellFunction(t, "build_container_install_command") + ` ` + extractRootInstallShellFunction(t, "print_container_recovery_command") + ` print_container_recovery_command ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := strings.TrimSpace(string(out)) want := "bash /tmp/install.sh --in-container --version 'v1.2.3' --disable-auto-updates" if got != want { t.Fatalf("recovery command = %q, want %q", got, want) } } func TestPrintContainerRecoveryCommandPreservesRCChannel(t *testing.T) { script := ` FORCE_VERSION="" FORCE_CHANNEL="rc" UPDATE_CHANNEL="" auto_updates_flag="" BUILD_FROM_SOURCE="false" SOURCE_BRANCH="main" frontend_port="7766" CONFIG_DIR="` + t.TempDir() + `" print_info() { printf '%s\n' "$1"; } ` + extractSelectedUpdateChannelShellFunctions(t) + ` ` + extractRootInstallShellFunction(t, "build_container_install_command") + ` ` + extractRootInstallShellFunction(t, "print_container_recovery_command") + ` print_container_recovery_command ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := strings.TrimSpace(string(out)) want := "FRONTEND_PORT=7766 bash /tmp/install.sh --in-container --rc" if got != want { t.Fatalf("recovery command = %q, want %q", got, want) } } func TestResolveReleaseAssetBaseURLUsesLatestRelease(t *testing.T) { script := ` GITHUB_REPO="rcourtman/Pulse" LATEST_RELEASE="v1.2.3" ` + extractRootInstallShellFunction(t, "resolve_release_asset_base_url") + ` resolve_release_asset_base_url ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := strings.TrimSpace(string(out)) want := "https://github.com/rcourtman/Pulse/releases/download/v1.2.3" if got != want { t.Fatalf("asset base url = %q, want %q", got, want) } } func TestDownloadAutoUpdateScriptUsesSelectedReleaseAssets(t *testing.T) { tmpDir := t.TempDir() curlPath := filepath.Join(tmpDir, "curl") sshKeygenPath := filepath.Join(tmpDir, "ssh-keygen") destPath := filepath.Join(tmpDir, "pulse-auto-update.sh") logPath := filepath.Join(tmpDir, "curl.log") curlStub := `#!/usr/bin/env bash set -e out="" url="" while [[ $# -gt 0 ]]; do case "$1" in -o) out="$2" shift 2 ;; --connect-timeout|--max-time) shift 2 ;; -fsSL|-fsS|-fsSL) shift ;; *) url="$1" shift ;; esac done printf '%s\n' "$url" >> "` + logPath + `" case "$url" in "https://github.com/rcourtman/Pulse/releases/download/v9.9.9/pulse-auto-update.sh") printf '#!/usr/bin/env bash\nexit 0\n' > "$out" ;; "https://github.com/rcourtman/Pulse/releases/download/v9.9.9/pulse-auto-update.sh.sshsig") printf 'signed-payload\n' > "$out" ;; *) echo "unexpected url: $url" >&2 exit 1 ;; esac ` if err := os.WriteFile(curlPath, []byte(curlStub), 0755); err != nil { t.Fatalf("write curl stub: %v", err) } sshKeygenStub := `#!/usr/bin/env bash exit 0 ` if err := os.WriteFile(sshKeygenPath, []byte(sshKeygenStub), 0755); err != nil { t.Fatalf("write ssh-keygen stub: %v", err) } script := ` PATH="` + tmpDir + `:$PATH" GITHUB_REPO="rcourtman/Pulse" LATEST_RELEASE="v9.9.9" PULSE_AUTO_UPDATE_DEST="` + destPath + `" print_warn() { :; } print_info() { :; } INSTALL_SIGNATURE_IDENTITY="pulse-installer" INSTALL_SIGNATURE_NAMESPACE="pulse-install" PINNED_RELEASE_SSH_PUBLIC_KEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZd/DaH+BldzOkq1A8KVTcFk73nAyrE8aJOyf7i00jm pulse-installer" ` + extractRootInstallShellFunction(t, "release_signature_key_available") + ` ` + extractRootInstallShellFunction(t, "require_release_signature_verifier") + ` ` + extractRootInstallShellFunction(t, "verify_release_signature") + ` ` + extractRootInstallShellFunction(t, "resolve_release_asset_base_url") + ` ` + extractRootInstallShellFunction(t, "download_auto_update_script") + ` download_auto_update_script ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } logContent, err := os.ReadFile(logPath) if err != nil { t.Fatalf("read curl log: %v", err) } got := string(logContent) if !strings.Contains(got, "https://github.com/rcourtman/Pulse/releases/download/v9.9.9/pulse-auto-update.sh") { t.Fatalf("missing versioned helper download:\n%s", got) } if !strings.Contains(got, "https://github.com/rcourtman/Pulse/releases/download/v9.9.9/pulse-auto-update.sh.sshsig") { t.Fatalf("missing versioned signature download:\n%s", got) } } func TestPulseAutoUpdatePerformUpdateUsesVersionedInstallerURL(t *testing.T) { // Load the complete helper, not an extracted perform_update with a stale // hand-written dependency list. Its real state/version gates must precede // the URL check. Transport, signature verification and the manager remain // confined doubles; this is not native or published-installer acceptance. updater, err := filepath.Abs(repoFile("scripts", "pulse-auto-update.sh")) if err != nil { t.Fatal(err) } for _, tc := range []struct { name, state, observation string admitted bool }{ {"active", "active", "complete", true}, {"inactive", "inactive", "complete", true}, {"failed-read-with-output", "active", "error", false}, {"partial-read", "active", "partial", false}, {"duplicate-read", "active", "duplicate", false}, } { t.Run(tc.name, func(t *testing.T) { tmpDir := t.TempDir() installDir := filepath.Join(tmpDir, "install") for _, dir := range []string{"tools", "tmp", "config", "install/bin"} { if err := os.MkdirAll(filepath.Join(tmpDir, dir), 0755); err != nil { t.Fatal(err) } } old := "#!/bin/sh\necho 'Pulse v1.0.0'\n" binary := filepath.Join(installDir, "bin", "pulse") if err := os.WriteFile(binary, []byte(old), 0755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(installDir, "VERSION"), []byte("v1.0.0\n"), 0644); err != nil { t.Fatal(err) } writeTool := func(name, script string) { t.Helper() if err := os.WriteFile(filepath.Join(tmpDir, "tools", name), []byte(script), 0755); err != nil { t.Fatal(err) } } writeTool("systemctl", `#!/usr/bin/env bash set -eu printf '%s\n' "$*" >> "$FIXTURE_DIR/manager.log" [[ "$*" == "show pulse-url-fixture --no-pager --property=LoadState --property=ActiveState" ]] || exit 97 printf 'LoadState=loaded\n' [[ "$OBSERVATION" == partial ]] || printf 'ActiveState=%s\n' "$PRIOR_STATE" [[ "$OBSERVATION" != duplicate ]] || printf 'ActiveState=inactive\n' [[ "$OBSERVATION" != error ]] || exit 42 `) writeTool("ssh-keygen", "#!/bin/sh\nexit 0\n") writeTool("curl", `#!/usr/bin/env bash set -eu printf '%s\n' "$*" >> "$FIXTURE_DIR/curl.log" out="" url="" while [[ $# -gt 0 ]]; do case "$1" in -o) out="$2"; shift 2 ;; --disable|-fsSL) shift ;; --connect-timeout|--max-time|--proto|--proto-redir) shift 2 ;; *) [[ -z "$url" ]] || exit 95; url="$1"; shift ;; esac done case "$url" in "https://github.com/rcourtman/Pulse/releases/download/v9.9.9/install.sh") cat > "$out" <<'INSTALLER' #!/usr/bin/env bash set -eu [[ "$*" == "--version v9.9.9" ]] [[ "$PULSE_SERVICE_NAME" == pulse-url-fixture ]] [[ "$PULSE_INSTALL_DIR" == "$FIXTURE_DIR/install" ]] [[ "$PULSE_CONFIG_DIR" == "$FIXTURE_DIR/config" ]] printf '#!/bin/sh\necho '\''Pulse v9.9.9'\''\n' > "$PULSE_INSTALL_DIR/bin/pulse" printf 'v9.9.9\n' > "$PULSE_INSTALL_DIR/VERSION" printf 'installed\n' >> "$FIXTURE_DIR/installer.log" INSTALLER ;; "https://github.com/rcourtman/Pulse/releases/download/v9.9.9/install.sh.sshsig") printf 'signed-payload\n' > "$out" ;; *) exit 96 ;; esac `) script := `source "$AUTO_UPDATER" log() { printf '[%s] %s\n' "$1" "${*:2}"; } # This URL/handoff fixture has script binaries and a simulated manager, # not a managed process. Record the exact verifier handoff; real owned # process identity and fail-closed transactions are required separately. verify_update_running_executable() { [[ "$1" == pulse-url-fixture ]] || return 97 printf '%s\n' "$1" >> "$FIXTURE_DIR/process-readback.log" } # Relocate only the helper's temporary files, keeping creation/mode/cleanup real. mktemp() { local -a args=("$@") local last=$((${#args[@]} - 1)) if [[ "${args[last]}" == /tmp/pulse-* ]]; then args[last]="$FIXTURE_DIR/tmp/${args[last]##*/}" fi command mktemp "${args[@]}" } if perform_update v9.9.9; then echo UPDATE_ACCEPTED else echo UPDATE_REFUSED exit 1 fi [[ -z "$(trap -p RETURN)" ]] ` ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() cmd := exec.CommandContext(ctx, "bash", "-c", script) cmd.Env = append(os.Environ(), "AUTO_UPDATER="+updater, "FIXTURE_DIR="+tmpDir, "PATH="+filepath.Join(tmpDir, "tools")+string(os.PathListSeparator)+os.Getenv("PATH"), "GITHUB_REPO=rcourtman/Pulse", "PULSE_SERVICE_NAME=pulse-url-fixture", "PULSE_INSTALL_DIR="+installDir, "PULSE_CONFIG_DIR="+filepath.Join(tmpDir, "config"), "PRIOR_STATE="+tc.state, "OBSERVATION="+tc.observation) out, runErr := cmd.CombinedOutput() if ctx.Err() != nil { t.Fatalf("fixture exceeded deadline: %v\n%s", ctx.Err(), out) } if (runErr == nil) != tc.admitted { t.Fatalf("admitted=%v, bash: %v\n%s", tc.admitted, runErr, out) } manager, err := os.ReadFile(filepath.Join(tmpDir, "manager.log")) if err != nil || len(manager) == 0 { t.Fatalf("real helper did not observe the selected unit: %v\n%s", err, manager) } for _, call := range strings.Split(strings.TrimSpace(string(manager)), "\n") { if call != "show pulse-url-fixture --no-pager --property=LoadState --property=ActiveState" { t.Fatalf("unexpected manager mutation or query: %s\n%s", call, out) } } processReadback, processErr := os.ReadFile(filepath.Join(tmpDir, "process-readback.log")) if tc.admitted && tc.state == "active" { if processErr != nil || string(processReadback) != "pulse-url-fixture\n" { t.Fatalf("active verifier handoff did not use the selected service: %v\n%s", processErr, processReadback) } } else if !os.IsNotExist(processErr) { t.Fatalf("inactive/refused transaction reached process verification: %v\n%s", processErr, processReadback) } temporary, err := os.ReadDir(filepath.Join(tmpDir, "tmp")) if err != nil || len(temporary) != 0 { t.Fatalf("unexpected retained temporary/backup files: %v, %v\n%s", temporary, err, out) } if !tc.admitted { for _, name := range []string{"curl.log", "installer.log"} { if _, err := os.Stat(filepath.Join(tmpDir, name)); !os.IsNotExist(err) { t.Fatalf("unknown state reached %s: %v\n%s", name, err, out) } } got, err := os.ReadFile(binary) if err != nil || string(got) != old || !strings.Contains(string(out), "UPDATE_REFUSED") { t.Fatalf("refusal did not preserve the original installation: %v\n%s\n%s", err, got, out) } version, err := os.ReadFile(filepath.Join(installDir, "VERSION")) if err != nil || string(version) != "v1.0.0\n" { t.Fatalf("refusal changed version metadata: %v\n%s", err, version) } return } logContent, err := os.ReadFile(filepath.Join(tmpDir, "curl.log")) policy := "--disable -fsSL --connect-timeout 5 --max-time 60 --proto =https --proto-redir =https " want := policy + "https://github.com/rcourtman/Pulse/releases/download/v9.9.9/install.sh -o " wantSignature := policy + "https://github.com/rcourtman/Pulse/releases/download/v9.9.9/install.sh.sshsig -o " requests := strings.Split(strings.TrimSpace(string(logContent)), "\n") if err != nil || len(requests) != 2 || !strings.HasPrefix(requests[0], want) || !strings.HasPrefix(requests[1], wantSignature) { t.Fatalf("expected only the two exact versioned requests: %v\n%s", err, logContent) } installed, err := os.ReadFile(filepath.Join(tmpDir, "installer.log")) if err != nil || string(installed) != "installed\n" || !strings.Contains(string(out), "UPDATE_ACCEPTED") { t.Fatalf("targeted installer did not execute exactly once and complete verification: %v\n%s\n%s", err, installed, out) } got, err := os.ReadFile(binary) if err != nil || string(got) != "#!/bin/sh\necho 'Pulse v9.9.9'\n" { t.Fatalf("installer did not replace the executable identity: %v\n%s", err, got) } }) } } func TestPulseAutoUpdateResolveInstallScriptURLUsesConfiguredRepo(t *testing.T) { script := ` GITHUB_REPO="example/pulse-fork" ` + extractAutoUpdateFunction(t, "resolve_install_script_url") + ` resolve_install_script_url v9.9.9 ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if got := strings.TrimSpace(string(out)); got != "https://github.com/example/pulse-fork/releases/download/v9.9.9/install.sh" { t.Fatalf("resolve_install_script_url = %q", got) } } func TestRepoDockerDocsURLUsesConfiguredRepo(t *testing.T) { script := ` GITHUB_REPO="example/pulse-fork" LATEST_RELEASE="v9.9.9" ` + extractRootInstallShellFunction(t, "repo_web_url") + ` ` + extractRootInstallShellFunction(t, "resolve_latest_release_tag_for_channel") + ` ` + extractRootInstallShellFunction(t, "repo_release_docs_ref") + ` ` + extractRootInstallShellFunction(t, "repo_docs_url_for_path") + ` ` + extractRootInstallShellFunction(t, "repo_docker_docs_url") + ` repo_docker_docs_url ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if got := strings.TrimSpace(string(out)); got != "https://github.com/example/pulse-fork/blob/v9.9.9/docs/DOCKER.md" { t.Fatalf("repo_docker_docs_url = %q", got) } } func TestRepoDockerDocsURLFallsBackToReleaseLandingPageWhenVersionUnknown(t *testing.T) { script := ` GITHUB_REPO="example/pulse-fork" get_latest_release_from_redirect() { return 1; } curl() { return 1; } timeout() { return 1; } ` + extractRootInstallShellFunction(t, "repo_web_url") + ` ` + extractRootInstallShellFunction(t, "is_stable_release_tag") + ` ` + extractRootInstallShellFunction(t, "is_pulse_release_tag") + ` ` + extractRootInstallShellFunction(t, "latest_pulse_release_tag_from_json") + ` ` + extractRootInstallShellFunction(t, "latest_stable_release_tag_from_json") + ` ` + extractRootInstallShellFunction(t, "resolve_latest_release_tag_for_channel") + ` ` + extractRootInstallShellFunction(t, "repo_release_docs_ref") + ` ` + extractRootInstallShellFunction(t, "repo_docs_url_for_path") + ` ` + extractRootInstallShellFunction(t, "repo_docker_docs_url") + ` repo_docker_docs_url ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if got := strings.TrimSpace(string(out)); got != "https://github.com/example/pulse-fork/releases/latest" { t.Fatalf("repo_docker_docs_url fallback = %q", got) } } func TestRepoDockerImageRefUsesConfiguredImageRepo(t *testing.T) { script := ` DOCKER_IMAGE_REPO="example/pulse-enterprise" ` + extractRootInstallShellFunction(t, "repo_docker_image_ref") + ` repo_docker_image_ref latest ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if got := strings.TrimSpace(string(out)); got != "example/pulse-enterprise:latest" { t.Fatalf("repo_docker_image_ref = %q", got) } } func TestCheckDockerEnvironmentUsesConfiguredImageAndDocs(t *testing.T) { script := ` GITHUB_REPO="example/pulse-fork" LATEST_RELEASE="v9.9.9" DOCKER_IMAGE_REPO="example/pulse-enterprise" print_error() { printf 'ERR:%s\n' "$1"; } grep() { return 1; } ` + extractRootInstallShellFunction(t, "repo_web_url") + ` ` + extractRootInstallShellFunction(t, "resolve_latest_release_tag_for_channel") + ` ` + extractRootInstallShellFunction(t, "repo_release_docs_ref") + ` ` + extractRootInstallShellFunction(t, "repo_docs_url_for_path") + ` ` + extractRootInstallShellFunction(t, "repo_docker_docs_url") + ` ` + extractRootInstallShellFunction(t, "repo_docker_image_ref") + ` ` + extractRootInstallShellFunction(t, "check_docker_environment") + ` container="docker" check_docker_environment ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err == nil { t.Fatalf("expected docker environment check to exit non-zero:\n%s", out) } got := string(out) if !strings.Contains(got, "docker run -d -p 7655:7655 example/pulse-enterprise:latest") { t.Fatalf("docker guidance missing configured image repo:\n%s", got) } if !strings.Contains(got, "https://github.com/example/pulse-fork/blob/v9.9.9/docs/DOCKER.md") { t.Fatalf("docker guidance missing configured docs url:\n%s", got) } } func TestBuildPrintedManagementCommandPreservesRCChannel(t *testing.T) { script := ` GITHUB_REPO="rcourtman/Pulse" FORCE_VERSION="" FORCE_CHANNEL="rc" UPDATE_CHANNEL="" ` + extractRootInstallShellFunction(t, "build_printed_management_command") + ` build_printed_management_command update build_printed_management_command reset build_printed_management_command uninstall ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } lines := strings.Split(strings.TrimSpace(string(out)), "\n") if len(lines) != 3 { t.Fatalf("expected 3 commands, got %d:\n%s", len(lines), out) } if got := lines[0]; got != "/bin/update --rc" { t.Fatalf("update command missing rc flag: %s", lines[0]) } if got := lines[1]; got != "/bin/update --rc --reset" { t.Fatalf("reset command missing rc flag: %s", lines[1]) } if strings.Contains(lines[2], "--rc") { t.Fatalf("uninstall command should not include channel flags: %s", lines[2]) } } func TestRootPrintCompletionRevealsBootstrapTokenThroughCLI(t *testing.T) { tmpDir := t.TempDir() configDir := filepath.Join(tmpDir, "config") installDir := filepath.Join(tmpDir, "install") pulseBin := filepath.Join(tmpDir, "bin", "pulse") if err := os.MkdirAll(configDir, 0755); err != nil { t.Fatalf("mkdir config dir: %v", err) } if err := os.MkdirAll(filepath.Dir(pulseBin), 0755); err != nil { t.Fatalf("mkdir pulse bin dir: %v", err) } if err := os.WriteFile(filepath.Join(configDir, ".bootstrap_token"), []byte(`{"version":2,"token_ciphertext":"encrypted-token","token_hash":"hash"}`), 0600); err != nil { t.Fatalf("write encrypted bootstrap token marker: %v", err) } if err := os.WriteFile(pulseBin, []byte(`#!/usr/bin/env bash if [[ "$1" != "bootstrap-token" ]]; then exit 2 fi printf 'Token: raw-bootstrap-token\n' printf 'Data: %s\n' "$PULSE_DATA_DIR" `), 0755); err != nil { t.Fatalf("write fake pulse binary: %v", err) } script := ` RED='' GREEN='' YELLOW='' BLUE='' NC='' CONFIG_DIR="$TEST_CONFIG_DIR" INSTALL_DIR="$TEST_INSTALL_DIR" BINARY_LINK_PATH="$TEST_PULSE_BIN" UPDATE_HELPER_PATH="/bin/update" SERVICE_NAME="pulse" UPDATE_TIMER_UNIT="pulse-update.timer" hostname() { if [[ "${1:-}" == "-I" ]]; then printf '127.0.0.1\n'; else command hostname "$@"; fi; } current_frontend_port() { printf '7655\n'; } print_header() { :; } print_success() { printf '%s\n' "$1"; } print_warn() { printf 'WARN: %s\n' "$1"; } print_info() { printf 'INFO: %s\n' "$1"; } update_timer_exists() { return 1; } update_timer_enabled() { return 1; } build_printed_management_command() { printf '/bin/update\n'; } ` + extractRootInstallShellFunction(t, "print_completion") + ` print_completion ` cmd := exec.Command("bash", "-c", script) cmd.Env = append(os.Environ(), "TEST_CONFIG_DIR="+configDir, "TEST_INSTALL_DIR="+installDir, "TEST_PULSE_BIN="+pulseBin, ) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } got := string(out) if !strings.Contains(got, "raw-bootstrap-token") { t.Fatalf("completion output did not include revealed token:\n%s", got) } if !strings.Contains(got, "Data: "+configDir) { t.Fatalf("completion output did not pass PULSE_DATA_DIR to bootstrap-token:\n%s", got) } if strings.Contains(got, "encrypted-token") || strings.Contains(got, "token_ciphertext") { t.Fatalf("completion output leaked encrypted bootstrap file contents:\n%s", got) } } func TestBuildPrintedManagementCommandPreservesForcedVersion(t *testing.T) { script := ` GITHUB_REPO="rcourtman/Pulse" FORCE_VERSION="v1.2.3" FORCE_CHANNEL="" UPDATE_CHANNEL="" ` + extractRootInstallShellFunction(t, "build_printed_management_command") + ` build_printed_management_command update build_printed_management_command reset ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } lines := strings.Split(strings.TrimSpace(string(out)), "\n") if len(lines) != 2 { t.Fatalf("expected 2 commands, got %d:\n%s", len(lines), out) } if got := lines[0]; got != "/bin/update --version v1.2.3" { t.Fatalf("update command missing version pin: %s", lines[0]) } if got := lines[1]; got != "/bin/update --version v1.2.3 --reset" { t.Fatalf("reset command missing version pin: %s", lines[1]) } } func TestBuildPrintedManagementCommandUsesConfiguredHelperPath(t *testing.T) { script := ` GITHUB_REPO="rcourtman/Pulse" FORCE_VERSION="" FORCE_CHANNEL="" UPDATE_CHANNEL="" UPDATE_HELPER_PATH="/usr/local/bin/update-pulse-preview" ` + extractRootInstallShellFunction(t, "build_printed_management_command") + ` build_printed_management_command update ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if got := strings.TrimSpace(string(out)); got != "/usr/local/bin/update-pulse-preview" { t.Fatalf("printed command = %q, want configured helper path", got) } } func TestSelectedUpdateChannelTreatsPrereleaseVersionAsRC(t *testing.T) { script := ` FORCE_CHANNEL="" FORCE_VERSION="v1.2.3-rc.4" UPDATE_CHANNEL="" CONFIG_DIR="` + t.TempDir() + `" ` + extractSelectedUpdateChannelShellFunctions(t) + ` selected_update_channel ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if got := strings.TrimSpace(string(out)); got != "rc" { t.Fatalf("selected_update_channel = %q, want rc", got) } } func TestResolveTargetReleaseIgnoresHostConfiguredRCChannelForFreshLXCInstall(t *testing.T) { tmpDir := t.TempDir() configDir := filepath.Join(tmpDir, "config") if err := os.MkdirAll(configDir, 0755); err != nil { t.Fatalf("mkdir config dir: %v", err) } if err := os.WriteFile(filepath.Join(configDir, "system.json"), []byte(`{"updateChannel":"rc"}`), 0644); err != nil { t.Fatalf("write system.json: %v", err) } timeoutPath := filepath.Join(tmpDir, "timeout") curlPath := filepath.Join(tmpDir, "curl") if err := os.WriteFile(timeoutPath, []byte("#!/usr/bin/env bash\nshift\nexec \"$@\"\n"), 0755); err != nil { t.Fatalf("write timeout stub: %v", err) } curlStub := `#!/usr/bin/env bash for arg in "$@"; do if [[ "$arg" == "https://api.github.com/repos/rcourtman/Pulse/releases" ]]; then printf '%s\n' '[{"draft":false,"prerelease":false,"tag_name":"v6.0.0-rc.1"},{"draft":false,"prerelease":false,"tag_name":"v5.1.28"}]' exit 0 fi done echo "unexpected curl invocation: $*" >&2 exit 1 ` if err := os.WriteFile(curlPath, []byte(curlStub), 0755); err != nil { t.Fatalf("write curl stub: %v", err) } script := ` PATH="` + tmpDir + `:$PATH" GITHUB_REPO="rcourtman/Pulse" FORCE_VERSION="" FORCE_CHANNEL="" UPDATE_CHANNEL="" IGNORE_CONFIGURED_UPDATE_CHANNEL="true" CONFIG_DIR="` + configDir + `" print_info() { :; } print_warn() { :; } get_latest_release_from_redirect() { return 1; } ` + extractRootInstallShellFunction(t, "read_configured_update_channel") + ` ` + extractRootInstallShellFunction(t, "is_stable_release_tag") + ` ` + extractRootInstallShellFunction(t, "is_pulse_release_tag") + ` ` + extractRootInstallShellFunction(t, "latest_pulse_release_tag_from_json") + ` ` + extractRootInstallShellFunction(t, "latest_stable_release_tag_from_json") + ` ` + extractRootInstallShellFunction(t, "resolve_latest_release_tag_for_channel") + ` ` + extractRootInstallShellFunction(t, "resolve_target_release") + ` resolve_target_release printf '%s\n' "$LATEST_RELEASE" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if got := strings.TrimSpace(string(out)); got != "v5.1.28" { t.Fatalf("LATEST_RELEASE = %q, want v5.1.28", got) } } func TestSetupAutoUpdatesCreatesSystemJSONWithSelectedChannel(t *testing.T) { tmpDir := t.TempDir() configDir := filepath.Join(tmpDir, "config") installDir := filepath.Join(tmpDir, "install") autoUpdateSrc := filepath.Join(installDir, "scripts", "pulse-auto-update.sh") autoUpdateDest, servicePath, timerPath := prepareAutoUpdatePaths(t, tmpDir) if err := os.MkdirAll(configDir, 0755); err != nil { t.Fatalf("mkdir config dir: %v", err) } if err := os.MkdirAll(filepath.Dir(autoUpdateSrc), 0755); err != nil { t.Fatalf("mkdir auto-update src dir: %v", err) } if err := os.WriteFile(autoUpdateSrc, []byte("#!/usr/bin/env bash\n"), 0755); err != nil { t.Fatalf("write auto-update src: %v", err) } script := ` CONFIG_DIR="` + configDir + `" INSTALL_DIR="` + installDir + `" PULSE_AUTO_UPDATE_DEST="` + autoUpdateDest + `" PULSE_UPDATE_SERVICE_PATH="` + servicePath + `" PULSE_UPDATE_TIMER_PATH="` + timerPath + `" FORCE_CHANNEL="rc" UPDATE_CHANNEL="" GITHUB_REPO="rcourtman/Pulse" print_info() { :; } print_warn() { :; } print_success() { :; } safe_systemctl() { :; } systemctl() { return 0; } cp() { command cp "$@"; } chmod() { command chmod "$@"; } chown() { :; } cat() { command cat "$@"; } mkdir() { command mkdir "$@"; } ` + extractSetupAutoUpdatesShellFunctions(t) + ` setup_auto_updates ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } content, err := os.ReadFile(filepath.Join(configDir, "system.json")) if err != nil { t.Fatalf("read system.json: %v", err) } var got map[string]any if err := json.Unmarshal(content, &got); err != nil { t.Fatalf("parse system.json: %v\n%s", err, content) } if enabled, ok := got["autoUpdateEnabled"].(bool); !ok || !enabled { t.Fatalf("system.json missing autoUpdateEnabled=true:\n%s", content) } if channel, ok := got["updateChannel"].(string); !ok || channel != "rc" { t.Fatalf("system.json missing updateChannel rc:\n%s", content) } } func TestSetupAutoUpdatesConfiguresInstalledAutoUpdateRepo(t *testing.T) { tmpDir := t.TempDir() configDir := filepath.Join(tmpDir, "config") systemdDir := filepath.Join(tmpDir, "systemd") installDir := filepath.Join(tmpDir, "install") autoUpdateSrc := filepath.Join(installDir, "scripts", "pulse-auto-update.sh") autoUpdateDest := filepath.Join(tmpDir, "bin", "pulse-auto-update.sh") servicePath := filepath.Join(systemdDir, "pulse-update.service") timerPath := filepath.Join(systemdDir, "pulse-update.timer") if err := os.MkdirAll(configDir, 0755); err != nil { t.Fatalf("mkdir config dir: %v", err) } if err := os.MkdirAll(systemdDir, 0755); err != nil { t.Fatalf("mkdir systemd dir: %v", err) } if err := os.MkdirAll(filepath.Dir(autoUpdateSrc), 0755); err != nil { t.Fatalf("mkdir auto-update src dir: %v", err) } if err := os.MkdirAll(filepath.Dir(autoUpdateDest), 0755); err != nil { t.Fatalf("mkdir auto-update dest dir: %v", err) } if err := os.WriteFile(autoUpdateSrc, []byte("#!/usr/bin/env bash\nGITHUB_REPO=\"rcourtman/Pulse\"\n"), 0755); err != nil { t.Fatalf("write auto-update src: %v", err) } script := ` CONFIG_DIR="` + configDir + `" INSTALL_DIR="` + installDir + `" PULSE_AUTO_UPDATE_DEST="` + autoUpdateDest + `" PULSE_UPDATE_SERVICE_PATH="` + servicePath + `" PULSE_UPDATE_TIMER_PATH="` + timerPath + `" FORCE_CHANNEL="" UPDATE_CHANNEL="" GITHUB_REPO="example/pulse-fork" print_info() { :; } print_warn() { :; } print_success() { :; } safe_systemctl() { :; } systemctl() { return 0; } cp() { command cp "$@"; } chmod() { command chmod "$@"; } chown() { :; } cat() { command cat "$@"; } mkdir() { command mkdir "$@"; } mv() { command mv "$@"; } rm() { command rm "$@"; } awk() { command awk "$@"; } mktemp() { command mktemp "$@"; } ` + extractSetupAutoUpdatesShellFunctions(t) + ` setup_auto_updates ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } content, err := os.ReadFile(autoUpdateDest) if err != nil { t.Fatalf("read configured auto-update script: %v", err) } got := string(content) if strings.Contains(got, "GITHUB_REPO=\"rcourtman/Pulse\"") { t.Fatalf("auto-update script kept upstream repo:\n%s", got) } if !strings.Contains(got, "GITHUB_REPO=\"example/pulse-fork\"") { t.Fatalf("auto-update script missing configured repo:\n%s", got) } serviceContent, err := os.ReadFile(servicePath) if err != nil { t.Fatalf("read service file: %v", err) } if !strings.Contains(string(serviceContent), "ExecStart="+autoUpdateDest) { t.Fatalf("service file missing configured auto-update path:\n%s", serviceContent) } if !strings.Contains(string(serviceContent), "Documentation=https://github.com/example/pulse-fork") { t.Fatalf("service file missing configured documentation url:\n%s", serviceContent) } if _, err := os.Stat(timerPath); err != nil { t.Fatalf("timer file missing: %v", err) } timerContent, err := os.ReadFile(timerPath) if err != nil { t.Fatalf("read timer file: %v", err) } if !strings.Contains(string(timerContent), "Documentation=https://github.com/example/pulse-fork") { t.Fatalf("timer file missing configured documentation url:\n%s", timerContent) } } func TestSetupAutoUpdatesTreatsPrereleaseVersionAsRCChannel(t *testing.T) { tmpDir := t.TempDir() configDir := filepath.Join(tmpDir, "config") installDir := filepath.Join(tmpDir, "install") autoUpdateSrc := filepath.Join(installDir, "scripts", "pulse-auto-update.sh") autoUpdateDest, servicePath, timerPath := prepareAutoUpdatePaths(t, tmpDir) if err := os.MkdirAll(configDir, 0755); err != nil { t.Fatalf("mkdir config dir: %v", err) } if err := os.MkdirAll(filepath.Dir(autoUpdateSrc), 0755); err != nil { t.Fatalf("mkdir auto-update src dir: %v", err) } if err := os.WriteFile(autoUpdateSrc, []byte("#!/usr/bin/env bash\n"), 0755); err != nil { t.Fatalf("write auto-update src: %v", err) } script := ` CONFIG_DIR="` + configDir + `" INSTALL_DIR="` + installDir + `" PULSE_AUTO_UPDATE_DEST="` + autoUpdateDest + `" PULSE_UPDATE_SERVICE_PATH="` + servicePath + `" PULSE_UPDATE_TIMER_PATH="` + timerPath + `" FORCE_CHANNEL="" FORCE_VERSION="v1.2.3-rc.4" UPDATE_CHANNEL="" GITHUB_REPO="rcourtman/Pulse" print_info() { :; } print_warn() { :; } print_success() { :; } safe_systemctl() { :; } systemctl() { return 0; } chown() { :; } ` + extractSetupAutoUpdatesShellFunctions(t) + ` setup_auto_updates ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } content, err := os.ReadFile(filepath.Join(configDir, "system.json")) if err != nil { t.Fatalf("read system.json: %v", err) } var got map[string]any if err := json.Unmarshal(content, &got); err != nil { t.Fatalf("parse system.json: %v\n%s", err, content) } if channel, ok := got["updateChannel"].(string); !ok || channel != "rc" { t.Fatalf("prerelease version did not persist rc channel:\n%s", content) } } func TestSetupAutoUpdatesPreservesRCChannelWhenUpdatingExistingConfig(t *testing.T) { tmpDir := t.TempDir() configDir := filepath.Join(tmpDir, "config") installDir := filepath.Join(tmpDir, "install") autoUpdateSrc := filepath.Join(installDir, "scripts", "pulse-auto-update.sh") autoUpdateDest, servicePath, timerPath := prepareAutoUpdatePaths(t, tmpDir) if err := os.MkdirAll(configDir, 0755); err != nil { t.Fatalf("mkdir config dir: %v", err) } if err := os.MkdirAll(filepath.Dir(autoUpdateSrc), 0755); err != nil { t.Fatalf("mkdir auto-update src dir: %v", err) } if err := os.WriteFile(autoUpdateSrc, []byte("#!/usr/bin/env bash\n"), 0755); err != nil { t.Fatalf("write auto-update src: %v", err) } if err := os.WriteFile(filepath.Join(configDir, "system.json"), []byte(`{"updateChannel":"rc","autoUpdateEnabled":false}`), 0644); err != nil { t.Fatalf("write system.json: %v", err) } script := ` CONFIG_DIR="` + configDir + `" INSTALL_DIR="` + installDir + `" PULSE_AUTO_UPDATE_DEST="` + autoUpdateDest + `" PULSE_UPDATE_SERVICE_PATH="` + servicePath + `" PULSE_UPDATE_TIMER_PATH="` + timerPath + `" FORCE_CHANNEL="" UPDATE_CHANNEL="" GITHUB_REPO="rcourtman/Pulse" print_info() { :; } print_warn() { :; } print_success() { :; } safe_systemctl() { :; } systemctl() { return 0; } command -v jq >/dev/null 2>&1 || true chown() { :; } ` + extractSetupAutoUpdatesShellFunctions(t) + ` setup_auto_updates ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } content, err := os.ReadFile(filepath.Join(configDir, "system.json")) if err != nil { t.Fatalf("read system.json: %v", err) } var got map[string]any if err := json.Unmarshal(content, &got); err != nil { t.Fatalf("parse system.json: %v\n%s", err, content) } if enabled, ok := got["autoUpdateEnabled"].(bool); !ok || !enabled { t.Fatalf("system.json missing enabled flag:\n%s", content) } if channel, ok := got["updateChannel"].(string); !ok || channel != "rc" { t.Fatalf("system.json lost rc channel:\n%s", content) } } // TestInstallSHVerifyAgentServerRegistrationDetectsRejectedToken verifies that // the post-install health check tells a rejected token (401/403) apart from a // transient "agent has not reported yet" state, so the installer can surface an // actionable error instead of leaving a silent 401 loop behind (issue #1515). func TestInstallSHVerifyAgentServerRegistrationDetectsRejectedToken(t *testing.T) { pulseAgent := buildPulseAgentLifecycleBinary(t) verifyFns := extractCollectorLifecycleShellFunctions(t, true) cases := []struct { name string status int body string wantRC string }{ {"rejected token 401", http.StatusUnauthorized, `{"error":"Authentication required"}`, "rc=2"}, {"rejected token 403", http.StatusForbidden, `{"error":"missing_scope"}`, "rc=2"}, {"previous hostname owner during binding", http.StatusForbidden, `{"error":{"code":"agent_lookup_forbidden","message":"Agent does not belong to this API token"}}`, "rc=1"}, {"registered", http.StatusOK, `{"success":true,"agent":{"id":"agent-omv","hostname":"omv","lastSeen":"2026-08-30T12:00:01Z"}}`, "rc=0"}, {"not reported yet", http.StatusNotFound, `{"error":"agent_not_found"}`, "rc=1"}, } for _, tc := range cases { tc := tc t.Run(tc.name, func(t *testing.T) { stateDir := t.TempDir() server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.HasPrefix(r.URL.Path, "/api/agents/agent/lookup") { http.Error(w, "not found", http.StatusNotFound) return } w.WriteHeader(tc.status) _, _ = w.Write([]byte(tc.body)) })) defer server.Close() script := ` PULSE_URL="` + server.URL + `" PULSE_TOKEN="stale-token" STATE_DIR="` + stateDir + `" RUNTIME_TOKEN_FILE="" COLLECTOR_LIFECYCLE_BINARY_PATH="` + pulseAgent + `" LEAST_PRIVILEGE_USER="$(id -un)" SERVER_FINGERPRINT="" AGENT_ID="" HOSTNAME_OVERRIDE="omv" INSECURE="false" CURL_CA_BUNDLE="" ` + verifyFns + ` verify_agent_server_registration echo "rc=$?" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if !strings.Contains(string(out), tc.wantRC) { t.Fatalf("case %q: want %s, got:\n%s", tc.name, tc.wantRC, out) } }) } } func TestInstallSHVerifyAgentServerRegistrationPrefersCanonicalAgentID(t *testing.T) { pulseAgent := buildPulseAgentLifecycleBinary(t) verifyFns := extractCollectorLifecycleShellFunctions(t, true) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if got := r.URL.Query().Get("id"); got != "agent-current" { t.Errorf("lookup id = %q, want canonical agent ID", got) } if got := r.URL.Query().Get("hostname"); got != "" { t.Errorf("hostname lookup = %q, want ID-only lookup", got) } _, _ = w.Write([]byte(`{"success":true,"agent":{"id":"agent-current","hostname":"hostname-owned-by-previous-token","lastSeen":"2026-08-30T12:00:01Z"}}`)) })) defer server.Close() script := ` PULSE_URL="` + server.URL + `" PULSE_TOKEN="install-token" STATE_DIR="` + t.TempDir() + `" RUNTIME_TOKEN_FILE="" COLLECTOR_LIFECYCLE_BINARY_PATH="` + pulseAgent + `" LEAST_PRIVILEGE_USER="$(id -un)" SERVER_FINGERPRINT="" AGENT_ID="agent-current" HOSTNAME_OVERRIDE="hostname-owned-by-previous-token" INSECURE="false" CURL_CA_BUNDLE="" ` + verifyFns + ` verify_agent_server_registration echo "rc=$?" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if !strings.Contains(string(out), "rc=0") { t.Fatalf("canonical ID lookup failed:\n%s", out) } } // TestInstallSHRegistrationRetryWindowOutlastsFirstReportCycle verifies that // the post-install registration check polls for a window instead of warning // after a single lookup: /readyz flips before the agent's first report cycle // completes, so an immediate one-shot lookup routinely misses a healthy // registration (issue #1644). A rejected token still short-circuits because // more polling cannot change a definitive 401/403. func TestInstallSHRegistrationRetryWindowOutlastsFirstReportCycle(t *testing.T) { pulseAgent := buildPulseAgentLifecycleBinary(t) verifyFns := extractCollectorLifecycleShellFunctions(t, true) retryFn := extractInstallShellFunction(t, "verify_agent_server_registration_with_retry") verifyStarted := extractInstallShellFunction(t, "verify_agent_started") if !strings.Contains(verifyStarted, "verify_agent_server_registration_with_retry") { t.Fatal("verify_agent_started does not use the registration retry window") } cases := []struct { name string // statuses is consumed one per lookup; the last value repeats. statuses []int wantRC string }{ {"registered after report cycle", []int{http.StatusNotFound, http.StatusNotFound, http.StatusOK}, "rc=0"}, {"rejected token short-circuits", []int{http.StatusUnauthorized}, "rc=2"}, } for _, tc := range cases { tc := tc t.Run(tc.name, func(t *testing.T) { var mu sync.Mutex lookups := 0 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.HasPrefix(r.URL.Path, "/api/agents/agent/lookup") { http.Error(w, "not found", http.StatusNotFound) return } mu.Lock() idx := lookups lookups++ mu.Unlock() if idx >= len(tc.statuses) { idx = len(tc.statuses) - 1 } w.WriteHeader(tc.statuses[idx]) if tc.statuses[idx] == http.StatusOK { _, _ = w.Write([]byte(`{"success":true,"agent":{"id":"agent-1644","hostname":"pve-1644","lastSeen":"2026-08-30T12:00:01Z"}}`)) } else { _, _ = w.Write([]byte(`{"error":"agent_not_found"}`)) } })) defer server.Close() script := ` PULSE_URL="` + server.URL + `" PULSE_TOKEN="install-token" STATE_DIR="` + t.TempDir() + `" RUNTIME_TOKEN_FILE="" COLLECTOR_LIFECYCLE_BINARY_PATH="` + pulseAgent + `" LEAST_PRIVILEGE_USER="$(id -un)" SERVER_FINGERPRINT="" AGENT_ID="" HOSTNAME_OVERRIDE="pve-1644" INSECURE="false" CURL_CA_BUNDLE="" sleep() { :; } ` + verifyFns + ` ` + retryFn + ` verify_agent_server_registration_with_retry echo "rc=$?" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } if !strings.Contains(string(out), tc.wantRC) { t.Fatalf("case %q: want %s, got:\n%s", tc.name, tc.wantRC, out) } if tc.wantRC == "rc=2" { mu.Lock() got := lookups mu.Unlock() if got != 1 { t.Fatalf("rejected token was retried %d times, want 1 lookup", got) } } }) } } func TestInstallSHRejectedCredentialPreventsSuccessfulCompletion(t *testing.T) { completeFlow := extractInstallShellFunction(t, "complete_installation_flow") script := ` EXIT_AUTH_REJECTED=18 UPGRADE_MODE="true" save_connection_info() { :; } verify_agent_started() { return 2; } report_proxmox_registration_outcome() { printf 'unexpected-proxmox-report\n'; } log_info() { printf 'INFO:%s\n' "$*"; } log_warn() { printf 'WARN:%s\n' "$*"; } log_error() { printf 'ERROR:%s\n' "$*"; } json_event() { printf 'JSON:%s:%s:%s:%s\n' "$1" "$2" "$3" "${4:-}"; } ` + completeFlow + ` complete_installation_flow "/tmp/state" "INSTALL-SUCCESS" "UPGRADE-SUCCESS" "logs" printf 'unreachable\n' ` out, err := exec.Command("bash", "-c", script).CombinedOutput() var exitErr *exec.ExitError if !errors.As(err, &exitErr) || exitErr.ExitCode() != 18 { t.Fatalf("rejected credential exit = %v, want 18\n%s", err, out) } output := string(out) if !strings.Contains(output, "JSON:complete:auth_rejected") || !strings.Contains(output, "authentication failed") { t.Fatalf("rejected credential did not produce actionable non-success completion:\n%s", output) } for _, forbidden := range []string{"INSTALL-SUCCESS", "UPGRADE-SUCCESS", "unreachable", "unexpected-proxmox-report"} { if strings.Contains(output, forbidden) { t.Fatalf("rejected credential emitted forbidden success output %q:\n%s", forbidden, output) } } } // TestInstallSHSurfacesBlockedProxmoxRegistration verifies the installer reads // the agent's proxmox--registration-blocked marker and prints the denial // in its own output instead of leaving it buried in the agent journal (#1644). func TestInstallSHSurfacesBlockedProxmoxRegistration(t *testing.T) { logInfo := extractInstallShellFunction(t, "log_info") logWarn := extractInstallShellFunction(t, "log_warn") logError := extractInstallShellFunction(t, "log_error") reportFn := extractInstallShellFunction(t, "report_proxmox_registration_outcome") completeFlow := extractInstallShellFunction(t, "complete_installation_flow") if !strings.Contains(completeFlow, `report_proxmox_registration_outcome "$state_dir"`) { t.Fatal("complete_installation_flow does not report the Proxmox registration outcome") } clearFn := extractInstallShellFunction(t, "clear_proxmox_state_if_needed") for _, marker := range []string{"proxmox-pve-registration-blocked", "proxmox-pbs-registration-blocked", "proxmox-detected-types"} { if !strings.Contains(clearFn, marker) { t.Fatalf("clear_proxmox_state_if_needed does not clear %s", marker) } } runReport := func(t *testing.T, stateDir string) (string, int) { t.Helper() script := ` NON_INTERACTIVE="false" ENABLE_PROXMOX="true" redact_token() { printf '%s' "$1"; } sleep() { :; } ` + logInfo + ` ` + logWarn + ` ` + logError + ` ` + reportFn + ` report_proxmox_registration_outcome "` + stateDir + `" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() rc := 0 if err != nil { var exitErr *exec.ExitError if !errors.As(err, &exitErr) { t.Fatalf("bash: %v\n%s", err, out) } rc = exitErr.ExitCode() } return string(out), rc } t.Run("blocked marker surfaces reason", func(t *testing.T) { stateDir := t.TempDir() reason := "Pulse at https://pulse.local has no PVE source for https://pve.local:8006 and this agent's token cannot create one." if err := os.WriteFile(filepath.Join(stateDir, "proxmox-pve-registration-blocked"), []byte(reason+"\n"), 0o600); err != nil { t.Fatalf("write blocked marker: %v", err) } out, rc := runReport(t, stateDir) if rc == 0 { t.Fatalf("blocked registration reported success:\n%s", out) } if !strings.Contains(out, "[ERROR]") || !strings.Contains(out, "registration failed") { t.Fatalf("blocked registration not surfaced as an error:\n%s", out) } if !strings.Contains(out, reason) { t.Fatalf("blocked registration output missing agent-recorded reason:\n%s", out) } }) t.Run("registered marker reports success", func(t *testing.T) { stateDir := t.TempDir() if err := os.WriteFile(filepath.Join(stateDir, "proxmox-pve-registered"), []byte("ok\n"), 0o600); err != nil { t.Fatalf("write registered marker: %v", err) } out, rc := runReport(t, stateDir) if rc != 0 { t.Fatalf("registered marker returned rc=%d:\n%s", rc, out) } if !strings.Contains(out, "Proxmox pve node registered with Pulse.") { t.Fatalf("registered marker did not report success:\n%s", out) } }) // A host with both PVE and PBS installed registers each product separately // (#1644). The installer must report each outcome rather than letting the // first marker it finds speak for the whole install. t.Run("combined host reports both products", func(t *testing.T) { stateDir := t.TempDir() writeMarker(t, stateDir, "proxmox-detected-types", "pve\npbs\n") writeMarker(t, stateDir, "proxmox-pve-registered", "ok\n") writeMarker(t, stateDir, "proxmox-pbs-registered", "ok\n") out, rc := runReport(t, stateDir) if rc != 0 { t.Fatalf("combined host returned rc=%d:\n%s", rc, out) } for _, want := range []string{"Proxmox pve node registered with Pulse.", "Proxmox pbs node registered with Pulse."} { if !strings.Contains(out, want) { t.Fatalf("combined host output missing %q:\n%s", want, out) } } if strings.Contains(out, "[ERROR]") { t.Fatalf("fully registered combined host reported an error:\n%s", out) } }) // The regression behind #1644: PVE registered, PBS was refused, and the // installer printed a bare ERROR banner that read as a failed install. // Both outcomes must now show up, each attributed to its own product. t.Run("combined host keeps a successful product visible next to a refusal", func(t *testing.T) { stateDir := t.TempDir() reason := "Pulse at https://pulse.local has no PBS source for https://pbs.local:8007 and this agent's token cannot create one." writeMarker(t, stateDir, "proxmox-detected-types", "pve\npbs\n") writeMarker(t, stateDir, "proxmox-pve-registered", "ok\n") writeMarker(t, stateDir, "proxmox-pbs-registration-blocked", reason+"\n") out, rc := runReport(t, stateDir) if rc == 0 { t.Fatalf("refused pbs registration reported overall success:\n%s", out) } if !strings.Contains(out, "Proxmox pve node registered with Pulse.") { t.Fatalf("pve success was erased by the pbs refusal:\n%s", out) } if !strings.Contains(out, "Proxmox pbs registration failed:") || !strings.Contains(out, reason) { t.Fatalf("pbs refusal not surfaced with its reason:\n%s", out) } }) // Only the detected products are awaited. A PVE-only host must not sit out // the full timeout waiting for a PBS outcome that will never arrive. t.Run("detected types bound what is awaited", func(t *testing.T) { stateDir := t.TempDir() writeMarker(t, stateDir, "proxmox-detected-types", "pve\n") writeMarker(t, stateDir, "proxmox-pve-registered", "ok\n") out, rc := runReport(t, stateDir) if rc != 0 { t.Fatalf("pve-only host returned rc=%d:\n%s", rc, out) } if strings.Contains(out, "pbs") { t.Fatalf("pve-only host reported on pbs:\n%s", out) } if strings.Contains(out, "not confirmed") { t.Fatalf("pve-only host warned about an unconfirmed registration:\n%s", out) } }) // Agents older than the detected-types marker keep the previous timing: // the first outcome that appears ends the wait. t.Run("missing detected types falls back to first outcome", func(t *testing.T) { stateDir := t.TempDir() writeMarker(t, stateDir, "proxmox-pbs-registered", "ok\n") out, rc := runReport(t, stateDir) if rc != 0 { t.Fatalf("legacy agent marker returned rc=%d:\n%s", rc, out) } if !strings.Contains(out, "Proxmox pbs node registered with Pulse.") { t.Fatalf("legacy agent marker did not report success:\n%s", out) } if strings.Contains(out, "not confirmed") { t.Fatalf("legacy agent marker warned about an unconfirmed registration:\n%s", out) } }) t.Run("no outcome warns once", func(t *testing.T) { stateDir := t.TempDir() out, rc := runReport(t, stateDir) if rc != 0 { t.Fatalf("unconfirmed registration returned rc=%d:\n%s", rc, out) } if !strings.Contains(out, "not confirmed") || !strings.Contains(out, "Check the agent logs") { t.Fatalf("unconfirmed registration did not warn:\n%s", out) } }) } func writeMarker(t *testing.T, stateDir, name, contents string) { t.Helper() if err := os.WriteFile(filepath.Join(stateDir, name), []byte(contents), 0o600); err != nil { t.Fatalf("write %s: %v", name, err) } } // TestInstallSHWarnAgentTokenRejectedIsActionable pins the actionable recovery // copy the installer prints when the server rejects the agent's token. func TestInstallSHWarnAgentTokenRejectedIsActionable(t *testing.T) { logWarn := extractInstallShellFunction(t, "log_warn") warnFn := extractInstallShellFunction(t, "warn_agent_token_rejected") script := ` NON_INTERACTIVE="false" redact_token() { printf '%s' "$1"; } ` + logWarn + ` ` + warnFn + ` warn_agent_token_rejected ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("bash: %v\n%s", err, out) } for _, needle := range []string{ "rejected this agent's API token", "Re-run the full agent install command", "mint a fresh token", } { if !strings.Contains(string(out), needle) { t.Fatalf("warn_agent_token_rejected missing %q:\n%s", needle, out) } } } // TestInstallSHAgentKillPatternsExcludeSiblingAgents guards a real incident: // the installer and the Unraid wrapper it generates both killed agents with // pkill -f "^". pkill -f matches the whole command line and "^" only // anchors the start, so on a host running a second, co-installed agent whose // binary name merely starts with the same prefix (pulse-agent alongside // pulse-agent-prod) every install, upgrade, and wrapper restart silently took // down the other agent too. func TestInstallSHAgentKillPatternsExcludeSiblingAgents(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } // Every pkill -f whose pattern is anchored at the agent binary must also // bound the far end, or it matches the sibling agent. unbounded := regexp.MustCompile(`pkill[^\n]*-f "\^\$\{(?:RUNTIME_BINARY|INSTALL_DIR)\}[^"\n]*"`) for _, match := range unbounded.FindAllString(string(content), -1) { if !strings.Contains(match, "[[:space:]]") { t.Errorf("unbounded agent pkill pattern would also match a sibling agent: %s", match) } } // A bare substring kill is worse still: it needs no prefix anchor at all. if strings.Contains(string(content), `pkill -9 -f "pulse-agent"`) { t.Error(`pkill -9 -f "pulse-agent" matches pulse-agent-prod; use -x on the exact process name`) } } // TestInstallSHAgentKillPatternSparesSiblingAgent proves the pattern semantics // rather than its spelling. pkill -f applies a POSIX ERE to the whole command // line, so the same engine is exercised here against the two command lines a // dual-agent host actually presents. func TestInstallSHAgentKillPatternSparesSiblingAgent(t *testing.T) { const ( targetCmd = "/usr/local/bin/pulse-agent --url http://192.168.0.113:7655 --interval 30s" siblingCmd = "/usr/local/bin/pulse-agent-prod --url http://192.168.0.220:7655 --interval 30s" ) ereMatches := func(t *testing.T, pattern, line string) bool { t.Helper() cmd := exec.Command("grep", "-E", "-q", pattern) cmd.Stdin = strings.NewReader(line + "\n") err := cmd.Run() if err == nil { return true } var exitErr *exec.ExitError if errors.As(err, &exitErr) && exitErr.ExitCode() == 1 { return false } t.Fatalf("grep -E %q: %v", pattern, err) return false } unbounded := "^/usr/local/bin/pulse-agent" if !ereMatches(t, unbounded, siblingCmd) { t.Fatal("premise check failed: the unbounded pattern is supposed to match the sibling agent") } bounded := "^/usr/local/bin/pulse-agent([[:space:]]|$)" if !ereMatches(t, bounded, targetCmd) { t.Error("bounded pattern must still match its own agent") } if ereMatches(t, bounded, siblingCmd) { t.Error("bounded pattern must not match pulse-agent-prod") } } // TestInstallSHUnraidStopsWatchdogBeforeAgent guards a real incident: the // Unraid install path killed the agent but never the wrapper supervising it, // then started a second wrapper at the end of the install. The survivor and // the newcomer both loop trying to own the same agent id, and because the old // wrapper is a watchdog it can respawn the agent during the install window // using the previous binary and arguments. func TestInstallSHUnraidStopsWatchdogBeforeAgent(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) start := strings.Index(script, "if [[ -f /etc/unraid-version ]]; then") if start < 0 { t.Fatal("could not locate the Unraid install branch") } endOffset := strings.Index(script[start:], "\n# 4.") if endOffset < 0 { endOffset = len(script) - start } unraid := script[start : start+endOffset] wrapperKill := strings.Index(unraid, `pkill -f "/start-pulse-agent\.sh`) if wrapperKill < 0 { t.Fatal("Unraid install must stop the existing wrapper supervisor, or its watchdog survives the install and races the new one") } agentKill := strings.Index(unraid, `pkill -f "^${RUNTIME_BINARY}`) if agentKill < 0 { t.Fatal("Unraid install must stop the existing agent") } if wrapperKill > agentKill { t.Error("the wrapper supervisor must be stopped before the agent, otherwise the watchdog respawns the agent during the install") } } // TestInstallSHWatchdogKillPatternSparesSiblingWrapper proves the wrapper // pattern discriminates, so stopping this agent's supervisor never stops a // co-installed agent's supervisor on the same host. func TestInstallSHWatchdogKillPatternSparesSiblingWrapper(t *testing.T) { const ( targetCmd = "bash /boot/config/plugins/pulse-agent/start-pulse-agent.sh" siblingCmd = "bash /boot/config/plugins/pulse-agent-prod/start-pulse-agent-prod.sh" ) ereMatches := func(t *testing.T, pattern, line string) bool { t.Helper() cmd := exec.Command("grep", "-E", "-q", pattern) cmd.Stdin = strings.NewReader(line + "\n") err := cmd.Run() if err == nil { return true } var exitErr *exec.ExitError if errors.As(err, &exitErr) && exitErr.ExitCode() == 1 { return false } t.Fatalf("grep -E %q: %v", pattern, err) return false } pattern := `/start-pulse-agent\.sh([[:space:]]|$)` if !ereMatches(t, pattern, targetCmd) { t.Error("wrapper pattern must match this agent's own supervisor") } if ereMatches(t, pattern, siblingCmd) { t.Error("wrapper pattern must not match a co-installed agent's supervisor") } // Premise check: an unescaped dot is what makes this worth pinning. if !ereMatches(t, "start-pulse-agent.sh", targetCmd) { t.Fatal("premise check failed: the loose pattern is supposed to match the target") } } // TestInstallSHWrapperKillsAreBoundedEverywhere pins every wrapper kill in the // installer, not just the Unraid install branch. A bare "start-pulse-agent.sh" // pattern leaves the dot as a wildcard and the far end unbounded, so it also // matches a .bak copy of the wrapper or an editor session holding it open. func TestInstallSHWrapperKillsAreBoundedEverywhere(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) if strings.Contains(script, `pkill -f "start-pulse-agent.sh"`) { t.Error(`bare pkill -f "start-pulse-agent.sh" leaves the dot as a wildcard and the tail unbounded`) } wrapperKill := regexp.MustCompile(`pkill -f "[^"\n]*start-pulse-agent[^"\n]*"`) found := wrapperKill.FindAllString(script, -1) if len(found) == 0 { t.Fatal("expected the installer to stop wrapper supervisors somewhere") } for _, match := range found { if !strings.Contains(match, `\.sh`) { t.Errorf("wrapper kill must escape the dot: %s", match) } if !strings.Contains(match, "[[:space:]]") { t.Errorf("wrapper kill must bound its far end: %s", match) } } } // TestInstallSHStopsWrapperBeforeAgentInEveryBranch pins the ordering rule the // agent-lifecycle and deployment-installability contracts now carry: a wrapper // is a watchdog, so stopping the agent while its wrapper still loops merely // races the respawn. Every branch that stops both must stop the wrapper first. func TestInstallSHStopsWrapperBeforeAgentInEveryBranch(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } lines := strings.Split(string(content), "\n") // Walk the file and, for each wrapper kill, confirm the nearest agent kill // that shares its stop block does not precede it. for i, line := range lines { if !strings.Contains(line, "pkill") || !strings.Contains(line, "start-pulse-agent") { continue } // Look back a short window for an agent kill in the same block. for back := i - 1; back >= 0 && back > i-12; back-- { prev := lines[back] if !strings.Contains(prev, "pkill") { continue } if strings.Contains(prev, "start-pulse-agent") { break } if strings.Contains(prev, `-x "pulse-agent"`) || strings.Contains(prev, "${RUNTIME_BINARY}") || strings.Contains(prev, "${BINARY_NAME}") || strings.Contains(prev, "${INSTALL_DIR}") { t.Errorf("line %d stops the agent before its wrapper at line %d; the watchdog will respawn it:\n %s\n %s", back+1, i+1, strings.TrimSpace(prev), strings.TrimSpace(line)) } break } } } // TestInstallSHVersionMismatchWarningIgnoresBuildMetadata pins the comparison // behind the "Downloaded agent version does not match" warning. A server built // from a working tree reports build metadata the agent it serves never carries, // so a raw comparison fired on every correct development install. That is not a // cosmetic annoyance: the warning is the only client-side signal that a stale // agent was downloaded, and one that cries wolf gets skipped the time it counts. func TestInstallSHVersionMismatchWarningIgnoresBuildMetadata(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) for _, required := range []string{ `NEW_VERSION_NORMALIZED="${NEW_VERSION_NORMALIZED%%+*}"`, `SERVER_VERSION_NORMALIZED="${SERVER_VERSION_NORMALIZED%%+*}"`, } { if !strings.Contains(script, required) { t.Errorf("version comparison must strip semver build metadata, missing: %s", required) } } // Exercise the same normalisation the installer performs. normalize := func(v string) string { v = strings.TrimPrefix(v, "v") if idx := strings.Index(v, "+"); idx >= 0 { v = v[:idx] } return v } cases := []struct { agent string server string warns bool }{ // The shape that fired on every correct dev install. {"v6.2.0-rc.8", "6.2.0-rc.8+git.46.g98a638e00.dirty", false}, {"v6.2.0-rc.8", "6.2.0-rc.8", false}, // The stale download this warning exists to catch. {"v6.0.5-54-gc862fb0ca0", "6.2.0-rc.8+git.46.g98a638e00.dirty", true}, // A prerelease is genuinely not its release. {"v6.2.0", "6.2.0-rc.8+git.46.gabc.dirty", true}, {"v6.1.2", "6.2.0-rc.8", true}, } for _, tc := range cases { got := normalize(tc.agent) != normalize(tc.server) if got != tc.warns { t.Errorf("agent %q vs server %q: warns=%v, want %v", tc.agent, tc.server, got, tc.warns) } } } // The least-privilege profile must stay a real profile, not a cosmetic flag: // a dedicated nologin system user, validated exact-command sudoers grants, a // pct grant that can never widen into pct exec, env-pinned absolute helper // paths, update-time preservation of the profile, and no ambient capability // grant. Silently falling back to root on unsupported platforms is forbidden. func TestInstallSHLeastPrivilegeProfile(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) required := []string{ `--least-privilege) LEAST_PRIVILEGE="true"; shift ;;`, `--grant-smart) GRANT_SMART="true"; shift ;;`, `--grant-pct) GRANT_PCT="true"; shift ;;`, `LEAST_PRIVILEGE_USER="pulse-agent"`, `PRIVILEGE_SUDOERS_FILE="/etc/sudoers.d/pulse-agent"`, "--least-privilege and --enable-commands are mutually exclusive", "--least-privilege is supported only on standard Linux systemd hosts", `useradd --system --user-group --home-dir "$STATE_DIR" --no-create-home`, `visudo -cf`, `install -o root -g root -m 0440 "$sudoers_tmp" "$PRIVILEGE_SUDOERS_FILE"`, "${pct_path} list, ${pct_path} df *", `append_service_env "PULSE_SMARTCTL_PATH" "${PRIVILEGE_HELPER_DIR}/smartctl"`, `append_service_env "PULSE_PCT_PATH" "${PRIVILEGE_HELPER_DIR}/pct"`, `grep -q "^User=${LEAST_PRIVILEGE_USER}\$" "$UNIT"`, `"network-online.target" "$SERVICE_USER" ""`, "# The least-privilege profile never attaches into guests", `rm -f "$PRIVILEGE_SUDOERS_FILE"`, // NoNewPrivileges blocks sudo, so an active grant must relax it or // the helpers silently fail inside the service (proven live). `if [[ "$LEAST_PRIVILEGE" == "true" ]] && [[ "$GRANT_SMART" == "true" || "$GRANT_PCT" == "true" ]]; then`, } for _, needle := range required { if !strings.Contains(script, needle) { t.Fatalf("install.sh missing least-privilege profile invariant: %s", needle) } } if strings.Contains(script, `NOPASSWD: ${pct_path}`) && !strings.Contains(script, "does NOT cover pct exec") { t.Fatal("install.sh must document that the pct grant excludes pct exec") } } func TestInstallSHTypedPrivilegedHelperUnits(t *testing.T) { root := t.TempDir() socketUnit := filepath.Join(root, "pulse-agent-helper.socket") serviceUnit := filepath.Join(root, "pulse-agent-helper.service") script := ` set -euo pipefail PRIVILEGED_HELPER_NAME="pulse-agent-helper" PRIVILEGED_HELPER_SOCKET_PATH="/run/pulse-agent/helper.sock" PRIVILEGED_HELPER_UPDATE_QUARANTINE_DIR="/var/lib/pulse-agent/update-quarantine" PRIVILEGED_HELPER_STATE_DIR="/var/lib/pulse-agent-helper" LEAST_PRIVILEGE_USER="pulse-agent" ` + extractInstallShellFunction(t, "render_privileged_helper_socket_unit") + ` ` + extractInstallShellFunction(t, "render_privileged_helper_service_unit") + ` render_privileged_helper_socket_unit "` + socketUnit + `" render_privileged_helper_service_unit "` + serviceUnit + `" "/usr/local/lib/pulse-agent/pulse-agent-helper" ` if out, err := exec.Command("bash", "-c", script).CombinedOutput(); err != nil { t.Fatalf("render helper units: %v\n%s", err, out) } socketBytes, err := os.ReadFile(socketUnit) if err != nil { t.Fatal(err) } socket := string(socketBytes) for _, required := range []string{ "ListenStream=/run/pulse-agent/helper.sock", "SocketUser=root", "SocketGroup=pulse-agent", "SocketMode=0660", "DirectoryMode=0755", "RemoveOnStop=true", } { if !strings.Contains(socket, required) { t.Fatalf("typed helper socket unit missing %q:\n%s", required, socket) } } serviceBytes, err := os.ReadFile(serviceUnit) if err != nil { t.Fatal(err) } service := string(serviceBytes) for _, required := range []string{ "ExecStart=/usr/local/lib/pulse-agent/pulse-agent-helper", "User=root", "Group=root", "NoNewPrivileges=true", "PrivateNetwork=true", "RestrictAddressFamilies=AF_UNIX", "ProtectSystem=strict", "ProtectHome=true", "ReadOnlyPaths=/var/lib/pulse-agent/update-quarantine", "ReadWritePaths=/var/lib/pulse-agent-helper /usr/local/bin", } { if !strings.Contains(service, required) { t.Fatalf("typed helper service unit missing %q:\n%s", required, service) } } if strings.Contains(service, "PrivateDevices") { t.Fatalf("typed helper service must retain host device visibility for SMART:\n%s", service) } } func TestInstallSHTypedPrivilegedHelperProfileIsOptInAndFailClosed(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) for _, required := range []string{ `PRIVILEGED_HELPER_ENABLED="false"`, `--enable-privileged-helper) PRIVILEGED_HELPER_ENABLED="true"; PRIVILEGED_HELPER_EXPLICIT="true"; shift ;;`, `--disable-privileged-helper) PRIVILEGED_HELPER_ENABLED="false"; PRIVILEGED_HELPER_EXPLICIT="true"; shift ;;`, `--enable-privileged-helper requires --least-privilege`, `--enable-privileged-helper cannot be combined with legacy --grant-smart/--grant-pct sudo paths`, `--enable-privileged-helper is supported only on standard Linux systemd hosts; no broader-privilege fallback was applied`, `Preserving existing typed privileged-helper profile`, `PULSE_AGENT_HELPER_SOCKET`, `chown root:root "${INSTALL_DIR}/${BINARY_NAME}"`, `chown root:root "$PRIVILEGED_HELPER_BINARY_PATH"`, `chown -R "${LEAST_PRIVILEGE_USER}:${LEAST_PRIVILEGE_USER}" "$STATE_DIR"`, `protect_typed_profile_credentials`, `INSTALLER_LIFECYCLE_DIR="/etc/pulse-agent"`, `PRIVILEGED_HELPER_CREDENTIAL_DIR="$INSTALLER_LIFECYCLE_DIR"`, `chown "root:${LEAST_PRIVILEGE_USER}" "$PRIVILEGED_HELPER_CREDENTIAL_DIR"`, `chmod 0750 "$PRIVILEGED_HELPER_CREDENTIAL_DIR"`, `chown "root:${LEAST_PRIVILEGE_USER}" "$RUNTIME_TOKEN_FILE"`, `chmod 0640 "$RUNTIME_TOKEN_FILE"`, `if [[ "$PRIVILEGED_HELPER_ENABLED" != "true" && "$ENABLE_DOCKER" != "false" ]]`, `/download/${PRIVILEGED_HELPER_BINARY_NAME}?${DOWNLOAD_QUERY}`, `verify_download_signature "$TMP_HELPER_BIN" "$helper_signature"`, `teardown_privileged_helper_service`, `socket_owner=$(stat -c '%u:%g' "$PRIVILEGED_HELPER_SOCKET_PATH"`, `socket_mode=$(stat -c '%a' "$PRIVILEGED_HELPER_SOCKET_PATH"`, } { if !strings.Contains(script, required) { t.Fatalf("install.sh missing typed-helper invariant: %s", required) } } if strings.Contains(script, `EXEC_ARG_ITEMS+=(--disable-auto-update)`) { t.Fatal("typed helper profile must keep updater enabled for signed helper-backed activation") } } func TestInstallSHTypedPrivilegeHelperUpdateFilesystemBoundary(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) for _, required := range []string{ `PRIVILEGED_HELPER_STATE_DIR="/var/lib/pulse-agent-helper"`, `PRIVILEGED_HELPER_UPDATE_STAGING_DIR="${PRIVILEGED_HELPER_STATE_DIR}/update-staging"`, `PRIVILEGED_HELPER_UPDATE_QUARANTINE_DIR="/var/lib/pulse-agent/update-quarantine"`, `ReadOnlyPaths=${PRIVILEGED_HELPER_UPDATE_QUARANTINE_DIR}`, `ReadWritePaths=${PRIVILEGED_HELPER_STATE_DIR} /usr/local/bin`, `install -d -o "$LEAST_PRIVILEGE_USER" -g "$LEAST_PRIVILEGE_USER" -m 0700`, `install -d -o root -g root -m 0700`, `Typed privileged-helper updates require the fixed /usr/local/bin/pulse-agent target`, } { if !strings.Contains(script, required) { t.Fatalf("install.sh missing typed helper update boundary %q", required) } } } func TestInstallSHActionRunnerIsSeparateOptInLifecycle(t *testing.T) { content, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatalf("read install.sh: %v", err) } script := string(content) for _, required := range []string{ `ACTION_RUNNER_ENABLED="false"`, `ACTION_RUNNER_NAME="pulse-agent-runner"`, `ACTION_RUNNER_TOKEN_FILE="${ACTION_RUNNER_CONFIG_DIR}/token"`, `--enable-action-runner) ACTION_RUNNER_ENABLED="true"; ACTION_RUNNER_EXPLICIT="true"; shift ;;`, `--disable-action-runner) ACTION_RUNNER_ENABLED="false"; ACTION_RUNNER_EXPLICIT="true"; shift ;;`, `--uninstall-action-runner) UNINSTALL_ACTION_RUNNER="true"; shift ;;`, `--enable-action-runner requires the safe --least-privilege --enable-privileged-helper collector profile`, `--enable-action-runner cannot be combined with legacy collector --enable-commands`, `--action-token-file requires --enable-action-runner (or an existing preserved runner profile)`, `The action runner must use a separate credential from the collector token`, `Preserving existing separately enabled action-runner profile`, `write_action_runner_env_value "PULSE_AGENT_RUNNER_TOKEN_FILE" "$ACTION_RUNNER_TOKEN_FILE"`, `write_action_runner_env_value "PULSE_AGENT_RUNNER_AGENT_ID" "$runner_agent_id"`, `collector-read-agent-id --agent-id-file "${STATE_DIR%/}/agent-id"`, `identity_args=(collector-read-agent-id --agent-id-file "$runner_agent_id_file")`, `write_action_runner_env_value "PULSE_AGENT_RUNNER_HEALTH_FILE" "$ACTION_RUNNER_HEALTH_FILE"`, `write_action_runner_env_value "PULSE_AGENT_RUNNER_ACTIVATION_NONCE" "$ACTION_RUNNER_ACTIVATION_NONCE"`, `write_action_runner_env_value "PULSE_AGENT_RUNNER_HOSTNAME" "$runner_hostname"`, `revoke-credential --url "$runner_url" --token-file "$runner_token_file"`, `cancel-pending-credential --url "$PULSE_URL" --token-file "$token_tmp"`, `Action runner removal requires a successful credential revocation`, `/download/${ACTION_RUNNER_BINARY_NAME}?${DOWNLOAD_QUERY}`, `verify_download_signature "$TMP_ACTION_RUNNER_BIN" "$runner_signature"`, `install -o root -g root -m 0755 "$TMP_ACTION_RUNNER_BIN"`, `chmod 0600 "$ACTION_RUNNER_TOKEN_FILE"`, `ACTION_TOKEN=""`, `action_runner_health_matches_activation "$expected_agent_id" "$activation_nonce"`, `[[ "$health_agent_id" == "$expected_agent_id" && "$health_activation_nonce" == "$expected_nonce" ]]`, `cancel_pending_action_runner_credential "$expected_agent_id" "$expected_hostname" "$replacement_action_token"`, `ACTION_TOKEN="$replacement_action_token"`, `Could not durably persist the complete replacement action-runner credential and environment`, `if ! cancel_pending_action_runner_credential`, `The exact replacement credential and runtime were retained durably`, `did not durably confirm cancellation`, `rolling back runner-only files while leaving monitoring active`, `Pulse action runner removed. Collector monitoring was left installed and running.`, } { if !strings.Contains(script, required) { t.Fatalf("install.sh missing action-runner invariant: %s", required) } } teardown := extractInstallShellFunction(t, "teardown_action_runner_service") if strings.Contains(teardown, "teardown_systemd_agent_service") || strings.Contains(teardown, `rm -f "${INSTALL_DIR}/${BINARY_NAME}"`) { t.Fatalf("runner teardown must not remove the monitoring collector:\n%s", teardown) } if strings.Contains(script, `head -1 "$runner_agent_id_file"`) { t.Fatal("legacy runner removal must not path-read the collector-owned identity file") } } func TestInstallSHActionRunnerConfigBindsKnownAgentIDImmediately(t *testing.T) { root := t.TempDir() envFile := filepath.Join(root, "runner.env") script := ` set -euo pipefail ACTION_RUNNER_CONFIG_DIR="` + filepath.Join(root, "config") + `" ACTION_RUNNER_STATE_DIR="` + filepath.Join(root, "runner-state") + `" ACTION_RUNNER_TOKEN_FILE="` + filepath.Join(root, "config", "token") + `" ACTION_RUNNER_ENV_FILE="` + envFile + `" ACTION_RUNNER_HEALTH_FILE="` + filepath.Join(root, "runner-state", "health.json") + `" ACTION_RUNNER_ACTIVATION_NONCE="` + strings.Repeat("a", 64) + `" STATE_DIR="` + filepath.Join(root, "collector-state") + `" HOSTNAME_OVERRIDE="secure-runtime.lab" AGENT_ID="agent-bound-before-collector-report" ACTION_TOKEN="runner-secret" PULSE_URL="https://pulse.example" SERVER_FINGERPRINT="" CURL_CA_BUNDLE="" INSECURE="false" EXIT_GENERAL=1 EXIT_MISSING_ARGS=2 fail() { printf '%s\n' "$1" >&2; exit "$2"; } install() { local destination="${!#}"; mkdir -p "$destination"; chmod 0700 "$destination"; } chown() { return 0; } stat() { case "$1:$2" in '-c:%u') printf '0\n' ;; '-c:%a') printf '600\n' ;; *) command stat "$@" ;; esac } ` + extractInstallShellFunction(t, "write_action_runner_env_value") + ` ` + extractInstallShellFunction(t, "action_runner_url_uses_loopback_http") + ` ` + extractInstallShellFunction(t, "action_runner_url_transport_allowed") + ` ` + extractInstallShellFunction(t, "resolve_action_runner_agent_id") + ` ` + extractInstallShellFunction(t, "persist_action_runner_replacement_token") + ` ` + extractInstallShellFunction(t, "write_action_runner_config") + ` write_action_runner_config ` if out, err := exec.Command("bash", "-c", script).CombinedOutput(); err != nil { t.Fatalf("write action-runner config: %v\n%s", err, out) } content, err := os.ReadFile(envFile) if err != nil { t.Fatal(err) } want := `PULSE_AGENT_RUNNER_AGENT_ID="agent-bound-before-collector-report"` if !strings.Contains(string(content), want) { t.Fatalf("action-runner environment missing immediate identity binding %q:\n%s", want, content) } for path, wantMode := range map[string]os.FileMode{ filepath.Join(root, "config", "token"): 0o600, envFile: 0o600, } { info, statErr := os.Stat(path) if statErr != nil { t.Fatal(statErr) } if got := info.Mode().Perm(); got != wantMode { t.Fatalf("%s mode = %o, want %o", path, got, wantMode) } } } func TestInstallSHActionRunnerConfigFailureLeavesOnlyCompleteFiles(t *testing.T) { for _, test := range []struct { name string failPattern string wantToken string wantEnvironment string wantDiagnostic string }{ { name: "credential sync failure preserves predecessor", failPattern: ".replacement-token.", wantToken: "old-runner-token\n", wantEnvironment: "old-runner-environment\n", wantDiagnostic: "atomically persist the action-runner credential", }, { name: "environment sync failure preserves complete environment", failPattern: ".runner-env.", wantToken: "new-runner-token\n", wantEnvironment: "old-runner-environment\n", wantDiagnostic: "atomically persist the action-runner environment", }, } { t.Run(test.name, func(t *testing.T) { root := t.TempDir() configDir := filepath.Join(root, "config") stateDir := filepath.Join(root, "state") if err := os.MkdirAll(configDir, 0o700); err != nil { t.Fatal(err) } tokenPath := filepath.Join(configDir, "token") envPath := filepath.Join(configDir, "runner.env") if err := os.WriteFile(tokenPath, []byte("old-runner-token\n"), 0o600); err != nil { t.Fatal(err) } if err := os.WriteFile(envPath, []byte("old-runner-environment\n"), 0o600); err != nil { t.Fatal(err) } script := ` set -u ACTION_RUNNER_CONFIG_DIR="` + configDir + `" ACTION_RUNNER_STATE_DIR="` + stateDir + `" ACTION_RUNNER_TOKEN_FILE="` + tokenPath + `" ACTION_RUNNER_ENV_FILE="` + envPath + `" ACTION_RUNNER_HEALTH_FILE="` + filepath.Join(stateDir, "health.json") + `" ACTION_RUNNER_ACTIVATION_NONCE="` + strings.Repeat("a", 64) + `" STATE_DIR="` + filepath.Join(root, "collector") + `" HOSTNAME_OVERRIDE="secure-runtime.lab" AGENT_ID="agent-durable" ACTION_TOKEN="new-runner-token" PULSE_URL="https://pulse.example" SERVER_FINGERPRINT="" CURL_CA_BUNDLE="" INSECURE="false" EXIT_GENERAL=1 EXIT_MISSING_ARGS=2 FAIL_PATTERN="` + test.failPattern + `" fail() { printf '%s\n' "$1" >&2; exit "$2"; } install() { local destination="${!#}"; mkdir -p "$destination"; chmod 0700 "$destination"; } chown() { return 0; } stat() { case "$1:$2" in '-c:%u') printf '0\n' ;; '-c:%a') printf '600\n' ;; *) command stat "$@" ;; esac } sync() { if [[ "${2:-}" == *"$FAIL_PATTERN"* ]]; then return 1 fi command sync "$@" } ` + extractInstallShellFunction(t, "write_action_runner_env_value") + ` ` + extractInstallShellFunction(t, "action_runner_url_uses_loopback_http") + ` ` + extractInstallShellFunction(t, "action_runner_url_transport_allowed") + ` ` + extractInstallShellFunction(t, "resolve_action_runner_agent_id") + ` ` + extractInstallShellFunction(t, "persist_action_runner_replacement_token") + ` ` + extractInstallShellFunction(t, "write_action_runner_config") + ` write_action_runner_config ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err == nil || !strings.Contains(string(out), test.wantDiagnostic) { t.Fatalf("fault injection did not fail closed: %v\n%s", err, out) } if strings.Contains(string(out), "new-runner-token") { t.Fatalf("credential leaked in failure output: %s", out) } for path, want := range map[string]string{tokenPath: test.wantToken, envPath: test.wantEnvironment} { got, readErr := os.ReadFile(path) if readErr != nil { t.Fatalf("read %s: %v", path, readErr) } if string(got) != want { t.Fatalf("%s = %q, want complete %q", path, got, want) } } for _, pattern := range []string{".replacement-token.*", ".runner-env.*"} { matches, globErr := filepath.Glob(filepath.Join(configDir, pattern)) if globErr != nil { t.Fatal(globErr) } if len(matches) != 0 { t.Fatalf("temporary authority files were retained: %v", matches) } } }) } } func TestInstallSHActionRunnerRejectsGenericInsecureHTTPSBeforeCredentialWrite(t *testing.T) { root := t.TempDir() tokenPath := filepath.Join(root, "config", "token") script := ` set -u ACTION_RUNNER_CONFIG_DIR="` + filepath.Join(root, "config") + `" ACTION_RUNNER_STATE_DIR="` + filepath.Join(root, "state") + `" ACTION_RUNNER_TOKEN_FILE="` + tokenPath + `" ACTION_RUNNER_ENV_FILE="` + filepath.Join(root, "config", "runner.env") + `" ACTION_RUNNER_HEALTH_FILE="` + filepath.Join(root, "state", "health.json") + `" ACTION_RUNNER_ACTIVATION_NONCE="` + strings.Repeat("a", 64) + `" STATE_DIR="` + filepath.Join(root, "collector") + `" HOSTNAME_OVERRIDE="host.local" AGENT_ID="agent-1" ACTION_TOKEN="must-not-be-written" PULSE_URL="https://pulse.example" SERVER_FINGERPRINT="" CURL_CA_BUNDLE="" INSECURE="true" EXIT_GENERAL=1 EXIT_MISSING_ARGS=2 fail() { printf '%s\n' "$1" >&2; exit "$2"; } install() { local destination="${!#}"; mkdir -p "$destination"; chmod 0700 "$destination"; } ` + extractInstallShellFunction(t, "write_action_runner_env_value") + ` ` + extractInstallShellFunction(t, "action_runner_url_uses_loopback_http") + ` ` + extractInstallShellFunction(t, "action_runner_url_transport_allowed") + ` ` + extractInstallShellFunction(t, "persist_action_runner_replacement_token") + ` ` + extractInstallShellFunction(t, "write_action_runner_config") + ` write_action_runner_config ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err == nil || !strings.Contains(string(out), "refuses generic insecure HTTPS") { t.Fatalf("generic insecure HTTPS was not rejected: %v\n%s", err, out) } if _, statErr := os.Stat(tokenPath); !os.IsNotExist(statErr) { t.Fatalf("runner credential was written before transport rejection: %v", statErr) } } func TestInstallSHActionRunnerDirectIdentityOverridesStaleCollectorFile(t *testing.T) { stateDir := t.TempDir() mustWrite(t, filepath.Join(stateDir, "agent-id"), "stale-agent-id\n") script := ` set -euo pipefail STATE_DIR="` + stateDir + `" AGENT_ID="current-agent-id" ` + extractInstallShellFunction(t, "resolve_action_runner_agent_id") + ` resolve_action_runner_agent_id ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("resolve action-runner identity: %v\n%s", err, out) } if got := strings.TrimSpace(string(out)); got != "current-agent-id" { t.Fatalf("resolved action-runner identity = %q, want direct binding", got) } } func TestInstallSHActionRunnerPersistsResolvedIdentityOutsideCollectorState(t *testing.T) { root := t.TempDir() stateDir := filepath.Join(root, "collector-state") if err := os.MkdirAll(stateDir, 0o700); err != nil { t.Fatal(err) } identityPath := filepath.Join(stateDir, "agent-id") if err := os.WriteFile(identityPath, []byte("agent-canonical\n"), 0o640); err != nil { t.Fatal(err) } pulseAgent := buildPulseAgentLifecycleBinary(t) envFile := filepath.Join(root, "runner-config", "runner.env") script := ` set -euo pipefail COLLECTOR_LIFECYCLE_BINARY_PATH="` + pulseAgent + `" LEAST_PRIVILEGE_USER="$(id -un)" INSTALL_DIR="` + root + `" BINARY_NAME="legacy-agent" TMP_BIN="" ACTION_RUNNER_CONFIG_DIR="` + filepath.Join(root, "runner-config") + `" ACTION_RUNNER_STATE_DIR="` + filepath.Join(root, "runner-state") + `" ACTION_RUNNER_TOKEN_FILE="` + filepath.Join(root, "runner-config", "token") + `" ACTION_RUNNER_ENV_FILE="` + envFile + `" ACTION_RUNNER_HEALTH_FILE="` + filepath.Join(root, "runner-state", "health.json") + `" ACTION_RUNNER_ACTIVATION_NONCE="` + strings.Repeat("a", 64) + `" STATE_DIR="` + stateDir + `" HOSTNAME_OVERRIDE="secure-runtime.lab" AGENT_ID="" ACTION_TOKEN="runner-secret" PULSE_URL="https://pulse.example" SERVER_FINGERPRINT="" CURL_CA_BUNDLE="" INSECURE="false" EXIT_GENERAL=1 EXIT_MISSING_ARGS=2 fail() { printf '%s\n' "$1" >&2; exit "$2"; } install() { local destination="${!#}"; mkdir -p "$destination"; chmod 0700 "$destination"; } chown() { return 0; } stat() { case "$1:$2" in '-c:%u') printf '0\n' ;; '-c:%a') printf '600\n' ;; *) command stat "$@" ;; esac } ` + extractInstallShellFunction(t, "collector_lifecycle_binary") + ` ` + extractInstallShellFunction(t, "read_action_runner_env_value") + ` ` + extractInstallShellFunction(t, "resolve_action_runner_agent_id") + ` ` + extractInstallShellFunction(t, "write_action_runner_env_value") + ` ` + extractInstallShellFunction(t, "action_runner_url_uses_loopback_http") + ` ` + extractInstallShellFunction(t, "action_runner_url_transport_allowed") + ` ` + extractInstallShellFunction(t, "persist_action_runner_replacement_token") + ` ` + extractInstallShellFunction(t, "write_action_runner_config") + ` write_action_runner_config printf 'attacker-rewrite\n' > "${STATE_DIR}/agent-id" grep '^PULSE_AGENT_RUNNER_AGENT_ID="agent-canonical"$' "$ACTION_RUNNER_ENV_FILE" if grep -q 'PULSE_AGENT_RUNNER_AGENT_ID_FILE' "$ACTION_RUNNER_ENV_FILE"; then exit 9; fi ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("persist action-runner identity: %v\n%s", err, out) } } func TestInstallSHSafeProfileLifecycleUsesVerifiedInstallFilesystemStage(t *testing.T) { root := t.TempDir() legacy := filepath.Join(root, "pulse-agent") tmpSource := filepath.Join(root, "download.tmp") staged := filepath.Join(root, ".pulse-agent.safe-profile-new") logPath := filepath.Join(root, "binary.log") mustWrite(t, legacy, "#!/bin/sh\nprintf 'legacy:%s\\n' \"$*\" >> \""+logPath+"\"\nexit 91\n") mustWrite(t, tmpSource, "verified bytes that cannot execute on a noexec mount\n") if err := os.Chmod(tmpSource, 0o644); err != nil { t.Fatal(err) } mustWrite(t, staged, "#!/bin/sh\nprintf 'staged:%s\\n' \"$*\" >> \""+logPath+"\"\nexit 0\n") script := ` set -euo pipefail INSTALL_DIR="` + root + `" BINARY_NAME="pulse-agent" TMP_BIN="` + tmpSource + `" COLLECTOR_LIFECYCLE_BINARY_PATH="` + staged + `" ` + extractInstallShellFunction(t, "collector_lifecycle_binary") + ` resolved=$(collector_lifecycle_binary) [[ "$resolved" == "` + staged + `" ]] "$resolved" collector-reduce-authority ` if out, err := exec.Command("bash", "-c", script).CombinedOutput(); err != nil { t.Fatalf("select staged lifecycle binary: %v\n%s", err, out) } logData, err := os.ReadFile(logPath) if err != nil { t.Fatal(err) } if strings.Contains(string(logData), "legacy:") || !strings.Contains(string(logData), "staged:collector-reduce-authority") { t.Fatalf("lifecycle binary selection log: %s", logData) } installScript, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatal(err) } source := string(installScript) stageAt := strings.Index(source, `install -o root -g root -m 0755 "$TMP_BIN" "$SAFE_PROFILE_STAGED_COLLECTOR"`) reduceAt := strings.Index(source, `reduce_safe_profile_collector_authority ||`) moveAt := strings.Index(source, `mv "$SAFE_PROFILE_STAGED_COLLECTOR" "${INSTALL_DIR}/${BINARY_NAME}"`) if stageAt < 0 || reduceAt <= stageAt || moveAt <= reduceAt { t.Fatalf("safe-profile staged lifecycle ordering invalid: stage=%d reduce=%d move=%d", stageAt, reduceAt, moveAt) } } func TestInstallSHActionRunnerReadinessRequiresCurrentActivationNonce(t *testing.T) { root := t.TempDir() healthPath := filepath.Join(root, "health.json") currentNonce := strings.Repeat("a", 64) priorNonce := strings.Repeat("b", 64) writeHealth := func(activated bool, nonce string) { t.Helper() payload := fmt.Sprintf(`{"registered":true,"activated":%t,"activation_nonce":%q,"host_id":"agent-1"}`, activated, nonce) if err := os.WriteFile(healthPath, []byte(payload), 0o600); err != nil { t.Fatal(err) } future := time.Now().Add(24 * time.Hour) if err := os.Chtimes(healthPath, future, future); err != nil { t.Fatal(err) } } check := func(nonce string) bool { t.Helper() script := ` set -euo pipefail ACTION_RUNNER_HEALTH_FILE="` + healthPath + `" stat() { case "$1" in -c) case "$2" in '%u') printf '0\n' ;; '%a') printf '600\n' ;; esac ;; esac } ` + extractInstallShellFunction(t, "action_runner_health_matches_activation") + ` action_runner_health_matches_activation "agent-1" "` + nonce + `" ` return exec.Command("bash", "-c", script).Run() == nil } writeHealth(true, priorNonce) if check(currentNonce) { t.Fatal("stale marker from a prior activation nonce was accepted") } writeHealth(false, currentNonce) if check(currentNonce) { t.Fatal("registered but uncommitted marker was accepted") } writeHealth(true, currentNonce) if !check(currentNonce) { t.Fatal("current activated marker was rejected because of filesystem clock skew") } if err := os.Remove(healthPath); err != nil { t.Fatal(err) } if err := os.Symlink(filepath.Join(root, "missing"), healthPath); err != nil { t.Fatal(err) } if check(currentNonce) { t.Fatal("symlinked activation marker was accepted") } } func TestInstallSHActionRunnerPostCommitReadinessFailureRetainsReplacement(t *testing.T) { for _, test := range []struct { name string cancelBody string recoveryConfigFailure bool stopFailure bool rollback bool effectiveTargetValid bool effectiveTargetDrifts bool remaskFailure bool tokenWriteBody string wantToken string }{ { name: "durable pending cancellation authorizes rollback", cancelBody: `[[ "$3" == "new-runner-token" ]] || return 1; return 0`, rollback: true, effectiveTargetValid: true, tokenWriteBody: `printf 'new-runner-token\n' > "$ACTION_RUNNER_TOKEN_FILE"`, wantToken: "old:token\n", }, { name: "activation committed or cancel indeterminate", cancelBody: `[[ "$3" == "new-runner-token" ]] || return 1; return 1`, effectiveTargetValid: true, tokenWriteBody: `printf 'new-runner-token\n' > "$ACTION_RUNNER_TOKEN_FILE"`, wantToken: "new-runner-token\n", }, { name: "unsafe effective target stays fenced after indeterminate cancellation", cancelBody: `[[ "$3" == "new-runner-token" ]] || return 1; return 1`, effectiveTargetValid: false, tokenWriteBody: `printf 'new-runner-token\n' > "$ACTION_RUNNER_TOKEN_FILE"`, wantToken: "new-runner-token\n", }, { name: "post-install effective target drift stays fenced", cancelBody: `[[ "$3" == "new-runner-token" ]] || return 1; return 1`, effectiveTargetValid: true, effectiveTargetDrifts: true, tokenWriteBody: `printf 'new-runner-token\n' > "$ACTION_RUNNER_TOKEN_FILE"`, wantToken: "new-runner-token\n", }, { name: "effective target remask failure retains repair artifacts", cancelBody: `[[ "$3" == "new-runner-token" ]] || return 1; return 1`, effectiveTargetValid: false, remaskFailure: true, tokenWriteBody: `printf 'new-runner-token\n' > "$ACTION_RUNNER_TOKEN_FILE"`, wantToken: "new-runner-token\n", }, { name: "replacement authority state durable write failure", cancelBody: "return 1", recoveryConfigFailure: true, effectiveTargetValid: true, tokenWriteBody: `: > "$ACTION_RUNNER_TOKEN_FILE"`, wantToken: "", }, { name: "failed stop retains fenced recovery material", cancelBody: "return 91", stopFailure: true, effectiveTargetValid: true, tokenWriteBody: `printf 'new-runner-token\n' > "$ACTION_RUNNER_TOKEN_FILE"`, wantToken: "new-runner-token\n", }, } { t.Run(test.name, func(t *testing.T) { testInstallSHActionRunnerReadinessFailureRetainsReplacement(t, test.cancelBody, test.recoveryConfigFailure, test.stopFailure, test.rollback, test.effectiveTargetValid, test.effectiveTargetDrifts, test.remaskFailure, test.tokenWriteBody, test.wantToken) }) } } func testInstallSHActionRunnerReadinessFailureRetainsReplacement(t *testing.T, cancelBody string, recoveryConfigFailure, stopFailure, rollback, effectiveTargetValid, effectiveTargetDrifts, remaskFailure bool, tokenWriteBody, wantToken string) { t.Helper() cancelFunction := `cancel_pending_action_runner_credential() { ` + cancelBody + `; }` root := t.TempDir() binPath := filepath.Join(root, "bin", "pulse-agent-runner") unitPath := filepath.Join(root, "systemd", "pulse-agent-runner.service") envPath := filepath.Join(root, "config", "runner.env") tokenPath := filepath.Join(root, "config", "token") stateDir := filepath.Join(root, "state") for _, path := range []string{binPath, unitPath, envPath, tokenPath} { if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) } mustWrite(t, path, "old:"+filepath.Base(path)+"\n") } if err := os.MkdirAll(stateDir, 0o700); err != nil { t.Fatal(err) } newBinary := filepath.Join(root, "new-runner") mustWrite(t, newBinary, "new:runner\n") serviceLog := filepath.Join(root, "systemctl.log") script := ` set -u ACTION_RUNNER_NAME="pulse-agent-runner" ACTION_RUNNER_BINARY_PATH="` + binPath + `" ACTION_RUNNER_SERVICE_UNIT="` + unitPath + `" ACTION_RUNNER_ENV_FILE="` + envPath + `" ACTION_RUNNER_TOKEN_FILE="` + tokenPath + `" ACTION_RUNNER_HEALTH_FILE="` + filepath.Join(stateDir, "health.json") + `" ACTION_RUNNER_STATE_DIR="` + stateDir + `" ACTION_RUNNER_CONFIG_DIR="` + filepath.Dir(tokenPath) + `" PRIVILEGE_HELPER_DIR="` + filepath.Join(root, "helper") + `" TMP_ACTION_RUNNER_BIN="` + newBinary + `" ACTION_TOKEN="new-runner-token" ACTION_RUNNER_ACTIVATION_NONCE="" HOSTNAME_OVERRIDE="host-1.local" STATE_DIR="` + filepath.Join(root, "collector-state") + `" PULSE_URL="http://127.0.0.1:7655" INSECURE="true" CURL_CA_BUNDLE="" EXIT_GENERAL=1 RECOVERY_CONFIG_FAILURE="` + strconv.FormatBool(recoveryConfigFailure) + `" STOP_FAILURE="` + strconv.FormatBool(stopFailure) + `" EFFECTIVE_TARGET_VALID="` + strconv.FormatBool(effectiveTargetValid) + `" EFFECTIVE_TARGET_DRIFTS="` + strconv.FormatBool(effectiveTargetDrifts) + `" EFFECTIVE_TARGET_MARKER="` + filepath.Join(root, "effective-target-validated") + `" REMASK_FAILURE="` + strconv.FormatBool(remaskFailure) + `" MASK_COUNT_FILE="` + filepath.Join(root, "runtime-mask-count") + `" CONFIG_WRITE_MARKER="` + filepath.Join(root, "config-write-marker") + `" SERVICE_ACTIVE_MARKER="` + filepath.Join(root, "service-active-marker") + `" generate_action_runner_activation_nonce() { printf '%064d\n' 0; } restore_selinux_contexts() { return 0; } write_action_runner_config() { printf 'write-config\n' >> "` + serviceLog + `" if [[ -e "$CONFIG_WRITE_MARKER" && "$RECOVERY_CONFIG_FAILURE" == "true" ]]; then return 1 fi : > "$CONFIG_WRITE_MARKER" ` + tokenWriteBody + ` printf 'new:env\n' > "$ACTION_RUNNER_ENV_FILE" } render_action_runner_service_unit() { printf 'new:unit\n' > "$1"; } install() { local source="${@: -2:1}" local destination="${@: -1}" mkdir -p "$(dirname "$destination")" cp "$source" "$destination" } chown() { return 0; } chmod() { return 0; } stat() { case "$1:$2" in '-c:%u') printf '0\n' ;; '-c:%a') printf '600\n' ;; *) command stat "$@" ;; esac } systemctl() { printf '%s\n' "$*" >> "` + serviceLog + `" case "$1" in mask) mask_count=0 if [[ -e "$MASK_COUNT_FILE" ]]; then read -r mask_count < "$MASK_COUNT_FILE"; fi mask_count=$((mask_count + 1)) printf '%s\n' "$mask_count" > "$MASK_COUNT_FILE" if [[ "$REMASK_FAILURE" == "true" && "$mask_count" -gt 2 ]]; then return 1 fi ;; restart) : > "$SERVICE_ACTIVE_MARKER" ;; stop) if [[ "$STOP_FAILURE" == "true" ]]; then return 1 fi rm -f "$SERVICE_ACTIVE_MARKER" ;; is-active) [[ -e "$SERVICE_ACTIVE_MARKER" ]] ; return ;; esac return 0 } action_runner_health_matches_activation() { return 1; } action_runner_verify_effective_target() { [[ "$EFFECTIVE_TARGET_VALID" == "true" ]] || return 1 if [[ "$EFFECTIVE_TARGET_DRIFTS" == "true" && -e "$EFFECTIVE_TARGET_MARKER" ]]; then return 1 fi : > "$EFFECTIVE_TARGET_MARKER" } resolve_action_runner_agent_id() { printf 'agent-1\n'; } ` + cancelFunction + ` sleep() { return 0; } log_error() { printf 'ERROR: %s\n' "$1" >&2; } log_info() { printf 'INFO: %s\n' "$1"; } fail() { printf 'FAIL: %s\n' "$1" >&2; exit "$2"; } ` + extractInstallShellFunction(t, "action_runner_reestablish_repair_fence") + ` ` + extractInstallShellFunction(t, "provision_action_runner") + ` provision_action_runner ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err == nil { t.Fatalf("post-commit readiness failure unexpectedly succeeded:\n%s", out) } if rollback { if !strings.Contains(string(out), "rolling back runner-only files") || strings.Contains(string(out), "retained durably") { t.Fatalf("204 cancellation did not exclusively authorize predecessor restore:\n%s", out) } } else if stopFailure { if !strings.Contains(string(out), "could not confirm the replacement is inactive") || strings.Contains(string(out), "did not durably confirm cancellation") { t.Fatalf("failed stop did not retain fenced recovery state before classification:\n%s", out) } } else if recoveryConfigFailure { if strings.Contains(string(out), "retained durably") || !strings.Contains(string(out), "re-enrollment") { t.Fatalf("durable authority-state failure did not fail closed for re-enrollment:\n%s", out) } } else if !strings.Contains(string(out), "replacement credential and runtime were retained durably") || !strings.Contains(string(out), "repair") { t.Fatalf("missing repair-required result:\n%s", out) } if !rollback && !stopFailure && !strings.Contains(string(out), "did not durably confirm cancellation") { t.Fatalf("missing atomic cancellation diagnostic:\n%s", out) } wantBinary, wantUnit, wantEnv := "new:runner\n", "new:unit\n", "new:env\n" if rollback { wantBinary, wantUnit, wantEnv = "old:pulse-agent-runner\n", "old:pulse-agent-runner.service\n", "old:runner.env\n" } for path, want := range map[string]string{ binPath: wantBinary, unitPath: wantUnit, envPath: wantEnv, tokenPath: wantToken, } { data, readErr := os.ReadFile(path) if readErr != nil { t.Fatalf("read retained %s: %v", path, readErr) } if string(data) != want { t.Fatalf("retained %s = %q, want %q", path, data, want) } } backups, err := filepath.Glob(filepath.Join(root, "**", "*.pulse-install-backup.*")) if err != nil { t.Fatal(err) } retainsRepairArtifacts := recoveryConfigFailure || stopFailure || remaskFailure if retainsRepairArtifacts && len(backups) == 0 { t.Fatal("durable authority-state failure removed every predecessor backup before repair") } if !retainsRepairArtifacts && len(backups) != 0 { t.Fatalf("revoked predecessor backups retained: %v", backups) } serviceCalls, err := os.ReadFile(serviceLog) if err != nil { t.Fatal(err) } if !strings.Contains(string(serviceCalls), "stop pulse-agent-runner.service") { t.Fatalf("replacement was not stopped before classification: %s", serviceCalls) } disableAt := strings.Index(string(serviceCalls), "disable pulse-agent-runner.service") maskAt := strings.Index(string(serviceCalls), "mask --runtime pulse-agent-runner.service") writeAt := strings.Index(string(serviceCalls), "write-config") if disableAt < 0 || maskAt < disableAt || writeAt < maskAt { t.Fatalf("runner was not disabled and runtime-masked before authority files changed: %s", serviceCalls) } if (recoveryConfigFailure || stopFailure || remaskFailure) && strings.Contains(string(serviceCalls), "enable --now pulse-agent-runner.service") { t.Fatalf("runner restarted without a complete durable authority state: %s", serviceCalls) } serviceLines := "\n" + string(serviceCalls) lastMaskAt := strings.LastIndex(serviceLines, "\nmask --runtime pulse-agent-runner.service") lastUnmaskAt := strings.LastIndex(serviceLines, "\nunmask --runtime pulse-agent-runner.service") if (recoveryConfigFailure || stopFailure) && lastMaskAt < lastUnmaskAt { t.Fatalf("runner remained unmasked without a complete durable authority state: %s", serviceCalls) } if !recoveryConfigFailure && !stopFailure && effectiveTargetValid && !effectiveTargetDrifts && !strings.Contains(string(serviceCalls), "enable --now pulse-agent-runner.service") { t.Fatalf("replacement was not stopped for classification and restarted for repair: %s", serviceCalls) } if !recoveryConfigFailure && !stopFailure && effectiveTargetValid && !effectiveTargetDrifts && lastUnmaskAt < lastMaskAt { t.Fatalf("complete authority state was not unmasked for restart: %s", serviceCalls) } if !recoveryConfigFailure && !stopFailure && (!effectiveTargetValid || effectiveTargetDrifts) { if strings.Contains(string(serviceCalls), "enable --now pulse-agent-runner.service") { t.Fatalf("unsafe effective target was restarted: %s", serviceCalls) } if lastMaskAt < lastUnmaskAt || !strings.Contains(string(out), "effective-target validation") { t.Fatalf("unsafe effective target did not remain fenced with an actionable diagnostic:\n%s\n%s", serviceCalls, out) } } if remaskFailure && !strings.Contains(string(out), "could not re-establish the disabled runtime mask") { t.Fatalf("remask failure was not surfaced explicitly:\n%s\n%s", serviceCalls, out) } } func TestInstallSHActionRunnerAtomicCancelUsesPrivateGoTransportAndRequires204(t *testing.T) { const token = "runner-cancel-secret" root := t.TempDir() runnerBinary := filepath.Join(root, "pulse-agent-runner") build := exec.Command("go", "build", "-o", runnerBinary, "./cmd/pulse-agent-runner") build.Dir = filepath.Dir(repoFile("go.mod")) if output, err := build.CombinedOutput(); err != nil { t.Fatalf("build action runner: %v\n%s", err, output) } caSource := httptest.NewTLSServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})) defer caSource.Close() caFile := filepath.Join(root, "runner-ca.pem") if err := os.WriteFile(caFile, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caSource.Certificate().Raw}), 0o600); err != nil { t.Fatal(err) } for _, tc := range []struct { name string statusCode int wantOK bool }{{"durable cancellation", http.StatusNoContent, true}, {"activation committed", http.StatusConflict, false}} { t.Run(tc.name, func(t *testing.T) { var gotAuthorization, gotMethod, gotBody string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotAuthorization = r.Header.Get("Authorization") gotMethod = r.Method body, _ := io.ReadAll(r.Body) gotBody = string(body) w.WriteHeader(tc.statusCode) })) defer server.Close() configDir := filepath.Join(root, strings.ReplaceAll(tc.name, " ", "-")) if err := os.MkdirAll(configDir, 0o700); err != nil { t.Fatal(err) } argsLog := filepath.Join(configDir, "args.log") wrapper := filepath.Join(configDir, "runner-wrapper") mustWrite(t, wrapper, "#!/bin/bash\nprintf '%s\\n' \"$@\" > \""+argsLog+"\"\nexec \""+runnerBinary+"\" \"$@\"\n") if err := os.Chmod(wrapper, 0o755); err != nil { t.Fatal(err) } script := ` set -euo pipefail ACTION_RUNNER_BINARY_PATH="` + wrapper + `" ACTION_RUNNER_CONFIG_DIR="` + configDir + `" PULSE_URL="` + server.URL + `" CURL_CA_BUNDLE="` + caFile + `" SERVER_FINGERPRINT="` + strings.Repeat("ab", 32) + `" chown() { return 0; } sync() { return 0; } ` + extractInstallShellFunction(t, "action_runner_url_uses_loopback_http") + ` ` + extractInstallShellFunction(t, "action_runner_url_transport_allowed") + ` ` + extractInstallShellFunction(t, "cancel_pending_action_runner_credential") + ` cancel_pending_action_runner_credential "agent-1" "host-1.local" "` + token + `" ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if (err == nil) != tc.wantOK { t.Fatalf("cancel result error=%v, want success=%v\n%s", err, tc.wantOK, out) } if gotAuthorization != "Bearer "+token || gotMethod != http.MethodDelete || gotBody != "" { t.Fatalf("cancel request auth=%q method=%q body=%q", gotAuthorization, gotMethod, gotBody) } if strings.Contains(string(out), token) { t.Fatalf("cancel credential leaked in output: %s", out) } args, readErr := os.ReadFile(argsLog) if readErr != nil || !strings.Contains(string(args), "--cacert\n"+caFile) || !strings.Contains(string(args), "--server-fingerprint\n"+strings.Repeat("ab", 32)) { t.Fatalf("runner lifecycle CA/pin args = %q, error=%v", args, readErr) } }) } } func TestInstallSHActionRunnerSelfRevokeUsesPrivateCredential(t *testing.T) { const token = "runner-secret-that-must-not-appear-in-output" var gotAuthorization string var gotMethod string var gotBody map[string]string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotAuthorization = r.Header.Get("Authorization") gotMethod = r.Method if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { t.Errorf("decode revoke body: %v", err) http.Error(w, "bad request", http.StatusBadRequest) return } w.WriteHeader(http.StatusNoContent) })) defer server.Close() root := t.TempDir() runnerBinary := filepath.Join(root, "pulse-agent-runner") build := exec.Command("go", "build", "-o", runnerBinary, "./cmd/pulse-agent-runner") build.Dir = filepath.Dir(repoFile("go.mod")) if output, err := build.CombinedOutput(); err != nil { t.Fatalf("build action runner: %v\n%s", err, output) } envFile := filepath.Join(root, "runner.env") tokenFile := filepath.Join(root, "token") mustWrite(t, tokenFile, token+"\n") if err := os.Chmod(tokenFile, 0o600); err != nil { t.Fatal(err) } mustWrite(t, envFile, strings.Join([]string{ `PULSE_URL="` + server.URL + `"`, `PULSE_AGENT_RUNNER_HOSTNAME="secure-runtime.lab"`, `PULSE_AGENT_RUNNER_AGENT_ID="agent-secure-runtime"`, `PULSE_AGENT_RUNNER_AGENT_ID_FILE="` + filepath.Join(root, "missing-agent-id") + `"`, `PULSE_AGENT_RUNNER_TOKEN_FILE="` + tokenFile + `"`, `PULSE_INSECURE="true"`, }, "\n")+"\n") script := ` set -euo pipefail ACTION_RUNNER_ENV_FILE="` + envFile + `" ACTION_RUNNER_BINARY_PATH="` + runnerBinary + `" stat() { if [[ "$1" == "-c" && "$2" == "%a" ]]; then printf '600\n'; return 0; fi command stat "$@" } ` + extractInstallShellFunction(t, "read_action_runner_env_value") + ` ` + extractInstallShellFunction(t, "action_runner_url_uses_loopback_http") + ` ` + extractInstallShellFunction(t, "action_runner_url_transport_allowed") + ` ` + extractInstallShellFunction(t, "revoke_action_runner_credential") + ` revoke_action_runner_credential printf 'revoked\n' ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("runner self-revoke: %v\n%s", err, out) } if strings.Contains(string(out), token) { t.Fatalf("runner credential leaked in output: %s", out) } if gotMethod != http.MethodDelete { t.Fatalf("revoke method = %q, want DELETE", gotMethod) } if gotAuthorization != "Bearer "+token { t.Fatalf("authorization header mismatch") } if gotBody["agentId"] != "agent-secure-runtime" || gotBody["hostname"] != "secure-runtime.lab" { t.Fatalf("revoke body = %#v", gotBody) } } func TestInstallSHActionRunnerUninstallRetainsRecoveryMaterialWhenRevokeIsUnconfirmed(t *testing.T) { root := t.TempDir() paths := []string{ filepath.Join(root, "bin", "pulse-agent-runner"), filepath.Join(root, "systemd", "pulse-agent-runner.service"), filepath.Join(root, "config", "runner.env"), filepath.Join(root, "config", "token"), filepath.Join(root, "state", "health.json"), } for _, path := range paths { if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { t.Fatal(err) } mustWrite(t, path, "retained\n") } serviceLog := filepath.Join(root, "systemctl.log") script := ` set -u ACTION_RUNNER_NAME="pulse-agent-runner" ACTION_RUNNER_BINARY_PATH="` + paths[0] + `" ACTION_RUNNER_SERVICE_UNIT="` + paths[1] + `" ACTION_RUNNER_ENV_FILE="` + paths[2] + `" ACTION_RUNNER_TOKEN_FILE="` + paths[3] + `" ACTION_RUNNER_CONFIG_DIR="` + filepath.Dir(paths[3]) + `" ACTION_RUNNER_STATE_DIR="` + filepath.Dir(paths[4]) + `" EXIT_GENERAL=1 runner_stopped=false runner_disabled=false timeout() { [[ "$1 $2 $3 $4" == '-k 1 5 systemctl' ]] || return 90 shift 4 systemctl "$@" } systemctl() { printf '%s\n' "$*" >> "` + serviceLog + `" case "$1" in show) if [[ "$runner_stopped" == true ]]; then printf 'ActiveState=inactive\n' else printf 'ActiveState=active\n' fi if [[ "$runner_disabled" == true ]]; then printf 'UnitFileState=disabled\n' else printf 'UnitFileState=enabled\n' fi printf 'LoadState=loaded\n' ;; stop) runner_stopped=true ;; disable) runner_disabled=true ;; *) return 91 ;; esac } revoke_action_runner_credential() { return 1; } log_error() { printf 'ERROR: %s\n' "$1" >&2; } log_info() { printf 'INFO: %s\n' "$1"; } fail() { printf 'FAIL: %s\n' "$1" >&2; exit "$2"; } ` + extractInstallShellFunction(t, "read_agent_removal_unit_state") + ` ` + extractInstallShellFunction(t, "confirm_agent_unit_quiescent") + ` ` + extractInstallShellFunction(t, "quiesce_agent_units_for_removal") + ` ` + extractInstallShellFunction(t, "teardown_action_runner_service") + ` teardown_action_runner_service ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err == nil || !strings.Contains(string(out), "successful credential revocation") || !strings.Contains(string(out), "retained for a safe retry") { t.Fatalf("unconfirmed revoke did not fail closed: %v\n%s", err, out) } for _, path := range paths { if _, statErr := os.Stat(path); statErr != nil { t.Fatalf("recovery material %s was removed: %v", path, statErr) } } serviceCalls, readErr := os.ReadFile(serviceLog) if readErr != nil { t.Fatal(readErr) } if !strings.Contains(string(serviceCalls), "stop pulse-agent-runner.service") || !strings.Contains(string(serviceCalls), "disable pulse-agent-runner.service") { t.Fatalf("runner was not stopped and disabled before failed revoke: %s", serviceCalls) } } func TestInstallSHSafeProfileDurablyReducesCollectorAuthority(t *testing.T) { const token = "collector-secret-that-must-not-appear-in-output" var gotAuthorization string var gotMethod string var gotBody map[string]string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotAuthorization = r.Header.Get("Authorization") gotMethod = r.Method if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { t.Errorf("decode authority reduction body: %v", err) http.Error(w, "bad request", http.StatusBadRequest) return } w.WriteHeader(http.StatusNoContent) })) defer server.Close() stateDir := t.TempDir() tokenPath := filepath.Join(stateDir, "runtime.token") mustWrite(t, tokenPath, token+"\n") if err := os.Chmod(tokenPath, 0o640); err != nil { t.Fatal(err) } pulseAgent := buildPulseAgentLifecycleBinary(t) script := ` set -euo pipefail STATE_DIR="` + stateDir + `" RUNTIME_TOKEN_FILE="" COLLECTOR_LIFECYCLE_BINARY_PATH="` + pulseAgent + `" LEAST_PRIVILEGE_USER="$(id -un)" PULSE_TOKEN="" AGENT_ID="agent-secure-runtime" HOSTNAME_OVERRIDE="" PULSE_URL="` + server.URL + `" INSECURE="false" CURL_CA_BUNDLE="" SERVER_FINGERPRINT="" hostname() { printf 'Secure-Runtime.Lab.\n'; } log_info() { printf '%s\n' "$*"; } ` + extractCollectorLifecycleShellFunctions(t, false) + ` ` + extractInstallShellFunction(t, "resolve_safe_profile_hostname") + ` ` + extractInstallShellFunction(t, "reduce_safe_profile_collector_authority") + ` resolve_safe_profile_hostname reduce_safe_profile_collector_authority ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("collector authority reduction: %v\n%s", err, out) } if strings.Contains(string(out), token) { t.Fatalf("collector credential leaked in output: %s", out) } if gotMethod != http.MethodPost || gotAuthorization != "Bearer "+token { t.Fatalf("authority reduction transport: method=%q authorization=%q", gotMethod, gotAuthorization) } if gotBody["agentId"] != "agent-secure-runtime" || gotBody["hostname"] != "secure-runtime.lab" { t.Fatalf("authority reduction body = %#v", gotBody) } } func TestInstallSHCollectorLifecycleRejectsActiveMITMForgedSuccess(t *testing.T) { const token = "collector-mitm-secret" var authorizationSeen bool mitm := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Authorization") != "" { authorizationSeen = true } if r.URL.Path == "/api/agents/collector/reduce-authority" { w.WriteHeader(http.StatusNoContent) return } _, _ = w.Write([]byte(`{"success":true,"agent":{"id":"agent-secure-runtime","hostname":"secure-runtime.lab","lastSeen":"2026-08-30T12:00:02Z"}}`)) })) defer mitm.Close() stateDir := t.TempDir() tokenPath := filepath.Join(stateDir, "runtime.token") mustWrite(t, tokenPath, token) if err := os.Chmod(tokenPath, 0o640); err != nil { t.Fatal(err) } pulseAgent := buildPulseAgentLifecycleBinary(t) script := ` set -u STATE_DIR="` + stateDir + `" RUNTIME_TOKEN_FILE="" COLLECTOR_LIFECYCLE_BINARY_PATH="` + pulseAgent + `" LEAST_PRIVILEGE_USER="$(id -un)" PULSE_TOKEN="" AGENT_ID="agent-secure-runtime" HOSTNAME_OVERRIDE="secure-runtime.lab" PULSE_URL="` + mitm.URL + `" INSECURE="true" CURL_CA_BUNDLE="" SERVER_FINGERPRINT="` + strings.Repeat("00", 32) + `" log_info() { printf '%s\n' "$*"; } ` + extractCollectorLifecycleShellFunctions(t, true) + ` ` + extractInstallShellFunction(t, "reduce_safe_profile_collector_authority") + ` if reduce_safe_profile_collector_authority; then printf 'forged reduction accepted\n' exit 3 fi if verify_agent_server_registration "2026-08-30T12:00:01Z"; then printf 'forged registration accepted\n' exit 4 fi printf 'forged success rejected\n' ` out, err := exec.Command("bash", "-c", script).CombinedOutput() if err != nil { t.Fatalf("MITM rejection script: %v\n%s", err, out) } if !strings.Contains(string(out), "forged success rejected") { t.Fatalf("missing forged-success rejection: %s", out) } if strings.Contains(string(out), token) { t.Fatalf("collector bearer leaked in output: %s", out) } if authorizationSeen { t.Fatal("MITM handler received the collector Authorization header") } } func TestInstallSHRendersHardenedActionRunnerUnit(t *testing.T) { root := t.TempDir() unitPath := filepath.Join(root, "pulse-agent-runner.service") script := ` set -euo pipefail ACTION_RUNNER_ENV_FILE="/etc/pulse-agent-runner/runner.env" ACTION_RUNNER_STATE_DIR="/var/lib/pulse-agent-runner" ` + extractInstallShellFunction(t, "render_action_runner_service_unit") + ` render_action_runner_service_unit "` + unitPath + `" "/usr/local/lib/pulse-agent/pulse-agent-runner" ` if out, err := exec.Command("bash", "-c", script).CombinedOutput(); err != nil { t.Fatalf("render action runner unit: %v\n%s", err, out) } content, err := os.ReadFile(unitPath) if err != nil { t.Fatal(err) } unit := string(content) for _, required := range []string{ "ExecStart=/usr/local/lib/pulse-agent/pulse-agent-runner", "EnvironmentFile=/etc/pulse-agent-runner/runner.env", "User=root", "Group=root", "NoNewPrivileges=true", "ProtectHome=true", "ProtectSystem=false", "ProtectKernelTunables=true", "ProtectKernelModules=true", "ProtectControlGroups=true", "RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6", "ReadWritePaths=/var/lib/pulse-agent-runner", } { if !strings.Contains(unit, required) { t.Fatalf("action runner unit missing %q:\n%s", required, unit) } } if strings.Contains(unit, "PrivateNetwork=true") { t.Fatalf("networked action runner cannot use the helper's private-network sandbox:\n%s", unit) } if strings.Contains(unit, "ProtectSystem=strict") { t.Fatalf("host-mutating action runner cannot make the host filesystem read-only:\n%s", unit) } } func TestInstallSHTypedPrivilegedHelperProtectsCredentialsAfterStateChown(t *testing.T) { stateDir := t.TempDir() credentialDir := t.TempDir() legacyToken := filepath.Join(stateDir, "token") protectedToken := filepath.Join(credentialDir, "token") for _, path := range []string{legacyToken, protectedToken} { if err := os.WriteFile(path, []byte("secret"), 0600); err != nil { t.Fatal(err) } } callLog := filepath.Join(t.TempDir(), "calls") script := ` set -euo pipefail STATE_DIR="` + stateDir + `" PRIVILEGED_HELPER_CREDENTIAL_DIR="` + credentialDir + `" RUNTIME_TOKEN_FILE="` + protectedToken + `" LEAST_PRIVILEGE_USER="pulse-agent" EXIT_GENERAL=1 CALL_LOG="` + callLog + `" fail() { printf 'fail:%s\n' "$1" >> "$CALL_LOG"; return "$2"; } chown() { printf 'chown:%s:%s\n' "$1" "$2" >> "$CALL_LOG"; } chmod() { printf 'chmod:%s:%s\n' "$1" "$2" >> "$CALL_LOG"; } rm() { printf 'rm:%s\n' "$2" >> "$CALL_LOG"; } ` + extractInstallShellFunction(t, "protect_typed_profile_credentials") + ` protect_typed_profile_credentials ` if out, err := exec.Command("bash", "-c", script).CombinedOutput(); err != nil { t.Fatalf("protect typed-profile credentials: %v\n%s", err, out) } logBytes, err := os.ReadFile(callLog) if err != nil { t.Fatal(err) } log := string(logBytes) for _, want := range []string{ "chown:root:pulse-agent:" + credentialDir, "chmod:0750:" + credentialDir, "chown:root:pulse-agent:" + protectedToken, "chmod:0640:" + protectedToken, "rm:" + legacyToken, } { if !strings.Contains(log, want) { t.Fatalf("credential protection log missing %q:\n%s", want, log) } } } // macOS has no root group. Exercise both shared lifecycle writes with a // chown stub that accepts only portable numeric superuser ownership. func TestInstallSHLifecyclePortableRootOwnership(t *testing.T) { script := `set -euo pipefail LEAST_PRIVILEGE=false TMP_FILES=() id() { echo 0; } chown() { [[ "$1" == "0:0" ]]; } sync_lifecycle_path() { :; } ` + extractInstallShellFunction(t, "prepare_installer_lifecycle_dir") + "\n" + extractInstallShellFunction(t, "install_lifecycle_file_atomically") + ` prepare_installer_lifecycle_dir "$PWD/state" printf 'saved installer\n' > "$PWD/source" install_lifecycle_file_atomically "$PWD/source" "$PWD/state/install.sh" 700 cmp "$PWD/source" "$PWD/state/install.sh" # A failed ownership operation must still prevent replacement. chown() { return 1; } if prepare_installer_lifecycle_dir "$PWD/rejected"; then exit 10; fi printf 'replacement\n' > "$PWD/source" if install_lifecycle_file_atomically "$PWD/source" "$PWD/state/install.sh" 700; then exit 11; fi grep -qx 'saved installer' "$PWD/state/install.sh" ` cmd := exec.Command("bash", "-c", script) cmd.Dir = t.TempDir() if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("lifecycle ownership: %v\n%s", err, out) } } func agentIDRecoveryShell(t *testing.T, binaryPath, root, path string) string { t.Helper() return ` set -euo pipefail COLLECTOR_LIFECYCLE_BINARY_PATH="` + binaryPath + `" INSTALL_DIR="` + root + `" BINARY_NAME="absent-legacy-agent" LEAST_PRIVILEGE_USER="pulse-agent-test-missing" ` + extractLifecycleTrustShellFunctions(t) + ` ` + extractInstallShellFunction(t, "collector_lifecycle_binary") + ` ` + extractInstallShellFunction(t, "read_agent_id_file_safely") + ` read_agent_id_file_safely "` + path + `" ` } func TestInstallSHLegacyAgentIDRecoveryAccountsForEveryByte(t *testing.T) { root := t.TempDir() cases := []struct { name string body []byte want string }{ {"newline", []byte("agent-safe-123\n"), "agent-safe-123"}, {"no-newline", []byte("agent-safe-123"), "agent-safe-123"}, {"maximum", []byte(strings.Repeat("a", 128) + "\n"), strings.Repeat("a", 128)}, {"oversized-valid-prefix", []byte("agent-safe-123\n" + strings.Repeat("a", 5000)), ""}, {"second-identity", []byte("agent-safe-123\nother-agent\n"), ""}, {"nul-with-newline", []byte("agent-safe-123\x00\n"), ""}, {"nul-without-newline", []byte("agent-safe-123\x00"), ""}, {"carriage-return", []byte("agent-safe-123\r\n"), ""}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { path := filepath.Join(root, tc.name) if err := os.WriteFile(path, tc.body, 0600); err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() cmd := exec.CommandContext(ctx, "bash", "-c", agentIDRecoveryShell(t, "", root, path)) cmd.WaitDelay = time.Second out, err := cmd.CombinedOutput() if ctx.Err() != nil { t.Fatalf("legacy recovery deadline expired: %v exit=%v", ctx.Err(), err) } if tc.want != "" { if err != nil || string(out) != tc.want+"\n" { t.Fatalf("valid legacy identity rejected: %v output=%q", err, out) } } else if err == nil || len(out) != 0 { t.Fatalf("legacy recovery accepted or disclosed corrupt state: %v output=%q", err, out) } }) } } // Run the production Unix removal gate through private state/service/transport // doubles. Native platform installation and actual transport remain separate. func TestInstallSHUninstallRetainsCredentialsWhenSavedURLIsMissing(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() cmd := exec.CommandContext(ctx, "python3", repoFile("scripts", "installtests", "agent_uninstall_state.py"), "-v") output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("Unix removal state controls: %v\n%s", err, output) } for _, name := range []string{ "test_missing_url_retains_every_credential_representation_without_a_request", "test_server_refusal_unknown_or_other_identity_retains_service_and_state", "test_systemd_refusal_precedes_remote_removal_and_every_local_mutation", "test_exact_confirmation_precedes_process_service_and_state_teardown", "test_credential_free_local_only_removal_needs_no_url_or_request", "test_server_confirmation_does_not_grant_unknown_path_removal", "test_noninteractive_input_token_survives_missing_url_refusal", "test_noninteractive_input_token_survives_unconfirmed_removal", "test_confirmed_removal_retains_caller_owned_input_token", "test_confirmed_removal_deletes_runtime_token_only_with_authorised_state", "test_noninteractive_input_token_survives_root_refusal", "test_interactive_input_token_is_read_only_at_ingress", } { if !strings.Contains(string(output), name+" (__main__.UnixUninstallState."+name+") ... ok") { t.Fatalf("Unix removal control %s did not pass:\n%s", name, output) } } if !strings.Contains(string(output), "\nRan 12 tests in ") || !strings.Contains(string(output), "\nOK\n") { t.Fatalf("incomplete Unix removal state verdict:\n%s", output) } t.Logf("%s", output) } func TestInstallSHSystemdTeardownRequiresObservedQuiescence(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() cmd := exec.CommandContext(ctx, "python3", repoFile("scripts", "installtests", "agent_systemd_teardown.py")) output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("systemd teardown refusal controls: %v\n%s", err, output) } for _, name := range []string{ "test_unknown_read_stop_and_disable_retain_every_artifact", "test_unsettled_or_unknown_states_never_authorise_deletion", "test_helper_socket_and_service_are_reconciled_together", "test_known_quiescence_allows_only_owned_teardown", "test_failed_revocation_retains_stopped_runner_recovery_material", "test_full_batch_reconciles_earlier_activation_sources", "test_real_read_timeout_stops_its_owned_child", "test_timeout_probe_keeps_prepared_tool_and_refuses_missing_preparation", } { if !strings.Contains(string(output), name+" (__main__.AgentSystemdTeardown."+name+") ... ok") { t.Fatalf("systemd teardown control %s did not pass:\n%s", name, output) } } if !strings.Contains(string(output), "\nRan 8 tests in ") || !strings.Contains(string(output), "\nOK\n") { t.Fatalf("incomplete systemd teardown verdict:\n%s", output) } entry, err := os.ReadFile(repoFile("scripts", "install.sh")) if err != nil { t.Fatal(err) } uninstall := strings.SplitN(string(entry), "# --- Uninstall Logic ---", 2)[1] quiesce := strings.Index(uninstall, "quiesce_agent_units_for_removal") if quiesce < 0 || quiesce >= strings.Index(uninstall, "if ! uninstall_collector_registration;") || quiesce >= strings.Index(uninstall, "pkill -f") { t.Fatal("full removal must quiesce every affected systemd unit before remote removal or process mutation") } t.Logf("%s", output) } func TestInstallSHOpenRCTeardownRequiresObservedQuiescence(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("Unix shell/OpenRC controls; Windows has its own native SCM gate") } ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() cmd := exec.CommandContext(ctx, "python3", repoFile("scripts", "installtests", "agent_openrc_teardown.py")) output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("OpenRC teardown refusal controls: %v\n%s", err, output) } for _, name := range []string{ "test_stop_and_disable_refusals_retain_every_local_file", "test_failed_partial_or_diagnostic_observations_are_not_replaced_by_another_read", "test_independent_post_stop_and_runlevel_reads_are_mandatory", "test_refused_manager_cannot_start_remote_or_local_removal", "test_quiescence_precedes_server_process_and_state_mutation", "test_server_refusal_keeps_stopped_disabled_recovery_without_restart", "test_late_manager_refusal_keeps_recovery_after_confirmed_remote_removal", "test_known_stopped_state_and_every_runlevel_allow_only_selected_teardown", "test_absent_and_repeated_removal_need_manager_and_process_absence", "test_unexpected_service_paths_refuse_before_stop_or_deletion", "test_source_selection_and_caller_order_have_one_bounded_observation_per_operation", "test_real_status_timeout_stops_its_owned_child_without_erasure", } { if !strings.Contains(string(output), name+" (__main__.AgentOpenRCTeardown."+name+") ... ok") { t.Fatalf("OpenRC teardown control %s did not pass:\n%s", name, output) } } if !strings.Contains(string(output), "\nRan 12 tests in ") || !strings.Contains(string(output), "\nOK\n") { t.Fatalf("incomplete OpenRC teardown verdict:\n%s", output) } t.Logf("%s", output) } func TestInstallSHLegacyTeardownRequiresObservedQuiescence(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("Unix rc.d/SysV controls; Windows has its own native SCM gate") } ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() cmd := exec.CommandContext(ctx, "python3", repoFile("scripts", "installtests", "agent_legacy_teardown.py")) output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("legacy manager removal controls: %v\n%s", err, output) } for _, name := range []string{ "test_stop_refusal_retains_all_local_recovery", "test_manager_refusal_precedes_remote_and_state_erasure", "test_ignored_stop_disable_and_reactivation_are_not_success", "test_failed_empty_diagnostic_or_duplicate_reads_are_not_absence", "test_enablement_refusals_and_unknown_values_retain_recovery", "test_missing_required_tools_refuse_without_losing_recovery", "test_final_process_read_and_orphan_supervisor_are_required", "test_pid_ownership_and_unexpected_paths_refuse_before_signals", "test_every_owned_runlevel_link_and_both_layouts_are_removed", "test_foreign_malformed_or_unobservable_runlevels_are_not_deleted", "test_confirmed_manager_then_server_then_recheck_then_erasure", "test_remote_refusal_keeps_stopped_install_and_all_recovery_files", "test_late_refusal_keeps_local_recovery_after_remote_confirmation", "test_known_stopped_missing_and_repeated_removal_are_safe", "test_legacy_script_pid_erasure_cannot_authorise_credential_erasure", "test_process_inventory_does_not_return_arguments_or_secrets", "test_real_process_timeout_stops_owned_child_without_erasure", "test_rendered_stop_retains_pidfiles_on_refused_or_ignored_signals", } { if !strings.Contains(string(output), name+" (__main__.AgentLegacyTeardown."+name+") ... ok") { t.Fatalf("legacy manager control %s did not pass:\n%s", name, output) } } if !strings.Contains(string(output), "\nRan 18 tests in ") || !strings.Contains(string(output), "\nOK\n") { t.Fatalf("incomplete legacy manager verdict:\n%s", output) } t.Logf("%s", output) } func TestInstallSHUpstartTeardownRequiresObservedQuiescence(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("DSM Upstart shell controls; Windows has its own native SCM gate") } ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() cmd := exec.CommandContext(ctx, "python3", repoFile("scripts", "installtests", "agent_upstart_teardown.py")) output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("DSM Upstart retirement and production EXIT cleanup controls: %v\n%s", err, output) } for _, name := range []string{ "test_link_fixture_preserves_exclusive_no_target_directory_semantics", "test_link_fixture_exposes_the_old_directory_restore_race", "test_refused_manager_precedes_remote_and_state_erasure", "test_failed_empty_partial_diagnostic_status_cannot_authorise_removal", "test_effective_disabled_config_is_required_after_override_reload", "test_independent_process_inventory_rejects_orphan_or_unavailable_read", "test_untrusted_config_override_and_prior_retirement_refuse_without_changes", "test_concurrent_override_file_or_directory_is_not_replaced_or_written_inside", "test_missing_tools_refuse_without_remote_or_erasure", "test_remote_refusal_keeps_stopped_disabled_recovery_without_restart", "test_late_manager_refusal_retains_recovery_after_remote_confirmation", "test_unconfirmed_job_retirement_restores_exact_config_and_retains_manual", "test_concurrent_config_is_not_clobbered_and_original_staged_recovery_is_retained", "test_confirmed_job_absence_precedes_selected_local_erasure", "test_absent_stopped_and_repeated_removal_require_observed_manager_absence", "test_source_selection_keeps_upstart_out_of_sysv_and_before_erasure", "test_real_status_timeout_stops_owned_child_without_erasure", } { if !strings.Contains(string(output), name+" (__main__.AgentUpstartTeardown."+name+") ... ok") { t.Fatalf("DSM Upstart control %s did not pass:\n%s", name, output) } } if !strings.Contains(string(output), "\nRan 17 tests in ") || !strings.Contains(string(output), "\nOK\n") { t.Fatalf("incomplete DSM Upstart verdict:\n%s", output) } t.Logf("%s", output) } func TestInstallSHTrueNASScaleBootTasksRetireBeforeRecoveryErasure(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("TrueNAS SCALE shell controls; Windows has its own native SCM gate") } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() cmd := exec.CommandContext(ctx, "python3", repoFile("scripts", "installtests", "agent_truenas_teardown.py")) output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("TrueNAS SCALE boot-task ownership/retirement controls: %v\n%s", err, output) } for _, name := range []string{ "test_unavailable_or_untrusted_task_inventory_cannot_remove_recovery", "test_foreign_or_indirect_bootstrap_is_not_mutated", "test_missing_activation_target_cannot_hide_an_owned_task", "test_bootstrap_symlink_directory_or_writable_file_refuses_before_mutation", "test_missing_tools_are_not_local_only_removal_permission", "test_every_matching_task_is_disabled_and_independently_observed_before_stop", "test_partial_refused_ignored_or_reactivated_disable_retains_recovery", "test_unit_or_process_uncertainty_keeps_stopped_task_recovery", "test_task_activation_during_systemd_stop_refuses_before_remote", "test_server_refusal_retains_disabled_tasks_and_all_recovery_without_restart", "test_changed_or_refused_post_remote_inventory_precedes_first_deletion", "test_partial_refused_ignored_or_recreated_task_deletion_keeps_recovery", "test_all_owned_tasks_retire_before_state_erasure_and_unrelated_task_survives", "test_disabled_absent_missing_bootstrap_and_repeated_removal_are_observed", "test_ordinary_linux_state_is_not_appliance_evidence", "test_process_read_exposes_only_roles_and_real_timeout_child_is_stopped", } { if !strings.Contains(string(output), name+" (__main__.AgentTrueNASTeardown."+name+") ... ok") { t.Fatalf("TrueNAS SCALE control %s did not pass:\n%s", name, output) } } if !strings.Contains(string(output), "\nRan 16 tests in ") || !strings.Contains(string(output), "\nOK\n") { t.Fatalf("incomplete TrueNAS SCALE verdict:\n%s", output) } t.Logf("%s", output) } func TestInstallSHNASBootOwnersQuiesceBeforeRecoveryErasure(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("Unix NAS shell controls; Windows retains its separate native SCM gate") } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() cmd := exec.CommandContext(ctx, "python3", repoFile("scripts", "installtests", "agent_nas_teardown.py")) output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("NAS boot-owner/watchdog production caller controls: %v\n%s", err, output) } for _, name := range []string{ "test_complete_process_evidence_is_required_before_boot_or_remote_mutation", "test_unknown_relative_foreign_or_deferred_boot_owners_are_not_signalled", "test_non_regular_foreign_wrapper_and_unselected_state_preserve_recovery", "test_only_complete_owned_boot_entries_are_admitted", "test_refused_edit_copy_or_prior_recovery_cannot_erase_credentials", "test_concurrent_boot_file_directory_and_symlink_are_not_clobbered", "test_flash_mount_and_unmount_failures_are_not_local_only_permission", "test_watchdog_stops_before_collector_and_ignored_or_refused_signals_retain_state", "test_all_owned_entries_disable_before_signalling_and_remote_erasure", "test_remote_refusal_keeps_disabled_boot_and_exact_local_recovery", "test_reactivation_before_remote_refuses_without_erasure", "test_late_boot_or_process_activation_retains_recovery_after_remote", "test_absent_missing_and_repeated_removal_require_fresh_quiescence", "test_process_observation_returns_roles_not_arguments_and_stops_timeout_child", "test_recovered_ordinary_linux_state_is_not_qnap_platform_authority", "test_live_unraid_boot_shell_is_unknown_not_a_signal_target", "test_actual_generated_watchdogs_and_children_become_quiescent", "test_private_process_reader_requests_native_basename_and_full_argv", "test_generated_collector_observation_keeps_exact_identity_on_both_unix_formats", "test_caller_has_no_blanket_nas_or_legacy_tree_deletion", } { if !strings.Contains(string(output), name+" (__main__.AgentNASTeardown."+name+") ... ok") { t.Fatalf("NAS removal control %s did not pass:\n%s", name, output) } } if !strings.Contains(string(output), "\nRan 20 tests in ") || !strings.Contains(string(output), "\nOK\n") { t.Fatalf("incomplete NAS removal verdict:\n%s", output) } t.Logf("%s", output) }