Install OpenClaw as the self-updating copy under ~/.openclaw

This commit is contained in:
Spencer Bull committed 2026-09-25 23:48:46 -05:00
1 parent 7b336b1b0d
commit be18b324f8
9 files changed
+353 -38

No files matched your search

+5 -4
View File
@@ -50,10 +50,11 @@ esac
agent_package=${agent_package:-$agent}
# Hermes and OpenClaw are not mise tools: Hermes is the desktop app's
# self-updating runtime and OpenClaw comes from its pacman package. Each has
# its own installer with the same --check/--now contract mise-backed agents
# get from mise; see omarchy-install-hermes-cli and omarchy-install-openclaw-cli.
# Hermes and OpenClaw are not mise tools: each is a self-updating runtime in
# the user's home, Hermes the desktop app's and OpenClaw the one its Control
# UI updates. Each has its own installer with the same --check/--now contract
# mise-backed agents get from mise; see omarchy-install-hermes-cli and
# omarchy-install-openclaw-cli.
if [[ -n ${agent_installer:-} ]]; then
# Not omarchy-cmd-present: a command on PATH says nothing about whether the
# install behind it finished or runs the sessions omarchy-agent starts.
+3 -3
View File
@@ -5,8 +5,8 @@
set -e
echo "Installing OpenClaw..."
omarchy-pkg-add openclaw
# The same self-updating install the default agent gets.
omarchy-install-openclaw-cli --now
# The desktop app is OpenClaw's Control UI: a web app served by its own
# gateway. The launcher entry goes through omarchy-launch-openclaw, which
@@ -14,7 +14,7 @@ omarchy-pkg-add openclaw
# inside the package, so nothing is fetched here.
echo "Installing the OpenClaw web app..."
omarchy-webapp-install OpenClaw "http://127.0.0.1:18789" \
/usr/lib/node_modules/openclaw/dist/control-ui/apple-touch-icon.png \
/usr/share/openclaw/openclaw.png \
omarchy-launch-openclaw
# A first install runs onboarding right here: launching the app instead would
+114 -16
View File
@@ -1,29 +1,127 @@
#!/bin/bash
# omarchy:summary=Ensure the OpenClaw CLI is installed for the default agent
# omarchy:args=[--check|--now]
# omarchy:summary=Install OpenClaw for the default agent as the self-updating copy under ~/.openclaw
# omarchy:args=<--check|--now>
# omarchy:examples=omarchy install openclaw cli --now | omarchy install openclaw cli --check
# omarchy:requires-sudo=true
# OpenClaw is not a mise tool: the openclaw pacman package is the one OpenClaw
# installation on the machine — the CLI, the gateway, and the Install > AI web
# app all share it, and the fast ring keeps it current. The default-agent flow
# talks to that package through the same --check/--now contract mise-backed
# agents get from mise itself.
# There is one OpenClaw on a machine, and it is the one under ~/.openclaw that
# upstream's install-cli.sh makes: a private Node and the package in a prefix
# the user owns, which `openclaw update` and the Control UI's Update button
# replace in place. A copy in /usr belongs to pacman, and upstream's updater
# refuses to touch it. The openclaw package is therefore the seed rather than
# the runtime: the release Omarchy ships and the installer that came with it.
# The CLI, the gateway service and the Install > AI web app all run this copy.
#
# Each mode is named outright, like omarchy-install-hermes-cli: the default
# agent asks --check first and opens a terminal for --now only on a no.
set -euo pipefail
case "${1:---now}" in
mode=${1:-}
prefix="$HOME/.openclaw"
runtime_command="$prefix/bin/openclaw"
command_path="$HOME/.local/bin/openclaw"
seed=/usr/share/openclaw
# Usable means the command answers, not that the file is there: upstream's
# wrapper execs the prefix's own Node, so a prefix missing either fails here.
runtime_runs() {
[[ -f $runtime_command && -x $runtime_command ]] &&
timeout 30 "$runtime_command" --version >/dev/null 2>&1
}
# The name on PATH is a link to the runtime's command. A link already aimed
# there, even one left dangling by a removed runtime, is Omarchy's to rewrite;
# anything else at the path is the user's.
command_ours() {
[[ ! -e $command_path && ! -L $command_path ]] || [[ $(readlink -- "$command_path") == "$runtime_command" ]]
}
# What omarchy-agent, the web app and a terminal run is whichever openclaw is
# first on PATH, and Omarchy puts /usr/bin and the mise shims ahead of
# ~/.local/bin, so the command has to resolve to the runtime from there.
on_path() {
local found
found=$(type -P openclaw) || return 1
[[ $(realpath -m -- "$found") == "$(realpath -m -- "$runtime_command")" ]]
}
installed() {
runtime_runs && on_path
}
# 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 restarts it. A service running any other OpenClaw is
# the user's arrangement and stays.
rehome_services() {
local role unit
for role in gateway node; do
unit="$HOME/.config/systemd/user/openclaw-$role.service"
if [[ -f $unit ]] && grep -qF "/usr/lib/node_modules/openclaw/" "$unit"; then
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
fi
done
}
case "$mode" in
--check)
omarchy-pkg-present openclaw
;;
--now)
if ! omarchy-pkg-present openclaw; then
echo "Installing OpenClaw..."
omarchy-pkg-add openclaw
fi
if installed; then exit 0; else exit 1; fi
;;
--now) ;;
*)
echo "Usage: omarchy-install-openclaw-cli [--check|--now]" >&2
echo "Usage: omarchy-install-openclaw-cli <--check|--now>" >&2
exit 1
;;
esac
if (( EUID == 0 )); then
echo "Run this command as your desktop user, without sudo." >&2
exit 1
fi
if ! omarchy-pkg-present openclaw; then
echo "Installing OpenClaw..."
omarchy-pkg-add openclaw
fi
# A runtime that already answers is never reinstalled: it may be past the
# packaged release by its own updates, and seeding would take it back.
if ! runtime_runs; then
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
# 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.
echo "Setting up OpenClaw in $prefix..."
bash "$seed/install-cli.sh" --install-method npm --prefix "$prefix" --version "$seed/openclaw.tgz" --no-onboard
if ! runtime_runs; then
echo "OpenClaw setup did not complete. Re-run this command after resolving the installer error." >&2
exit 1
fi
fi
if command_ours; then
mkdir -p "${command_path%/*}"
ln -sfn "$runtime_command" "$command_path"
else
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
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
echo "Remove it or reorder PATH, then run omarchy-install-openclaw-cli --now again." >&2
exit 1
fi
+16 -2
View File
@@ -49,6 +49,19 @@ done
omarchy-pkg-drop openclaw
# The runtime omarchy-install-openclaw-cli set up lives beside the user's state
# in ~/.openclaw: the command upstream's installer wrote, and the Node and
# package under tools/. Only a command that execs into those tools is the
# installer's; the link on PATH goes only when it points at that command.
if [[ -f $HOME/.openclaw/bin/openclaw ]] && grep -qF "\"$HOME/.openclaw/tools/" "$HOME/.openclaw/bin/openclaw"; then
rm -rf "$HOME/.openclaw/tools"
rm -f "$HOME/.openclaw/bin/openclaw"
rmdir "$HOME/.openclaw/bin" 2>/dev/null || true
fi
if [[ -L $HOME/.local/bin/openclaw && $(readlink -- "$HOME/.local/bin/openclaw") == "$HOME/.openclaw/bin/openclaw" ]]; then
rm -f "$HOME/.local/bin/openclaw"
fi
# The web app launcher and icon omarchy-install-ai-openclaw created.
rm -f "$HOME/.local/share/applications/OpenClaw.desktop"
rm -f "$HOME/.local/share/icons/hicolor/256x256/apps/openclaw.png"
@@ -59,12 +72,13 @@ gtk-update-icon-cache "$HOME/.local/share/icons/hicolor" &>/dev/null || true
# better surprise. But it also holds the plugin runtimes and caches OpenClaw
# downloads for itself, easily hundreds of megabytes, so the choice is put in
# front of the user with the size rather than left silent. Without a terminal
# to ask in, keeping it is the answer.
# to ask in, keeping it is the answer. The snapshots `openclaw update` takes
# of that state sit beside it and go with it.
state_removed=false
if [[ -d $HOME/.openclaw && -t 0 ]]; then
size=$(du -sh "$HOME/.openclaw" 2>/dev/null | cut -f1)
if gum confirm --default=false "Also delete ~/.openclaw ($size: chats, memories, credentials, and downloaded plugins)?"; then
rm -rf "$HOME/.openclaw"
rm -rf "$HOME/.openclaw" "$HOME/.openclaw.update-captures"
state_removed=true
fi
fi
+1 -1
View File
@@ -54,7 +54,7 @@ The _Install > AI_ menu also carries a few graphical AI apps: the ChatGPT deskto
Hermes Desktop is the one to know about, because a machine has one Hermes, and it is the app's: Hermes is only ever installed through the app, and the `hermes` command the default agent runs is the app's. Choosing Hermes as the default agent installs the same thing the _Install > AI_ entry does: the package, and a Hermes runtime under `~/.hermes` set up by Hermes' own installer, which takes a few minutes the first time. That runtime is the one Hermes the terminal `hermes` command, the default agent and the app all use, and it updates itself with `hermes update` rather than waiting on an Omarchy release. Installing it also hands Hermes the Omarchy theme as a skin named `omarchy`, which every Hermes surface follows as you switch themes; pick another under Hermes' Appearance settings or with `/skin` if you'd rather it didn't, and Omarchy leaves that choice alone. Removing the app under _Remove > AI_ closes Hermes first, gateway included, takes that runtime with it, and keeps your chats, memories, and the skills Hermes wrote for itself unless you tell it otherwise: it asks, defaulting to no, whether that data and your connection settings should go too.
OpenClaw's desktop experience is its Control UI, which opens as a web app backed by its own local gateway. OpenClaw updates arrive through Omarchy's package updates, so skip the Control UI's own "Update Gateway" button: it would try to write into the package-managed install and fail. Removing OpenClaw under _Remove > AI_ takes the gateway service and the app with it and then asks whether `~/.openclaw` should go too, since that holds your chats and credentials alongside the plugin runtimes OpenClaw downloads for itself; the default keeps it.
OpenClaw's desktop experience is its Control UI, which opens as a web app backed by its own local gateway. OpenClaw lives in `~/.openclaw` and updates itself: use the Control UI's "Update Gateway" button or run `openclaw update`, and the gateway restarts on the new version. Omarchy only chooses the release a fresh install starts from. Removing OpenClaw under _Remove > AI_ takes the gateway service, the app and OpenClaw itself with it and then asks whether the rest of `~/.openclaw` should go too, since that holds your chats and credentials alongside the plugin runtimes OpenClaw downloads for itself; the default keeps it.
### Local LLMs
+12
View File
@@ -0,0 +1,12 @@
echo "Move OpenClaw to a self-updating install under ~/.openclaw"
# The openclaw package used to be OpenClaw itself, installed under /usr where `openclaw update` and the Control UI's Update button cannot write. It is now the seed for a copy under ~/.openclaw that updates itself, and omarchy-install-openclaw-cli sets that copy up, points the command on PATH at it and moves a gateway service the old package installed over to it. Only machines with the package have an OpenClaw of Omarchy's to move.
omarchy-pkg-present openclaw || exit 0
# Updates install packages before migrations. An older package is still the runtime and has no seed, so this stays pending until it is updated.
if [[ ! -r /usr/share/openclaw/install-cli.sh || ! -r /usr/share/openclaw/openclaw.tgz ]]; then
echo "Update the openclaw package before rerunning this migration." >&2
exit 1
fi
omarchy-install-openclaw-cli --now
+13 -12
View File
@@ -761,15 +761,16 @@ grep -F "missing is not installed" "$test_tmp/missing-output" >/dev/null ||
fail "agent launcher explains when the default command is missing"
pass "agent launcher reports a missing default command"
# OpenClaw comes from its pacman package, not mise: choosing it must route
# OpenClaw is its own self-updating runtime, not mise's: choosing it must route
# through omarchy-install-openclaw-cli and never touch a mise environment.
cat >"$mock_bin/omarchy-pkg-present" <<'SH'
# openclaw-cli-test.sh covers the installer itself.
cat >"$mock_bin/omarchy-install-openclaw-cli" <<'SH'
#!/bin/bash
[[ $1 == openclaw && ${OMARCHY_TEST_OPENCLAW_INSTALLED:-false} == "true" ]]
SH
cat >"$mock_bin/omarchy-pkg-add" <<'SH'
#!/bin/bash
printf '%s\n' "pkg-add $*" >>"$OMARCHY_TEST_STUB_LOG"
if [[ $1 == "--check" ]]; then
[[ ${OMARCHY_TEST_OPENCLAW_INSTALLED:-false} == "true" ]]
else
printf '%s\n' "install-openclaw-cli $*" >>"$OMARCHY_TEST_STUB_LOG"
fi
SH
cat >"$mock_bin/omarchy-launch-openclaw" <<'SH'
#!/bin/bash
@@ -779,7 +780,7 @@ cat >"$mock_bin/openclaw" <<'SH'
#!/bin/bash
exit 0
SH
chmod +x "$mock_bin/omarchy-pkg-present" "$mock_bin/omarchy-pkg-add" \
chmod +x "$mock_bin/omarchy-install-openclaw-cli" \
"$mock_bin/omarchy-launch-openclaw" "$mock_bin/openclaw"
: >"$launch_log"
@@ -793,7 +794,7 @@ mapfile -d '' -t launch_args <"$launch_log"
fail "choosing OpenClaw launches its terminal UI"
[[ ! -s $terminal_log ]] || fail "an installed OpenClaw needs no install terminal"
! grep -q 'use -g openclaw' "$mise_history" || fail "OpenClaw never installs through mise"
pass "choosing OpenClaw uses the package and launches its terminal UI"
pass "choosing OpenClaw uses its runtime and launches its terminal UI"
: >"$terminal_log"
OMARCHY_TEST_OPENCLAW_INSTALLED=false omarchy-default-agent openclaw
@@ -805,12 +806,12 @@ pass "a missing OpenClaw routes through the install terminal"
: >"$stub_log"
: >"$inline_log"
OMARCHY_TEST_OPENCLAW_INSTALLED=false omarchy-default-agent --install openclaw >/dev/null
grep -Fx "pkg-add openclaw" "$stub_log" >/dev/null ||
fail "installing OpenClaw as default agent adds its package"
grep -Fx "install-openclaw-cli --now" "$stub_log" >/dev/null ||
fail "installing OpenClaw as default agent sets up its runtime"
mapfile -d '' -t inline_args <"$inline_log"
[[ ${inline_args[*]} == "omarchy-launch-openclaw --tui" ]] ||
fail "installing OpenClaw as default agent hands over to its terminal UI"
pass "installing OpenClaw as default agent adds its package"
pass "installing OpenClaw as default agent sets up its runtime"
: >"$launch_log"
omarchy agent prompt "Review this project"
+168
View File
@@ -0,0 +1,168 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT
mock_bin="$test_tmp/bin"
seed="$test_tmp/share"
events="$test_tmp/events"
mkdir -p "$mock_bin" "$seed"
cat >"$mock_bin/omarchy-pkg-present" <<'SH'
#!/bin/bash
[[ $1 == openclaw && -e $OMARCHY_TEST_ROOT/package-installed ]]
SH
cat >"$mock_bin/omarchy-pkg-add" <<'SH'
#!/bin/bash
printf 'pkg-add %s\n' "$*" >>"$OMARCHY_TEST_ROOT/events"
touch "$OMARCHY_TEST_ROOT/package-installed"
SH
chmod +x "$mock_bin/"*
# Stands in for upstream's install-cli.sh: it writes the command the way the
# 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"
prefix=$HOME/.openclaw
mkdir -p "$prefix/bin" "$prefix/tools/node-v24.19.0"
cat >"$prefix/bin/openclaw" <<EOF
#!/usr/bin/env bash
set -euo pipefail
[[ -z "${OMARCHY_TEST_INSTALL_BROKEN:-}" ]] || exit 1
printf 'runtime %s\n' "\$*" >>"$OMARCHY_TEST_ROOT/events"
exec true "$prefix/tools/node-v24.19.0/lib/node_modules/openclaw/dist/entry.js" "\$@"
EOF
chmod 755 "$prefix/bin/openclaw"
SH
touch "$seed/openclaw.tgz"
# Scratch copies of the actual scripts, with only the package's path swapped.
for script in bin/omarchy-install-openclaw-cli migrations/1790397381.sh; do
sed "s|/usr/share/openclaw|$seed|g" "$ROOT/$script" >"$mock_bin/${script##*/}"
done
mv "$mock_bin/1790397381.sh" "$test_tmp/migration.sh"
chmod +x "$mock_bin/omarchy-install-openclaw-cli"
new_home() {
test_home="$test_tmp/$1"
runtime="$test_home/.openclaw/bin/openclaw"
command="$test_home/.local/bin/openclaw"
mkdir -p "$test_home/.local/bin"
rm -f "$test_tmp/package-installed"
: >"$events"
}
# PATH puts a directory ahead of ~/.local/bin the way Omarchy's does, where a
# test can drop another openclaw.
mkdir -p "$test_tmp/usr-bin"
run() {
HOME="$test_home" OMARCHY_TEST_ROOT="$test_tmp" PATH="$test_tmp/usr-bin:$mock_bin:$test_home/.local/bin:$PATH" \
"$@" >"$test_tmp/output" 2>&1
}
new_home usage
run omarchy-install-openclaw-cli && fail "no mode is a usage error"
[[ ! -s $events ]] || fail "no mode installs nothing" "$(cat "$events")"
pass "every mode is named outright"
new_home fresh
run omarchy-install-openclaw-cli --check && fail "--check calls a machine without OpenClaw installed"
run omarchy-install-openclaw-cli --now || fail "--now sets OpenClaw up" "$(cat "$test_tmp/output")"
grep -Fxq "pkg-add openclaw" "$events" || fail "--now installs the package" "$(cat "$events")"
grep -Fxq "install-cli --install-method npm --prefix $test_home/.openclaw --version $seed/openclaw.tgz --no-onboard" "$events" ||
fail "--now seeds the runtime from the packaged release" "$(cat "$events")"
[[ -L $command && $(readlink -- "$command") == "$runtime" ]] || fail "--now points the command on PATH at the runtime"
run omarchy-install-openclaw-cli --check || fail "--check follows a finished install"
pass "--now seeds a self-updating OpenClaw from the packaged release"
: >"$events"
run omarchy-install-openclaw-cli --now || fail "--now accepts a finished install" "$(cat "$test_tmp/output")"
! grep -q '^install-cli\|^pkg-add' "$events" || fail "a runtime that answers is never reseeded" "$(cat "$events")"
pass "a runtime that answers is never reseeded, whatever version its updates reached"
rm -f "$command"
ln -s "$runtime" "$command.tmp" && mv "$command.tmp" "$command"
mv "$test_home/.openclaw" "$test_home/.openclaw.gone"
run omarchy-install-openclaw-cli --check && fail "--check calls a dangling link installed"
run omarchy-install-openclaw-cli --now || fail "--now reseeds behind its own dangling link" "$(cat "$test_tmp/output")"
[[ -x $runtime && $(readlink -- "$command") == "$runtime" ]] || fail "--now reseeds behind its own dangling link"
pass "a link Omarchy left behind is rewritten, and a missing runtime reseeded"
new_home old-package
touch "$test_tmp/package-installed"
mv "$seed" "$seed.old"
run omarchy-install-openclaw-cli --now && fail "a package with no seed cannot set OpenClaw up"
grep -q "Run 'omarchy update'" "$test_tmp/output" || fail "a package with no seed says to update" "$(cat "$test_tmp/output")"
[[ ! -e $test_home/.openclaw && ! -e $command ]] || fail "a package with no seed leaves the home untouched"
mv "$seed.old" "$seed"
pass "a package that is still the runtime is refused before anything is touched"
new_home broken
OMARCHY_TEST_INSTALL_BROKEN=1 run omarchy-install-openclaw-cli --now && fail "a runtime that does not run is not a finished install"
grep -q "did not complete" "$test_tmp/output" || fail "a failed setup says so" "$(cat "$test_tmp/output")"
[[ ! -e $command ]] || fail "a failed setup puts nothing on PATH"
pass "a runtime that does not run fails the install"
new_home foreign
printf '#!/bin/bash\necho mine\n' >"$command"
chmod +x "$command"
run omarchy-install-openclaw-cli --now && fail "a command that is the user's is not replaced"
grep -q "Move it aside" "$test_tmp/output" || fail "a command that is the user's is named" "$(cat "$test_tmp/output")"
[[ ! -L $command ]] && grep -q mine "$command" || fail "a command that is the user's is kept"
run omarchy-install-openclaw-cli --check && fail "--check calls the runtime installed while the command is somebody else's"
pass "a command at the path that is the user's is kept and named"
new_home shadowed
printf '#!/bin/bash\n' >"$test_tmp/usr-bin/openclaw"
chmod +x "$test_tmp/usr-bin/openclaw"
run omarchy-install-openclaw-cli --now && fail "an openclaw earlier on PATH fails the install"
grep -q "on PATH is $test_tmp/usr-bin/openclaw" "$test_tmp/output" || fail "an openclaw earlier on PATH is named" "$(cat "$test_tmp/output")"
run omarchy-install-openclaw-cli --check && fail "--check calls a shadowed runtime installed"
rm "$test_tmp/usr-bin/openclaw"
run omarchy-install-openclaw-cli --check || fail "--check follows once nothing shadows the runtime"
pass "the runtime has to be the openclaw PATH finds"
# A gateway the old package installed runs from /usr/lib/node_modules, which
# the seed package no longer ships; one running any other OpenClaw stays.
new_home services
units="$test_home/.config/systemd/user"
mkdir -p "$units"
printf 'ExecStart=/usr/bin/node /usr/lib/node_modules/openclaw/dist/index.js gateway --port 18789\n' >"$units/openclaw-gateway.service"
printf 'ExecStart=/opt/node /home/someone/openclaw/dist/index.js node run\n' >"$units/openclaw-node.service"
run omarchy-install-openclaw-cli --now || fail "--now moves the old package's services" "$(cat "$test_tmp/output")"
grep -Fxq "runtime gateway install --force" "$events" || fail "--now moves a gateway the old package installed" "$(cat "$events")"
! grep -q "runtime node install" "$events" || fail "--now leaves a service running another OpenClaw alone" "$(cat "$events")"
pass "a service the old package installed moves to the runtime, and only that one"
# The migration moves only machines that have the package, and waits for the
# package that seeds.
new_home migration-none
run bash -euo pipefail "$test_tmp/migration.sh" || fail "a machine without OpenClaw has nothing to move" "$(cat "$test_tmp/output")"
[[ ! -s $events && ! -e $test_home/.openclaw ]] || fail "a machine without OpenClaw is untouched" "$(cat "$events")"
new_home migration-old
touch "$test_tmp/package-installed"
mv "$seed" "$seed.old"
run bash -euo pipefail "$test_tmp/migration.sh" && fail "an old package keeps the migration pending"
[[ ! -e $test_home/.openclaw ]] || fail "an old package leaves the home untouched"
mv "$seed.old" "$seed"
new_home migration
touch "$test_tmp/package-installed"
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"
run bash -euo pipefail "$test_tmp/migration.sh" || fail "the migration moves OpenClaw" "$(cat "$test_tmp/output")"
grep -q "^install-cli " "$events" && grep -Fxq "runtime gateway install --force" "$events" ||
fail "the migration seeds the runtime and moves the gateway to it" "$(cat "$events")"
[[ $(readlink -- "$command") == "$runtime" ]] || fail "the migration points the command at the runtime"
new_home migration-foreign
touch "$test_tmp/package-installed"
printf '#!/bin/bash\n' >"$command"
run bash -euo pipefail "$test_tmp/migration.sh" && fail "a migration that cannot finish stays pending"
pass "the migration moves a packaged OpenClaw to its runtime, and stays pending until it can"
+21
View File
@@ -295,6 +295,27 @@ pass "OpenClaw removal stops the gateway service"
fail "OpenClaw removal keeps the user's agent state" "asked about ~/.openclaw without a terminal"
pass "OpenClaw removal keeps the user's agent state"
# The runtime omarchy-install-openclaw-cli set up sits beside that state and
# goes; a command that is not the installer's, or a link elsewhere, stays.
fresh_openclaw_home
mkdir -p "$HOME/.openclaw/bin" "$HOME/.openclaw/tools/node-v24.19.0/lib" "$HOME/.local/bin"
printf '#!/usr/bin/env bash\nexec "%s/.openclaw/tools/node/bin/node" "%s/.openclaw/tools/node-v24.19.0/lib/node_modules/openclaw/dist/entry.js" "$@"\n' "$HOME" "$HOME" >"$HOME/.openclaw/bin/openclaw"
ln -s "$HOME/.openclaw/bin/openclaw" "$HOME/.local/bin/openclaw"
"$ROOT/bin/omarchy-remove-ai-openclaw" >/dev/null
for gone in .openclaw/tools .openclaw/bin .local/bin/openclaw; do
[[ ! -e $HOME/$gone && ! -L $HOME/$gone ]] || fail "OpenClaw removal deletes the runtime it set up" "$gone"
done
[[ -f $HOME/.openclaw/openclaw.json ]] || fail "OpenClaw removal deletes the runtime it set up" "state went with it"
fresh_openclaw_home
mkdir -p "$HOME/.openclaw/bin" "$HOME/.openclaw/tools" "$HOME/.local/bin"
printf '#!/bin/bash\nexec /opt/openclaw/openclaw.mjs "$@"\n' >"$HOME/.openclaw/bin/openclaw"
ln -s /opt/openclaw/openclaw "$HOME/.local/bin/openclaw"
"$ROOT/bin/omarchy-remove-ai-openclaw" >/dev/null
[[ -f $HOME/.openclaw/bin/openclaw && -d $HOME/.openclaw/tools && -L $HOME/.local/bin/openclaw ]] ||
fail "OpenClaw removal deletes the runtime it set up" "someone else's install went with it"
pass "OpenClaw removal deletes the runtime it set up, and only that"
# A CLI that knows `gateway uninstall` owns the teardown; the manual systemd
# fallback must not run.
cat >"$tmp_dir/bin/openclaw" <<'SCRIPT'