diff --git a/scripts/installtests/install_sh_test.go b/scripts/installtests/install_sh_test.go index b460c4424..e6de37d6f 100644 --- a/scripts/installtests/install_sh_test.go +++ b/scripts/installtests/install_sh_test.go @@ -132,6 +132,9 @@ func TestInstallSHAgentDownloadIsServerVersionAware(t *testing.T) { `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 { @@ -141,6 +144,41 @@ func TestInstallSHAgentDownloadIsServerVersionAware(t *testing.T) { } } +// 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 { diff --git a/scripts/installtests/root_install_sh_test.go b/scripts/installtests/root_install_sh_test.go index 5c0ed51fd..83fb199b3 100644 --- a/scripts/installtests/root_install_sh_test.go +++ b/scripts/installtests/root_install_sh_test.go @@ -1242,3 +1242,40 @@ func TestRootInstallScriptUpdateFlowsRefreshExistingAutoUpdateAssets(t *testing. t.Fatalf("expected at least 5 install flows to refresh existing auto-update assets, found %d", got) } } + +// Regression test for #1526 (and the earlier #1396): when the installer is piped +// to bash (curl ... | bash) there is no source file, so BASH_SOURCE is unset. +// The "am I being sourced?" guard must default the lookup or `set -u` aborts the +// whole run before the installer body with "BASH_SOURCE[0]: unbound variable". +func TestRootInstallScriptSourceGuardSurvivesPipedExecution(t *testing.T) { + content, err := os.ReadFile(filepath.Join("..", "..", "install.sh")) + if err != nil { + t.Fatalf("read root install.sh: %v", err) + } + script := string(content) + + if strings.Contains(script, `[[ "${BASH_SOURCE[0]}" == "$0" ]] || return 0`) { + t.Fatal("install.sh still uses the unguarded BASH_SOURCE source check that aborts under curl | bash") + } + guard := `if [[ -n "${BASH_SOURCE[0]:-}" && "${BASH_SOURCE[0]}" != "$0" ]]; then` + if !strings.Contains(script, guard) { + t.Fatalf("install.sh missing piped-safe source guard: %s", guard) + } + + // Run the guard the way `curl ... | bash` does: fed through stdin with no + // source file, so BASH_SOURCE is empty. It must fall through to the body. + harness := "set -euo pipefail\n" + guard + "\n return 0\nfi\necho INSTALLER_BODY_REACHED\n" + cmd := exec.Command("bash") + cmd.Stdin = strings.NewReader(harness) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("piped source guard failed: %v\n%s", err, out) + } + got := string(out) + if strings.Contains(got, "unbound variable") { + t.Fatalf("source guard aborted piped execution with unbound variable:\n%s", got) + } + if !strings.Contains(got, "INSTALLER_BODY_REACHED") { + t.Fatalf("source guard did not fall through to the installer body when piped:\n%s", got) + } +}