From 2a1bf283942ddd6b890d2a36fa1960530693fea1 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Mon, 3 Aug 2026 09:47:03 +0100 Subject: [PATCH] Create the config directory before enabling auto-updates Reinstalling after removing /etc/pulse reaches setup_auto_updates before setup_directories has recreated the config directory. The system.json write then failed with "No such file or directory" while the run still printed that automatic updates were enabled, so the installer reported a state it had not reached. mkdir -p the config directory first, and fall back to disabling auto-updates when it cannot be created. Contract-Neutral: installer behavioural fix, no contract delta Refs #1663 --- install.sh | 10 +++++ scripts/installtests/root_install_sh_test.go | 45 ++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/install.sh b/install.sh index 21b723ae7..10ec9e694 100755 --- a/install.sh +++ b/install.sh @@ -4420,6 +4420,16 @@ setup_auto_updates() { # Enable timer but don't start it yet safe_systemctl enable "$update_timer_unit" || true + # A reinstall over a removed /etc/pulse reaches this point before + # setup_directories has recreated the config directory, and the + # system.json write below would fail with "No such file or directory" + # while the run still reported that auto-updates were enabled. + if ! mkdir -p "$config_dir"; then + print_warn "Could not create $config_dir. Continuing without automatic updates." + ENABLE_AUTO_UPDATES=false + return 0 + fi + # Update system.json to enable auto-updates if [[ -f "$config_dir/system.json" ]]; then # Update existing file diff --git a/scripts/installtests/root_install_sh_test.go b/scripts/installtests/root_install_sh_test.go index 8b0c902cc..73fe76015 100644 --- a/scripts/installtests/root_install_sh_test.go +++ b/scripts/installtests/root_install_sh_test.go @@ -2205,3 +2205,48 @@ start_pulse t.Fatalf("start_pulse did not report a successful start:\n%s", out) } } + +// Reported on #1663: reinstalling after `rm -rf /etc/pulse` reached +// setup_auto_updates before setup_directories had recreated the config +// directory, so the system.json write failed with "No such file or +// directory" while the run still reported that auto-updates were enabled. +func TestRootInstallScriptAutoUpdateSetupCreatesMissingConfigDir(t *testing.T) { + parent := t.TempDir() + configDir := filepath.Join(parent, "pulse") + + script := ` + set -euo pipefail + print_info() { :; } + print_warn() { echo "WARN: $*"; } + print_success() { :; } + selected_update_channel() { printf 'stable\n'; } + install_auto_update_assets() { return 0; } + safe_systemctl() { return 0; } + chown() { return 0; } + CONFIG_DIR="$CONFIG_DIR_UNDER_TEST" + SERVICE_NAME="pulse" + UPDATE_TIMER_PATH="/tmp/pulse-update.timer" + ENABLE_AUTO_UPDATES=true +` + extractRootInstallShellFunction(t, "setup_auto_updates") + ` + setup_auto_updates + ` + + cmd := exec.Command("bash", "-c", script) + cmd.Env = append(os.Environ(), "CONFIG_DIR_UNDER_TEST="+configDir) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("bash: %v\n%s", err, out) + } + if strings.Contains(string(out), "No such file or directory") { + t.Fatalf("expected config dir to be created before the system.json write, got:\n%s", out) + } + + systemJSON := filepath.Join(configDir, "system.json") + contents, readErr := os.ReadFile(systemJSON) + if readErr != nil { + t.Fatalf("expected %s to be written, got: %v\n%s", systemJSON, readErr, out) + } + if !strings.Contains(string(contents), `"autoUpdateEnabled":true`) { + t.Fatalf("expected auto-updates to be enabled in system.json, got: %s", contents) + } +}