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
This commit is contained in:
rcourtman 2026-08-03 09:47:03 +01:00
parent 99ad8c2c4b
commit 2a1bf28394
2 changed files with 55 additions and 0 deletions

View file

@ -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

View file

@ -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)
}
}