diff --git a/bin/omarchy-install-openclaw-cli b/bin/omarchy-install-openclaw-cli index e4469181..5b01eecf 100755 --- a/bin/omarchy-install-openclaw-cli +++ b/bin/omarchy-install-openclaw-cli @@ -26,6 +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 + prefix="$HOME/.openclaw" runtime_command="$prefix/bin/openclaw" command_path="$HOME/.local/bin/openclaw" @@ -80,24 +84,41 @@ foreign_gateway() { } # A service the openclaw package's own copy installed runs from -# /usr/lib/node_modules/openclaw, which that package no longer ships. Installed -# again from the runtime, it runs code `openclaw update` can replace; upstream -# rewrites the unit and starts it. It is stopped first: still running the old -# code from deleted files, it holds the state directory the newer runtime has -# to migrate, and could not restart from those files anyway. A service running -# any other OpenClaw is the user's arrangement and stays. -rehome_services() { +# /usr/lib/node_modules/openclaw, which that package no longer ships, and is +# moved to the runtime, where `openclaw update` can replace its code. A service +# running any other OpenClaw is the user's arrangement and stays. +legacy_units() { local role unit for role in gateway node; do unit="$HOME/.config/systemd/user/openclaw-$role.service" if [[ -f $unit ]] && unit_runs "$unit" "/usr/lib/node_modules/openclaw/"; then - echo "Moving the OpenClaw $role service to $prefix..." - systemctl --user stop "openclaw-$role.service" 2>/dev/null || true - # The selectors would point the install at another unit and command. - if ! env -u OPENCLAW_PROFILE -u OPENCLAW_SYSTEMD_UNIT -u OPENCLAW_WRAPPER "$runtime_command" "$role" install --force; then - echo "Could not move the OpenClaw $role service. Finish with: openclaw $role install --force" >&2 - return 1 - fi + echo "$role" + fi + done +} + +# Stopped before anything is installed: still running the old code from deleted +# files, such a service holds the state directory the newer runtime has to +# migrate, which upstream's installer does the moment it finds one loaded. +stop_legacy_services() { + local role + for role in $(legacy_units); do + if ! systemctl --user stop "openclaw-$role.service"; then + echo "Could not stop the OpenClaw $role service, so OpenClaw was not moved to $prefix." >&2 + return 1 + fi + done +} + +# Installed again from the runtime, upstream rewrites the unit and starts it, +# unless its installer already did. +rehome_services() { + local role + for role in $(legacy_units); do + echo "Moving the OpenClaw $role service to $prefix..." + if ! "$runtime_command" "$role" install --force; then + echo "Could not move the OpenClaw $role service. Finish with: openclaw $role install --force" >&2 + return 1 fi done } @@ -113,14 +134,16 @@ case "$mode" in ;; esac +# Refused before anything is installed or touched: the command's name, what +# PATH finds, and a gateway running another OpenClaw all have to be clear. if ! command_ours; then echo "$command_path is not the OpenClaw Omarchy set up. Move it aside, then run omarchy-install-openclaw-cli --now again." >&2 exit 1 fi - -if ! omarchy-pkg-present openclaw; then - echo "Installing OpenClaw..." - omarchy-pkg-add openclaw +if ! path_clear; then + echo "'openclaw' on PATH is $(type -P openclaw), which is what Omarchy would run instead of $runtime_command." >&2 + echo "Remove it or reorder PATH, then run omarchy-install-openclaw-cli --now again." >&2 + exit 1 fi # A runtime that already answers is never reinstalled: it may be past the @@ -128,17 +151,6 @@ fi seeding=false if ! runtime_runs; then seeding=true - if [[ ! -r $seed/install-cli.sh || ! -r $seed/openclaw.tgz ]]; then - echo "The installed OpenClaw package cannot set up a self-updating OpenClaw. Run 'omarchy update', then try again." >&2 - exit 1 - fi -fi - -# Refused before anything in the home is touched. -if ! path_clear; then - echo "'openclaw' on PATH is $(type -P openclaw), which is what Omarchy would run instead of $runtime_command." >&2 - echo "Remove it or reorder PATH, then run omarchy-install-openclaw-cli --now again." >&2 - exit 1 fi if [[ $seeding == "true" ]] && foreign_gateway; then echo "The OpenClaw gateway service runs another OpenClaw, which setting one up in $prefix would take over." >&2 @@ -146,6 +158,17 @@ if [[ $seeding == "true" ]] && foreign_gateway; then exit 1 fi +if ! omarchy-pkg-present openclaw; then + echo "Installing OpenClaw..." + omarchy-pkg-add openclaw +fi +if [[ $seeding == "true" && ( ! -r $seed/install-cli.sh || ! -r $seed/openclaw.tgz ) ]]; then + echo "The installed OpenClaw package cannot set up a self-updating OpenClaw. Run 'omarchy update', then try again." >&2 + exit 1 +fi + +stop_legacy_services + if [[ $seeding == "true" ]]; then # Every choice the installer reads from the environment is named, so an # OPENCLAW_INSTALL_METHOD or OPENCLAW_PREFIX left in a shell cannot move it. diff --git a/test/shell.d/openclaw-cli-test.sh b/test/shell.d/openclaw-cli-test.sh index 74ac24b6..21bdb17a 100755 --- a/test/shell.d/openclaw-cli-test.sh +++ b/test/shell.d/openclaw-cli-test.sh @@ -24,6 +24,7 @@ SH cat >"$mock_bin/systemctl" <<'SH' #!/bin/bash printf 'systemctl %s\n' "$*" >>"$OMARCHY_TEST_ROOT/events" +[[ -z ${OMARCHY_TEST_STOP_FAIL:-} ]] SH chmod +x "$mock_bin/"* @@ -31,7 +32,7 @@ chmod +x "$mock_bin/"* # real one does, execing into the prefix's tools, and that command logs what # it is asked. OMARCHY_TEST_INSTALL_BROKEN leaves a command that cannot run. cat >"$seed/install-cli.sh" <<'SH' -printf 'install-cli %s\n' "$*" >>"$OMARCHY_TEST_ROOT/events" +printf 'install-cli %s%s\n' "$*" "${OPENCLAW_PROFILE:+ profile=$OPENCLAW_PROFILE}" >>"$OMARCHY_TEST_ROOT/events" prefix=$HOME/.openclaw mkdir -p "$prefix/bin" "$prefix/tools/node-v24.19.0" cat >"$prefix/bin/openclaw" <"$units/openclaw-gateway.service" printf 'ExecStart=/opt/node /home/someone/openclaw/dist/index.js node run\n' >"$units/openclaw-node.service" OPENCLAW_PROFILE=work run omarchy-install-openclaw-cli --now || fail "--now moves the old package's services" "$(cat "$test_tmp/output")" -grep -A1 -Fx "systemctl --user stop openclaw-gateway.service" "$events" | grep -Fxq "runtime gateway install --force" || - fail "--now stops a gateway the old package installed, then moves it" "$(cat "$events")" +order=$(grep -n -e '^systemctl --user stop openclaw-gateway.service$' -e '^install-cli ' -e '^runtime gateway install --force$' "$events" | cut -d: -f2- | cut -c1-11) +[[ $order == $'systemctl -\ninstall-cli\nruntime gat' ]] || + fail "--now stops a gateway the old package installed before seeding, then moves it" "$(cat "$events")" ! grep -q "runtime node install\|openclaw-node" "$events" || fail "--now leaves a service running another OpenClaw alone" "$(cat "$events")" -! grep -q "install --force profile=work" "$events" || fail "--now moves the default unit whatever profile the shell selects" "$(cat "$events")" +! grep -q "profile=work" <(grep -v -e '^runtime --version' "$events") || + fail "--now seeds and moves the default unit whatever profile the shell selects" "$(cat "$events")" pass "a service the old package installed moves to the runtime, and only that one" +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" +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" + # The migration moves only machines that have the package, and waits for the # package that seeds. new_home migration-none