diff --git a/scripts/e2e/lib/doctor-install-switch/scenario.sh b/scripts/e2e/lib/doctor-install-switch/scenario.sh index 0441cd78192a..4090607ee411 100644 --- a/scripts/e2e/lib/doctor-install-switch/scenario.sh +++ b/scripts/e2e/lib/doctor-install-switch/scenario.sh @@ -59,6 +59,24 @@ update_doctor_env+=" OPENCLAW_UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE=1" update_doctor_env+=" OPENCLAW_UPDATE_PARENT_SUPPORTS_GATEWAY_RESTART=1" update_doctor_env+=" OPENCLAW_UPDATE_PARENT_ALLOWS_GATEWAY_SERVICE_REPAIR=1" update_doctor_env+=" OPENCLAW_UPDATE_PARENT_ALLOWS_GATEWAY_ACTIVATION=0" + +create_default_service_state() { + local account_home + account_home="$(node -p 'require("node:os").userInfo().homedir')" + + # Service mutation is intentionally restricted to the OS account home. Keep + # these disposable-container flows isolated without pretending a temp HOME owns it. + rm -rf \ + "$account_home/.openclaw" \ + "$account_home/.config/systemd/user/openclaw-gateway.service" \ + "$account_home/.config/fish" \ + "$account_home/.config/powershell" \ + "$account_home/.local/bin/openclaw-wrapper" \ + "$account_home/openclaw-wrapper-argv.log" + openclaw_test_state_create "$account_home" empty + unset OPENCLAW_HOME OPENCLAW_STATE_DIR OPENCLAW_CONFIG_PATH +} + is_legacy_package_acceptance_compat() { [ "$(node scripts/e2e/lib/package-compat.mjs "$1")" = "1" ] } @@ -136,17 +154,9 @@ run_flow() { local install_log="/tmp/openclaw-doctor-switch-${name}-install.log" local doctor_log="/tmp/openclaw-doctor-switch-${name}-doctor.log" local command_timeout="${OPENCLAW_DOCKER_DOCTOR_SWITCH_COMMAND_TIMEOUT:-900s}" - local account_home="" echo "== Flow: $name ==" - openclaw_test_state_create "switch-${name}" empty - account_home="$(getent passwd "$(id -u)" | cut -d: -f6)" - if [ -z "$account_home" ]; then - echo "Could not resolve the current account home" >&2 - exit 1 - fi - export HOME="$account_home" - unset OPENCLAW_HOME OPENCLAW_STATE_DIR OPENCLAW_CONFIG_PATH + create_default_service_state export USER="testuser" if ! openclaw_e2e_maybe_timeout "$command_timeout" bash -c "$install_cmd" >"$install_log" 2>&1; then @@ -270,7 +280,7 @@ run_proxy_env_flow() { local command_timeout="${OPENCLAW_DOCKER_DOCTOR_SWITCH_COMMAND_TIMEOUT:-900s}" echo "== Flow: $name ==" - openclaw_test_state_create "switch-${name}" empty + create_default_service_state export USER="testuser" unit_path="$HOME/.config/systemd/user/openclaw-gateway.service" @@ -316,7 +326,7 @@ run_wrapper_flow() { local command_timeout="${OPENCLAW_DOCKER_DOCTOR_SWITCH_COMMAND_TIMEOUT:-900s}" echo "== Flow: $name ==" - openclaw_test_state_create "switch-${name}" empty + create_default_service_state export USER="testuser" mkdir -p "$HOME/.local/bin" local wrapper="$HOME/.local/bin/openclaw-wrapper" diff --git a/scripts/e2e/lib/doctor-install-switch/shims/systemctl b/scripts/e2e/lib/doctor-install-switch/shims/systemctl index 94f13451595a..6e3b89f10ab9 100755 --- a/scripts/e2e/lib/doctor-install-switch/shims/systemctl +++ b/scripts/e2e/lib/doctor-install-switch/shims/systemctl @@ -2,7 +2,9 @@ set -euo pipefail args=("$@") +scope="system" if [[ "${args[0]:-}" == "--user" ]]; then + scope="user" args=("${args[@]:1}") fi cmd="${args[0]:-}" @@ -24,6 +26,20 @@ case "$cmd" in exit 1 ;; show) + property="" + for arg in "${args[@]:1}"; do + case "$arg" in + --property=*) property="${arg#--property=}" ;; + esac + done + if [[ "$scope" == "system" && "$property" == "LoadState" ]]; then + echo "not-found" + exit 0 + fi + if [[ "$scope" == "system" && "$property" == "UnitPath" ]]; then + echo "/etc/systemd/system /usr/lib/systemd/system" + exit 0 + fi printf "%s\n" \ "ActiveState=inactive" \ "SubState=dead" \ diff --git a/test/scripts/docker-build-helper.test.ts b/test/scripts/docker-build-helper.test.ts index a8b47ff38f21..617324bdf0b9 100644 --- a/test/scripts/docker-build-helper.test.ts +++ b/test/scripts/docker-build-helper.test.ts @@ -4357,6 +4357,26 @@ heartbeat_elapsed="\${BASH_REMATCH[1]}" expect(pluginsAssertions).toContain("expected modern installRecords in installed plugin index"); }); + it("keeps the doctor switch systemctl shim system scope empty", () => { + const home = tempDirs.make("openclaw-doctor-systemctl-shim-"); + const env = { ...process.env, HOME: home }; + const loadState = spawnSync( + DOCTOR_SWITCH_SYSTEMCTL_SHIM_PATH, + ["show", "--property=LoadState", "--value", "openclaw-gateway.service"], + { encoding: "utf8", env }, + ); + const unitPath = spawnSync( + DOCTOR_SWITCH_SYSTEMCTL_SHIM_PATH, + ["show", "--property=UnitPath", "--value"], + { encoding: "utf8", env }, + ); + + expect(loadState.status).toBe(0); + expect(loadState.stdout.trim()).toBe("not-found"); + expect(unitPath.status).toBe(0); + expect(unitPath.stdout).toContain("/etc/systemd/system"); + }); + it("routes doctor install switch commands through the E2E timeout helper", () => { const runner = readFileSync(DOCTOR_SWITCH_DOCKER_E2E_PATH, "utf8"); const scenario = readFileSync(DOCTOR_SWITCH_SCENARIO_PATH, "utf8"); @@ -4370,16 +4390,8 @@ heartbeat_elapsed="\${BASH_REMATCH[1]}" expectTextToIncludeAll(scenario, [ 'command_timeout="${OPENCLAW_DOCKER_DOCTOR_SWITCH_COMMAND_TIMEOUT:-900s}"', - [ - 'openclaw_test_state_create "switch-${name}" empty', - ' account_home="$(getent passwd "$(id -u)" | cut -d: -f6)"', - ' if [ -z "$account_home" ]; then', - ' echo "Could not resolve the current account home" >&2', - " exit 1", - " fi", - ' export HOME="$account_home"', - " unset OPENCLAW_HOME OPENCLAW_STATE_DIR OPENCLAW_CONFIG_PATH", - ].join("\n"), + 'openclaw_test_state_create "$account_home" empty\n unset OPENCLAW_HOME OPENCLAW_STATE_DIR OPENCLAW_CONFIG_PATH', + "create_default_service_state", 'openclaw_e2e_maybe_timeout "$command_timeout" bash -c "$install_cmd"', 'openclaw_e2e_maybe_timeout "$command_timeout" bash -c "$doctor_cmd"', 'openclaw_e2e_maybe_timeout "$command_timeout" "$npm_bin" gateway install --wrapper "$wrapper" --force', @@ -4389,9 +4401,6 @@ heartbeat_elapsed="\${BASH_REMATCH[1]}" expect( scenario.match(/unset OPENCLAW_HOME OPENCLAW_STATE_DIR OPENCLAW_CONFIG_PATH/gu), ).toHaveLength(1); - expect(scenario.match(/getent passwd "\$\(id -u\)"/gu)).toHaveLength(1); - expect(scenario.match(/export HOME="\$account_home"/gu)).toHaveLength(1); - expect(scenario).not.toMatch(/^\s*if ! timeout "\$command_timeout"/mu); });