Pulse/scripts/installtests/root_install_sh_test.go
2026-04-10 10:53:17 +01:00

277 lines
11 KiB
Go

package installtests
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestRootInstallScriptVersionFlagRequiresValue(t *testing.T) {
scriptPath := filepath.Join("..", "..", "install.sh")
cmd := exec.Command("bash", scriptPath, "--version")
out, err := cmd.CombinedOutput()
if err == nil {
t.Fatal("expected install.sh --version without value to fail")
}
got := string(out)
if !strings.Contains(got, "Missing value for --version") {
t.Fatalf("expected friendly missing-value error, got:\n%s", got)
}
if strings.Contains(got, "unbound variable") {
t.Fatalf("expected guarded parser error, got shell failure:\n%s", got)
}
}
func TestRootInstallScriptArchiveFlagRequiresValue(t *testing.T) {
scriptPath := filepath.Join("..", "..", "install.sh")
cmd := exec.Command("bash", scriptPath, "--archive")
out, err := cmd.CombinedOutput()
if err == nil {
t.Fatal("expected install.sh --archive without value to fail")
}
got := string(out)
if !strings.Contains(got, "--archive requires a local .tar.gz path") {
t.Fatalf("expected friendly archive missing-value error, got:\n%s", got)
}
if strings.Contains(got, "unbound variable") {
t.Fatalf("expected guarded parser error, got shell failure:\n%s", got)
}
}
func TestRootInstallScriptArchiveCannotBeUsedWithSource(t *testing.T) {
scriptPath := filepath.Join("..", "..", "install.sh")
cmd := exec.Command("bash", scriptPath, "--source", "--archive", "/tmp/pulse-v6.0.0-linux-amd64.tar.gz")
out, err := cmd.CombinedOutput()
if err == nil {
t.Fatal("expected install.sh --source --archive to fail")
}
got := string(out)
if !strings.Contains(got, "--archive cannot be used with --source") {
t.Fatalf("expected archive/source conflict error, got:\n%s", got)
}
}
func TestRootInstallScriptArchiveSupportContract(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)
required := []string{
`ARCHIVE_OVERRIDE="${PULSE_ARCHIVE_PATH:-}"`,
`--archive PATH`,
`resolve_archive_override()`,
`infer_release_from_archive_name()`,
`validate_pulse_binary_architecture()`,
`prefetch_pulse_archive_for_container()`,
`download_release_archive()`,
`install_pulse_archive()`,
`Archive version $inferred_release does not match requested version $FORCE_VERSION`,
}
for _, needle := range required {
if !strings.Contains(script, needle) {
t.Fatalf("install.sh missing archive support contract: %s", needle)
}
}
}
func TestRootInstallScriptAutoRegisterUsesSecureContractShape(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)
required := []string{
`str(data.get("setupToken", ""))`,
`str(data.get("tokenHint", ""))`,
`str(data.get("type", ""))`,
`str(data.get("host", ""))`,
`str(data.get("url", ""))`,
`str(data.get("downloadURL", ""))`,
`str(data.get("scriptFileName", ""))`,
`str(data.get("command", ""))`,
`str(data.get("commandWithEnv", ""))`,
`str(data.get("commandWithoutEnv", ""))`,
`expires_raw = data.get("expires", "")`,
`str(expires_raw)`,
`expiry_state = "live"`,
`expires_int > int(time.time())`,
`expected_setup_url = f"{pulse_url}/api/setup-script?host={quote(host, safe='')}&pulse_url={quote(pulse_url, safe='')}&type=pve"`,
`expected_download_url = f"{pulse_url}/api/setup-script?host={quote(host, safe='')}&pulse_url={quote(pulse_url, safe='')}&setup_token={quote(setup_token, safe='')}&type=pve"`,
`expected_script_name = "pulse-setup-pve.sh"`,
`setup_url != expected_setup_url`,
`setup_download_url != expected_download_url`,
`setup_script_name != expected_script_name`,
`not setup_command`,
`not setup_command_with_env`,
`not setup_command_without_env`,
`command_fields = (`,
`if not _value or expected_setup_url not in _value:`,
`'if [ "$(id -u)" -eq 0 ]; then' not in _value`,
`'elif command -v sudo >/dev/null 2>&1; then' not in _value`,
`if "PULSE_SETUP_TOKEN=" not in _value or setup_token not in _value:`,
`elif "PULSE_SETUP_TOKEN=" in _value or setup_token in _value:`,
`not token_hint or token_hint == setup_token`,
`[[ "$setup_type" != "pve" ]]`,
`[[ "$setup_host" != "$normalized_host_url" ]]`,
`[[ "$setup_url" != "$expected_setup_url" ]]`,
`[[ "$setup_download_url" != "$expected_download_url" ]]`,
`[[ "$setup_script_name" != "$expected_script_name" ]]`,
`[[ -z "$setup_command" ]]`,
`[[ -z "$setup_command_with_env" ]]`,
`[[ -z "$setup_command_without_env" ]]`,
`[[ -z "$setup_token_hint" ]]`,
`[[ "$setup_expiry_state" != "live" ]]`,
`host, token_id, token_value, server_name, setup_token = sys.argv[1:]`,
`"tokenId": token_id`,
`"tokenValue": token_value`,
`"authToken": setup_token`,
`"source": "script"`,
`data.get("action", "")`,
`data.get("type", "")`,
`data.get("source", "")`,
`data.get("host", "")`,
`data.get("tokenId", "")`,
`data.get("tokenValue", "")`,
`data.get("nodeId", "")`,
`data.get("nodeName", "")`,
`[[ "$register_status" != "success" ]] || [[ "$register_action" != "use_token" ]] || [[ "$register_type" != "pve" ]] || [[ "$register_source" != "script" ]]`,
`AUTO_NODE_REGISTERED_NAME="$register_node_name"`,
`curl --retry 3 --retry-delay 2 -fsS -X POST "$pulse_url/api/setup-script-url" -H "Content-Type: application/json" -d "$setup_payload"`,
`curl --retry 3 --retry-delay 2 -fsS -X POST "$pulse_url/api/auto-register" -H "Content-Type: application/json" -d "$register_payload"`,
`slug = re.sub(r"[^a-z0-9]+", "-", host)`,
`print(f"pulse-{slug}")`,
}
for _, needle := range required {
if !strings.Contains(script, needle) {
t.Fatalf("root install.sh missing secure installer auto-register contract fragment: %s", needle)
}
}
if strings.Contains(script, `local token_name="pulse-${pulse_host_slug}-$(date +%s)"`) {
t.Fatalf("root install.sh preserved stale timestamp-suffixed Proxmox token naming")
}
forbidden := []string{
`local bootstrap_token=""`,
`X-Setup-Token: $bootstrap_token`,
`Discovered bootstrap token from container`,
}
for _, needle := range forbidden {
if strings.Contains(script, needle) {
t.Fatalf("root install.sh preserved stale setup-script-url bootstrap auth fragment: %s", needle)
}
}
}
func TestPrereleaseUpdateCopyUsesPreviewFraming(t *testing.T) {
rootInstall, err := os.ReadFile(filepath.Join("..", "..", "install.sh"))
if err != nil {
t.Fatalf("read root install.sh: %v", err)
}
installScript := string(rootInstall)
requiredInstall := []string{
`Update to $RC_VERSION (prerelease preview)`,
`--rc, --pre Install latest prerelease preview version`,
`Prerelease channel detected in configuration`,
`Prerelease channel: get latest release (including prereleases, but skip drafts)`,
}
for _, needle := range requiredInstall {
if !strings.Contains(installScript, needle) {
t.Fatalf("root install.sh missing prerelease framing fragment: %s", needle)
}
}
forbiddenInstall := []string{
`Update to $RC_VERSION (release candidate)`,
`--rc, --pre Install latest RC/pre-release version`,
`RC channel detected in configuration`,
`RC channel: Get latest release (including pre-releases, but skip drafts)`,
}
for _, needle := range forbiddenInstall {
if strings.Contains(installScript, needle) {
t.Fatalf("root install.sh preserved stale release-candidate framing fragment: %s", needle)
}
}
autoUpdate, err := os.ReadFile(filepath.Join("..", "..", "scripts", "pulse-auto-update.sh"))
if err != nil {
t.Fatalf("read pulse-auto-update.sh: %v", err)
}
autoUpdateScript := string(autoUpdate)
if !strings.Contains(autoUpdateScript, `Prerelease channel detected; unattended auto-updates run only on stable`) {
t.Fatalf("pulse-auto-update.sh missing prerelease channel log message")
}
if strings.Contains(autoUpdateScript, `RC channel detected; unattended auto-updates run only on stable`) {
t.Fatalf("pulse-auto-update.sh preserved stale release-candidate channel log message")
}
}
func TestRootInstallScriptSupportsInstanceScopedServerInstalls(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)
required := []string{
`SERVICE_NAME_EXPLICIT="false"`,
`SERVICE_NAME="${PULSE_SERVICE_NAME:-$DEFAULT_SERVICE_NAME}"`,
`INSTALL_DIR="${PULSE_INSTALL_DIR:-$(default_install_dir_for_service "$SERVICE_NAME")}"`,
`CONFIG_DIR="${PULSE_CONFIG_DIR:-$(default_config_dir_for_service "$SERVICE_NAME")}"`,
`BINARY_LINK_PATH="${PULSE_BINARY_LINK_PATH:-$(default_binary_link_path_for_service "$SERVICE_NAME")}"`,
`UPDATE_HELPER_PATH="${PULSE_UPDATE_HELPER_PATH:-$(default_update_helper_path_for_service "$SERVICE_NAME")}"`,
`AUTO_UPDATE_DEST="${PULSE_AUTO_UPDATE_DEST:-$(default_auto_update_dest_for_service "$SERVICE_NAME")}"`,
`UPDATE_SERVICE_PATH="${PULSE_UPDATE_SERVICE_PATH:-$(default_update_service_path_for_service "$SERVICE_NAME")}"`,
`UPDATE_TIMER_PATH="${PULSE_UPDATE_TIMER_PATH:-$(default_update_timer_path_for_service "$SERVICE_NAME")}"`,
`if [[ "$SERVICE_NAME_EXPLICIT" == "true" ]]; then`,
`mkdir -p "$(dirname "$BINARY_LINK_PATH")"`,
`ln -sf "$INSTALL_DIR/bin/pulse" "$BINARY_LINK_PATH"`,
`safe_systemctl enable "$update_timer_unit" || true`,
`safe_systemctl start "$update_timer_unit" || true`,
`Environment="PULSE_SERVICE_NAME=$service_name"`,
`Environment="PULSE_INSTALL_DIR=$install_dir"`,
`Environment="PULSE_CONFIG_DIR=$config_dir"`,
`Environment="PULSE_UPDATE_TIMER_UNIT=$update_timer_unit"`,
`printf ' env'`,
`env_vars+=("PULSE_SERVICE_NAME=$SERVICE_NAME")`,
}
for _, needle := range required {
if !strings.Contains(script, needle) {
t.Fatalf("root install.sh missing instance-scoped install contract fragment: %s", needle)
}
}
}
func TestPulseAutoUpdateScriptSupportsInstanceScopedServerInstalls(t *testing.T) {
content, err := os.ReadFile(filepath.Join("..", "..", "scripts", "pulse-auto-update.sh"))
if err != nil {
t.Fatalf("read pulse-auto-update.sh: %v", err)
}
script := string(content)
required := []string{
`SERVICE_NAME="${PULSE_SERVICE_NAME:-pulse}"`,
`INSTALL_DIR="${PULSE_INSTALL_DIR:-/opt/pulse}"`,
`CONFIG_DIR="${PULSE_CONFIG_DIR:-/etc/pulse}"`,
`UPDATE_TIMER_UNIT="${PULSE_UPDATE_TIMER_UNIT:-${SERVICE_NAME}-update.timer}"`,
`if [[ -n "${PULSE_SERVICE_NAME:-}" ]]; then`,
`"PULSE_SERVICE_NAME=$service_name"`,
`"PULSE_INSTALL_DIR=$INSTALL_DIR"`,
`"PULSE_CONFIG_DIR=$CONFIG_DIR"`,
`systemctl is-enabled --quiet "$UPDATE_TIMER_UNIT"`,
}
for _, needle := range required {
if !strings.Contains(script, needle) {
t.Fatalf("pulse-auto-update.sh missing instance-scoped install contract fragment: %s", needle)
}
}
}