Prepare v6.0.0 release candidate

Tighten v5-to-v6 upgrade safety, release installability, provider MSP mode handling, AI cost accounting, metrics flushing, and frontend guardrails for the v6.0.0 GA candidate.
This commit is contained in:
rcourtman 2026-06-04 14:07:14 +01:00
parent a73a24259c
commit bd6f77e093
119 changed files with 4753 additions and 2046 deletions

View file

@ -515,6 +515,33 @@ func TestDockerAndDemoBuildsUseCanonicalReleaseLdflags(t *testing.T) {
}
}
func TestAgentRuntimeImagePersistsAgentIdentityByDefault(t *testing.T) {
dockerfileBytes, err := os.ReadFile(repoFile("Dockerfile"))
if err != nil {
t.Fatalf("read Dockerfile: %v", err)
}
dockerfile := string(dockerfileBytes)
required := []string{
`mkdir -p /var/lib/pulse-agent`,
`PULSE_DISABLE_AUTO_UPDATE=true`,
`PULSE_ENABLE_HOST=false`,
`PULSE_ENABLE_DOCKER=true`,
`PULSE_AGENT_ID_FILE=/var/lib/pulse-agent/agent-id`,
`PULSE_STATE_DIR=/var/lib/pulse-agent`,
`VOLUME ["/var/lib/pulse-agent"]`,
`ENTRYPOINT ["/usr/local/bin/pulse-agent"]`,
}
for _, needle := range required {
if !strings.Contains(dockerfile, needle) {
t.Fatalf("Dockerfile agent_runtime missing persistent identity contract: %s", needle)
}
}
if strings.Contains(dockerfile, `ENTRYPOINT ["/usr/local/bin/pulse-agent", "--enable-docker", "--enable-host=false"]`) {
t.Fatal("agent_runtime must not hard-code module flags in ENTRYPOINT; env defaults keep user args overridable")
}
}
func TestReleaseWorkflowsUseSecretSafeAttestedImageBuilds(t *testing.T) {
createReleaseBytes, err := os.ReadFile(repoFile(".github", "workflows", "create-release.yml"))
if err != nil {

View file

@ -68,6 +68,28 @@ func TestInstallPS1AllowsMissingTokenForOptionalAuth(t *testing.T) {
}
}
func TestInstallPS1AgentDownloadIsServerVersionAware(t *testing.T) {
content, err := os.ReadFile(repoFile("scripts", "install.ps1"))
if err != nil {
t.Fatalf("read install.ps1: %v", err)
}
script := string(content)
required := []string{
`Invoke-WebRequest -Uri "$Url/api/version"`,
`$versionInfo = $versionResponse.Content | ConvertFrom-Json`,
`$ServerVersion = [string]$versionInfo.version`,
`$escapedServerVersion = [Uri]::EscapeDataString($ServerVersion)`,
`$DownloadUrl = "$DownloadUrl&serverVersion=$escapedServerVersion"`,
`downloaded agent version ($DownloadedVersion) does not match Pulse server version ($ServerVersion)`,
}
for _, needle := range required {
if !strings.Contains(script, needle) {
t.Fatalf("install.ps1 missing version-aware agent download behavior: %s", needle)
}
}
}
func TestInstallPS1AllowsOptionalAuthUninstallWithoutToken(t *testing.T) {
content, err := os.ReadFile(repoFile("scripts", "install.ps1"))
if err != nil {

View file

@ -3,6 +3,7 @@ package installtests
import (
"encoding/json"
"io"
"net"
"net/http"
"net/http/httptest"
"os"
@ -70,6 +71,51 @@ func TestInstallSHAutoDetectProxmoxKeepsRuntimeTypeUnpinned(t *testing.T) {
}
}
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"; shift ;;`,
`--enable-host=false) ENABLE_HOST="false"; 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}"`,
`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)
}
}
}
func TestInstallSHAgentServiceSecurityDefaults(t *testing.T) {
content, err := os.ReadFile(repoFile("scripts", "install.sh"))
if err != nil {
@ -706,6 +752,107 @@ func TestInstallSHUsesSharedServiceRenderers(t *testing.T) {
}
}
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=""`,
`discover_rootless_container_runtime() {`,
`discover_single_socket_match "/run/user/*/docker.sock"`,
`discover_single_socket_match "/run/user/*/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"`,
`env_line="$SYSTEMD_ENV_LINES"`,
`local service_env_lines="$SHELL_EXPORT_LINES"`,
`</array>${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 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"
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"`,
`<key>DOCKER_HOST</key>`,
`<string>unix:///run/user/1000/docker.sock</string>`,
}
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)
}
}
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 {
@ -2236,6 +2383,8 @@ esac
` + extractAutoUpdateFunction(t, "require_release_signature_verifier") + `
` + extractAutoUpdateFunction(t, "verify_release_signature") + `
` + extractAutoUpdateFunction(t, "resolve_install_script_url") + `
` + extractAutoUpdateFunction(t, "is_prerelease_tag") + `
wait_for_service_active() { return 0; }
` + extractAutoUpdateFunction(t, "perform_update") + `
perform_update v9.9.9
`

View file

@ -0,0 +1,119 @@
package installtests
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestUninstallSensorProxyScriptContract(t *testing.T) {
scriptPath := repoFile("scripts", "uninstall-sensor-proxy.sh")
content, err := os.ReadFile(scriptPath)
if err != nil {
t.Fatalf("read uninstall-sensor-proxy.sh: %v", err)
}
script := string(content)
required := []string{
`--remove-proxmox-access`,
`pulse-sensor-proxy-selfheal.timer`,
`pulse-sensor-cleanup.path`,
`remove_managed_keys_from_authorized_keys_file()`,
`cleanup_stale_sensor_proxy_mounts()`,
`pulse-monitor@pam`,
`# pulse-(managed|proxy)-key$`,
}
for _, needle := range required {
if !strings.Contains(script, needle) {
t.Fatalf("uninstall-sensor-proxy.sh missing cleanup contract: %s", needle)
}
}
if out, err := exec.Command("bash", "-n", scriptPath).CombinedOutput(); err != nil {
t.Fatalf("bash -n uninstall-sensor-proxy.sh: %v\n%s", err, out)
}
}
func TestUninstallSensorProxyScriptRemovesTempFootprintAndManagedKeys(t *testing.T) {
tmpDir := t.TempDir()
binDir := filepath.Join(tmpDir, "bin")
if err := os.MkdirAll(binDir, 0755); err != nil {
t.Fatalf("mkdir bin dir: %v", err)
}
if err := os.WriteFile(filepath.Join(binDir, "systemctl"), []byte("#!/bin/sh\nexit 0\n"), 0755); err != nil {
t.Fatalf("write systemctl stub: %v", err)
}
if err := os.WriteFile(filepath.Join(binDir, "pct"), []byte("#!/bin/sh\nexit 0\n"), 0755); err != nil {
t.Fatalf("write pct stub: %v", err)
}
binaryPath := filepath.Join(tmpDir, "pulse-sensor-proxy")
installRoot := filepath.Join(tmpDir, "install-root")
servicePath := filepath.Join(tmpDir, "pulse-sensor-proxy.service")
runtimeDir := filepath.Join(tmpDir, "run")
socketPath := filepath.Join(runtimeDir, "pulse-sensor-proxy.sock")
workDir := filepath.Join(tmpDir, "work")
configDir := filepath.Join(tmpDir, "config")
logDir := filepath.Join(tmpDir, "logs")
authKeys := filepath.Join(tmpDir, "authorized_keys")
for _, dir := range []string{installRoot, runtimeDir, workDir, configDir, logDir} {
if err := os.MkdirAll(dir, 0755); err != nil {
t.Fatalf("mkdir %s: %v", dir, err)
}
}
for _, path := range []string{binaryPath, servicePath, socketPath, filepath.Join(installRoot, "bin", "pulse-sensor-proxy")} {
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
t.Fatalf("mkdir parent for %s: %v", path, err)
}
if err := os.WriteFile(path, []byte("legacy"), 0644); err != nil {
t.Fatalf("write %s: %v", path, err)
}
}
if err := os.WriteFile(authKeys, []byte(strings.Join([]string{
"ssh-ed25519 AAAAlegacy1 pulse # pulse-managed-key",
"ssh-ed25519 AAAAkeep user-key",
"ssh-ed25519 AAAAlegacy2 pulse # pulse-proxy-key",
"",
}, "\n")), 0600); err != nil {
t.Fatalf("write authorized_keys: %v", err)
}
cmd := exec.Command("bash", repoFile("scripts", "uninstall-sensor-proxy.sh"), "--uninstall", "--purge", "--quiet")
cmd.Env = append(os.Environ(),
"PATH="+binDir+string(os.PathListSeparator)+os.Getenv("PATH"),
"PULSE_SENSOR_PROXY_BINARY_PATH="+binaryPath,
"PULSE_SENSOR_PROXY_INSTALL_ROOT="+installRoot,
"PULSE_SENSOR_PROXY_SERVICE_PATH="+servicePath,
"PULSE_SENSOR_PROXY_RUNTIME_DIR="+runtimeDir,
"PULSE_SENSOR_PROXY_SOCKET_PATH="+socketPath,
"PULSE_SENSOR_PROXY_WORK_DIR="+workDir,
"PULSE_SENSOR_PROXY_CONFIG_DIR="+configDir,
"PULSE_SENSOR_PROXY_LOG_DIR="+logDir,
"PULSE_SENSOR_PROXY_SERVICE_USER=pulse-sensor-proxy-test-user",
"PULSE_SENSOR_PROXY_AUTHORIZED_KEYS_PATH="+authKeys,
)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("uninstall-sensor-proxy.sh failed: %v\n%s", err, out)
}
for _, path := range []string{binaryPath, installRoot, servicePath, runtimeDir, workDir, configDir, logDir} {
if _, err := os.Stat(path); !os.IsNotExist(err) {
t.Fatalf("expected %s to be removed, stat err=%v", path, err)
}
}
content, err := os.ReadFile(authKeys)
if err != nil {
t.Fatalf("read authorized_keys: %v", err)
}
got := string(content)
if strings.Contains(got, "pulse-managed-key") || strings.Contains(got, "pulse-proxy-key") {
t.Fatalf("managed Pulse SSH keys were not removed:\n%s", got)
}
if !strings.Contains(got, "AAAAkeep") {
t.Fatalf("non-Pulse SSH key was not preserved:\n%s", got)
}
}