From abf2f756f265ece1a9df476d142d3fcd4f6bcbc1 Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Sat, 26 Sep 2026 01:48:45 -0500 Subject: [PATCH] Judge a service by its program, and name any service left stopped A unit is Omarchy's or the old package's by the program its ExecStart runs or the script it hands that program, not by a path appearing anywhere in the line, so a gateway that merely names ~/.openclaw in an argument is not taken over. The state directory and config overrides are cleared with the profile selectors, since Omarchy's OpenClaw is the default one. Any failure after a running service was stopped for the move names that service, a partial stop included. Co-Authored-By: Codex XHigh --- bin/omarchy-install-openclaw-cli | 47 +++++++++++++++++++++---------- test/shell.d/openclaw-cli-test.sh | 20 ++++++++++--- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/bin/omarchy-install-openclaw-cli b/bin/omarchy-install-openclaw-cli index 825de500..10084c63 100755 --- a/bin/omarchy-install-openclaw-cli +++ b/bin/omarchy-install-openclaw-cli @@ -26,9 +26,10 @@ if (( EUID == 0 )); then exit 1 fi -# These select another profile's unit and command, for upstream's installer -# and the CLI alike, and everything here is about the default ones. -unset OPENCLAW_PROFILE OPENCLAW_SYSTEMD_UNIT OPENCLAW_WRAPPER +# Omarchy's OpenClaw is the default one, in ~/.openclaw with the default unit, +# whatever another profile, unit or state directory a shell selects; a service +# installed from here would otherwise carry that selection. +unset OPENCLAW_PROFILE OPENCLAW_SYSTEMD_UNIT OPENCLAW_WRAPPER OPENCLAW_HOME OPENCLAW_STATE_DIR OPENCLAW_CONFIG_PATH prefix="$HOME/.openclaw" runtime_command="$prefix/bin/openclaw" @@ -69,10 +70,22 @@ installed() { omarchy-pkg-present openclaw && runtime_runs && on_path } -# The service a unit runs, read from its ExecStart alone: other lines can name -# ~/.openclaw paths whichever OpenClaw the unit starts. +# Whether a unit's ExecStart runs a program from under a path: the program +# itself, or the script it is handed. Later arguments and other lines can name +# ~/.openclaw whichever OpenClaw the unit starts. unit_runs() { - grep -E '^ExecStart=' "$1" | grep -qF -- "$2" + local words word + read -ra words <<<"$(sed -n 's/^ExecStart=//p' "$1" | head -1)" + if [[ ${words[0]:-} == "$2"* ]]; then + return 0 + fi + for word in "${words[@]:1}"; do + if [[ $word != -* ]]; then + [[ $word == "$2"* ]] + return + fi + done + return 1 } # A gateway service already running some other OpenClaw. Upstream's installer @@ -103,6 +116,9 @@ stop_legacy_services() { echo "Could not stop the OpenClaw $role service, so OpenClaw was not moved to $prefix." >&2 return 1 fi + if [[ " ${running[*]} " == *" $role "* ]]; then + stopped+=("$role") + fi done } @@ -129,11 +145,14 @@ rehome_services() { done } -# Said whenever a failure leaves a service stopped by this run. +# Said on any failure that leaves a service this run stopped not running. stopped_note() { - if (( ${#running[@]} )); then - echo "The OpenClaw ${running[*]} service was stopped for this and is not running now." >&2 - fi + local role + for role in "${stopped[@]}"; do + if ! systemctl --user is-active --quiet "openclaw-$role.service"; then + echo "The OpenClaw $role service was stopped for this and is not running now." >&2 + fi + done } case "$mode" in @@ -184,6 +203,7 @@ fi # anything changes. moving=() running=() +stopped=() for role in gateway node; do if [[ -f $(unit_path "$role") ]] && unit_runs "$(unit_path "$role")" "/usr/lib/node_modules/openclaw/"; then moving+=("$role") @@ -193,6 +213,7 @@ for role in gateway node; do fi done +trap 'status=$?; (( status == 0 )) || stopped_note' EXIT stop_legacy_services if [[ $seeding == "true" ]]; then @@ -202,7 +223,6 @@ if [[ $seeding == "true" ]]; then bash "$seed/install-cli.sh" --install-method npm --prefix "$prefix" --version "$seed/openclaw.tgz" --no-onboard || true if ! runtime_runs; then echo "OpenClaw setup did not complete. Re-run this command after resolving the installer error." >&2 - stopped_note exit 1 fi fi @@ -210,10 +230,7 @@ fi mkdir -p "${command_path%/*}" ln -sfn "$runtime_command" "$command_path" -if ! rehome_services; then - stopped_note - exit 1 -fi +rehome_services if ! on_path; then echo "$runtime_command is ready, but 'openclaw' on PATH is $(type -P openclaw || echo missing), which is what Omarchy runs." >&2 diff --git a/test/shell.d/openclaw-cli-test.sh b/test/shell.d/openclaw-cli-test.sh index 1ffa279e..a06c7742 100755 --- a/test/shell.d/openclaw-cli-test.sh +++ b/test/shell.d/openclaw-cli-test.sh @@ -22,13 +22,14 @@ printf 'pkg-add %s\n' "$*" >>"$OMARCHY_TEST_ROOT/events" touch "$OMARCHY_TEST_ROOT/package-installed" SH # The user manager, as far as these tests need one: a service is active while -# a marker says so. Stopping clears it; OMARCHY_TEST_STOP_FAIL refuses. +# a marker says so. Stopping clears it, except for the unit named in +# OMARCHY_TEST_STOP_FAIL. cat >"$mock_bin/systemctl" <<'SH' #!/bin/bash case "$2" in stop) printf 'systemctl %s\n' "$*" >>"$OMARCHY_TEST_ROOT/events" - [[ -z ${OMARCHY_TEST_STOP_FAIL:-} ]] || exit 1 + [[ ${OMARCHY_TEST_STOP_FAIL:-} != "$3" ]] || exit 1 rm -f "$HOME/active-$3" ;; is-active) [[ -e $HOME/active-$4 ]] ;; @@ -166,7 +167,7 @@ pass "--check needs the package too, so --now never has a password to ask for un # just made, so a gateway running another OpenClaw stops the seeding first. new_home foreign-gateway mkdir -p "$test_home/.config/systemd/user" -printf 'Environment=OPENCLAW_CONFIG_PATH=%s/.openclaw/openclaw.json\nExecStart=/opt/node %s/openclaw/dist/index.js gateway\n' "$test_home" "$test_home" \ +printf 'Environment=OPENCLAW_CONFIG_PATH=%s/.openclaw/openclaw.json\nExecStart=/opt/node %s/openclaw/dist/index.js gateway --config %s/.openclaw/openclaw.json\n' "$test_home" "$test_home" "$test_home" \ >"$test_home/.config/systemd/user/openclaw-gateway.service" run omarchy-install-openclaw-cli --now && fail "a gateway running another OpenClaw stops the install" grep -q "runs another OpenClaw" "$test_tmp/output" || fail "a gateway running another OpenClaw is named" "$(cat "$test_tmp/output")" @@ -193,12 +194,23 @@ pass "a service the old package installed moves to the runtime, and only that on new_home stop-fails mkdir -p "$test_home/.config/systemd/user" printf 'ExecStart=/usr/bin/node /usr/lib/node_modules/openclaw/dist/index.js gateway --port 18789\n' >"$test_home/.config/systemd/user/openclaw-gateway.service" -OMARCHY_TEST_STOP_FAIL=1 run omarchy-install-openclaw-cli --now && fail "a gateway that will not stop stops the install" +OMARCHY_TEST_STOP_FAIL=openclaw-gateway.service run omarchy-install-openclaw-cli --now && fail "a gateway that will not stop stops the install" grep -q "Could not stop the OpenClaw gateway service" "$test_tmp/output" || fail "a gateway that will not stop is named" "$(cat "$test_tmp/output")" ! grep -q '^install-cli' "$events" && [[ ! -e $test_home/.openclaw ]] || fail "a gateway that will not stop leaves the runtime unseeded" "$(cat "$events")" pass "a gateway that will not stop stops the install before anything is seeded" +new_home stop-partial +mkdir -p "$test_home/.config/systemd/user" +for role in gateway node; do + printf 'ExecStart=/usr/bin/node /usr/lib/node_modules/openclaw/dist/index.js %s\n' "$role" >"$test_home/.config/systemd/user/openclaw-$role.service" + touch "$test_home/active-openclaw-$role.service" +done +OMARCHY_TEST_STOP_FAIL=openclaw-node.service run omarchy-install-openclaw-cli --now && fail "a node host that will not stop stops the install" +grep -q "gateway service was stopped for this and is not running now" "$test_tmp/output" || + fail "a gateway stopped before a later stop failed is named as stopped" "$(cat "$test_tmp/output")" +pass "a service this run stopped is named whenever the run then fails" + # Upstream's installer rewrites the gateway itself and only warns when it will # not start again, so a gateway that was running has to be running afterwards. new_home start-fails