Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2026-02-26 07:55:38 -06:00
parent 363036e188
commit 9dc95e0f29
9 changed files with 610 additions and 30 deletions

View File

@ -19,9 +19,19 @@ cd /Volumes/Data/openclaw-setups/openclaw-setup-max
cat README.md
```
Existing install upgrade (in-place):
```bash
cd /Volumes/Data/openclaw-setups/openclaw-setup-max
bash ./scripts/update_openclaw.sh
```
This update workflow also reapplies schedule/budget guardrails and self-heals gateway LaunchAgent issues for external-drive state dirs.
Primary docs:
- `openclaw-setup-max/README.md`
- `openclaw-setup-max/PRD.md`
- `openclaw-setup-max/docs/operations/UPGRADING.md`
### Copilot setup

View File

@ -36,7 +36,7 @@ Operators need an easy way to:
- As an operator, I can run one command to switch to free local models immediately.
- As an operator, I can switch back to paid model for deep tasks.
- As an operator, I can set free mode from 9pm-7am automatically.
- As an operator, I can set free mode from 10pm-7am automatically.
- As an operator, I get warned if expensive model remains active too long.
- As a maintainer, I can copy this folder to another Mac and follow docs to bootstrap.
@ -104,6 +104,21 @@ Implementation:
Implementation:
- `README.md`
### FR-7 Existing-Install Upgrade Workflow
- Must provide one command for in-place upgrades on existing machines.
- Must back up OpenClaw config/session files before migration/update.
- Must run config migration (`openclaw doctor --fix`) before attempting update.
- Must update CLI using built-in updater with timeout + npm fallback.
- Must emit timestamped update logs for operator traceability.
- Must stop gateway before update and bring gateway back after update.
- Must ensure gateway LaunchAgent is loaded/running after update.
- If `~/.openclaw` is symlinked to `/Volumes/...`, must ensure gateway LaunchAgent logs use `/tmp/openclaw-gateway.launchd*.log` to avoid launchd `EX_CONFIG`.
- Must reinstall and kickstart budget/schedule guard LaunchAgents after update.
Implementation:
- `scripts/update_openclaw.sh`
- `setup/setup_openclaw_ollama.sh`
## 7) Non-Functional Requirements
- NFR-1 Reliability: scripts should be safe to run repeatedly.
@ -154,11 +169,14 @@ Outputs:
## 10) Acceptance Criteria
- AC-1: Running `bash ./scripts/model_profile_switch.sh free` changes default to `openrouter/qwen/qwen3-coder:free`.
- AC-2: Running `bash ./scripts/model_profile_switch.sh paid` restores default to `openrouter/qwen/qwen3-coder:free`.
- AC-3: With schedule enabled (`21`/`7`), profile changes correctly by local time window.
- AC-2: Running `bash ./scripts/model_profile_switch.sh paid` restores default to `openrouter/moonshotai/kimi-k2.5`.
- AC-3: With schedule enabled (`22`/`7`), profile changes correctly by local time window.
- AC-4: If operator manually drifts model during scheduled window, schedule guard re-aligns on next run.
- AC-5: Budget guard warns and reverts when high model remains active beyond thresholds.
- AC-6: New operator can follow README from zero knowledge and complete setup without editing scripts.
- AC-7: Running `bash ./scripts/update_openclaw.sh` upgrades an existing install and prints a healthy gateway/model status at completion.
- AC-8: After update, schedule + budget LaunchAgents are loaded and can run immediately when kickstarted.
- AC-9: Upgrade output lines include timestamps to support restart/audit troubleshooting.
## 11) Risks and Mitigations
@ -170,6 +188,8 @@ Outputs:
- Mitigation: prerequisite checks in README.
- Risk: user confusion between ChatGPT subscription and API billing.
- Mitigation: clear FAQ note in README.
- Risk: gateway LaunchAgent exits with `EX_CONFIG` when logs point into external-volume symlink paths.
- Mitigation: force gateway LaunchAgent stdout/stderr to `/tmp/openclaw-gateway.launchd*.log` when `~/.openclaw` resolves to `/Volumes/...`.
## 12) Rollout Plan

View File

@ -1,13 +1,13 @@
# OpenClaw Setup Max (Paid + Free Model Switching)
This workspace runs OpenClaw with:
- A paid high-quality model profile (`openrouter/qwen/qwen3-coder:free`)
- A paid high-quality model profile (`openrouter/moonshotai/kimi-k2.5`)
- A free OpenRouter profile
- One-command live switching
- Optional automatic day/night switching (example: free from 9pm-7am)
- Optional automatic day/night switching (example: free from 10pm-7am)
- Optional budget guard that warns and auto-reverts from expensive models
This guide is written for first-time users.
This guide covers both first-time setup and in-place upgrades.
## 0) Workspace Layout
@ -27,7 +27,7 @@ Everything else is grouped by function:
## 1) What You Get
- `paid` profile:
- Primary: `openrouter/qwen/qwen3-coder:free`
- Primary: `openrouter/moonshotai/kimi-k2.5`
- Fallbacks: `openrouter/qwen/qwen3-coder-next`, `openrouter/qwen/qwen3-14b`
- `free` profile:
- Primary: `openrouter/qwen/qwen3-coder:free`
@ -49,6 +49,9 @@ Profiles are defined in `config/model-profiles.config.json`.
- `scripts/install_model_schedule_guard_launchd.sh`: installs schedule LaunchAgent
- `scripts/install_model_budget_guard_launchd.sh`: installs budget LaunchAgent
- `scripts/install_local_model_guardrails.sh`: installs both LaunchAgents
- `scripts/update_openclaw.sh`: in-place OpenClaw upgrade + migration workflow
- `docs/operations/UPGRADING.md`: upgrade runbook for existing installs
- `docs/operations/troubleshooting.md`: known issue recovery commands
## 3) Prerequisites
@ -70,6 +73,7 @@ Script behavior:
- `openclaw`
- `jq`
- `python3`
- If `~/.openclaw` is symlinked to an external volume (`/Volumes/...`), setup patches gateway LaunchAgent logs to `/tmp/openclaw-gateway.launchd*.log` to avoid launchd `EX_CONFIG` failures.
- It will prompt before:
- Installing Ollama (if missing)
- Pulling local Ollama models (large downloads)
@ -84,6 +88,45 @@ AUTO_YES=true bash ./setup/setup_openclaw_ollama.sh
PULL_LOCAL_MODELS=false bash ./setup/setup_openclaw_ollama.sh
```
## 3A) Update Existing Install (In-place)
Use this for upgrades on an already-working machine:
```bash
cd /Volumes/Data/openclaw-setups/openclaw-setup-max
bash ./scripts/update_openclaw.sh
```
What it handles:
- Backup of `~/.openclaw/openclaw.json` + main `sessions.json`
- Config migration (`openclaw doctor --fix --non-interactive`)
- CLI update with timeout + npm fallback
- Timestamped step logging in terminal output
- Explicit gateway stop/start
- Gateway LaunchAgent self-heal (install if missing)
- External-drive launchd log-path patch (`/tmp/openclaw-gateway.launchd*.log`)
- Reinstall + immediate kickstart of budget/schedule guards
- Final model/profile/gateway verification output
Useful options:
```bash
# Switch update channel
bash ./scripts/update_openclaw.sh --channel beta
# Pin one update run to a version/tag
bash ./scripts/update_openclaw.sh --tag 2026.2.24
# Increase built-in updater timeout (seconds)
UPDATE_MAX_SECONDS=900 bash ./scripts/update_openclaw.sh
# Save a timestamped run log file
bash ./scripts/update_openclaw.sh 2>&1 | tee "/tmp/openclaw-update-$(date +%Y%m%d-%H%M%S).log"
```
Detailed runbook:
- `docs/operations/UPGRADING.md`
## 4) Quick Start (Most Common)
Use env-driven sync + hygiene workflow (recommended after any model changes):
@ -145,7 +188,7 @@ If you want config-only change (no live session message), use:
bash ./scripts/model_profile_switch.sh free --no-live
```
## 6) Enable Schedule (Example: Free 9pm-7am)
## 6) Enable Schedule (Example: Free 10pm-7am)
1. Edit config:
@ -162,7 +205,7 @@ open config/model-schedule.config.json
"dayProfile": "paid",
"nightProfile": "free",
"dayStartHour": 7,
"nightStartHour": 21
"nightStartHour": 22
}
```
@ -224,7 +267,7 @@ bash ./scripts/install_local_model_guardrails.sh
Budget behavior from `config/model-budget-guard.config.json`:
- Warn after 2 minutes on high-cost model
- Auto-revert to `ollama/qwen3:14b` after 45 minutes
- Auto-revert to `openrouter/qwen/qwen3-coder:free` after 45 minutes
- Prevent repeated spam with minimum warning interval
## 9) View Current LaunchAgents
@ -239,6 +282,7 @@ Logs:
```bash
tail -f /tmp/openclaw-model-schedule-guard.log /tmp/openclaw-model-schedule-guard.err.log
tail -f /tmp/openclaw-model-budget-guard.log /tmp/openclaw-model-budget-guard.err.log
tail -f /tmp/openclaw-gateway.launchd.log /tmp/openclaw-gateway.launchd.err.log
```
## 10) Manual Switching Tips

View File

@ -49,6 +49,13 @@ openclaw gateway stop
openclaw gateway restart
```
**Repo-specific in-place upgrade (existing install):**
```bash
cd /Volumes/Data/openclaw-setups/openclaw-setup-max
bash ./scripts/update_openclaw.sh
```
This script stops gateway, upgrades OpenClaw, reapplies schedule/budget guardrails, and restarts gateway.
### Directory Structure
**Workspace Location:**

View File

@ -2,7 +2,7 @@
This setup adds two model profiles:
- `paid` -> `openrouter/qwen/qwen3-coder:free` with OpenRouter fallbacks
- `paid` -> `openrouter/moonshotai/kimi-k2.5` with OpenRouter fallbacks
- `free` -> `openrouter/qwen/qwen3-coder:free` with OpenRouter fallback
## Recommended workflow (prevents legacy drift)
@ -40,7 +40,7 @@ Notes:
bash ./scripts/model_profile_switch.sh status
```
## Schedule auto-switching (example: free from 9pm-7am)
## Schedule auto-switching (example: free from 10pm-7am)
1. Edit config:
@ -57,7 +57,7 @@ Ensure:
"dayProfile": "paid",
"nightProfile": "free",
"dayStartHour": 7,
"nightStartHour": 21
"nightStartHour": 22
}
```
@ -100,3 +100,14 @@ bash ./scripts/install_local_model_guardrails.sh
launchctl bootout gui/$(id -u)/ai.openclaw.local.model-schedule-guard 2>/dev/null || true
launchctl bootout gui/$(id -u)/ai.openclaw.local.model-budget-guard 2>/dev/null || true
```
## After OpenClaw upgrades
Use the update workflow so guards/profile state are reapplied automatically:
```bash
cd /Volumes/Data/openclaw-setups/openclaw-setup-max
bash ./scripts/update_openclaw.sh
```
The update script reinstalls both guard LaunchAgents and kickstarts them immediately, so budget/profile logic is re-evaluated in the same run.

View File

@ -0,0 +1,66 @@
# OpenClaw Upgrade Runbook (Existing Install)
Use this when OpenClaw is already installed and you want to upgrade in place.
## Recommended command
```bash
cd /Volumes/Data/openclaw-setups/openclaw-setup-max
bash ./scripts/update_openclaw.sh
```
## What `update_openclaw.sh` does
- Backs up:
- `~/.openclaw/openclaw.json`
- `~/.openclaw/agents/main/sessions/sessions.json`
- Stops local model guard LaunchAgents and gateway before update
- Runs `openclaw doctor --fix --non-interactive`
- Runs `openclaw update` with timeout protection
- Falls back to `npm install -g openclaw@...` if updater hangs/fails
- Prints timestamped step logs for restart/update traceability
- Starts gateway and installs service if missing
- If `~/.openclaw` is symlinked to `/Volumes/...`, patches gateway LaunchAgent logs to:
- `/tmp/openclaw-gateway.launchd.log`
- `/tmp/openclaw-gateway.launchd.err.log`
- Reinstalls schedule + budget guard LaunchAgents
- Kickstarts both guards immediately so profile/budget logic is applied now
- Prints gateway + model/guard status summary
## Useful options
```bash
# switch update channel
bash ./scripts/update_openclaw.sh --channel beta
# one-off version/tag
bash ./scripts/update_openclaw.sh --tag 2026.2.24
# increase built-in update timeout (seconds)
UPDATE_MAX_SECONDS=900 bash ./scripts/update_openclaw.sh
# capture a timestamped run log file
bash ./scripts/update_openclaw.sh 2>&1 | tee "/tmp/openclaw-update-$(date +%Y%m%d-%H%M%S).log"
```
## Verify after upgrade
```bash
openclaw --version
openclaw gateway status
openclaw gateway health --json
bash ./scripts/model_profile_switch.sh status
launchctl print gui/$(id -u)/ai.openclaw.local.model-schedule-guard | rg 'last exit code|state'
launchctl print gui/$(id -u)/ai.openclaw.local.model-budget-guard | rg 'last exit code|state'
ls -lT /tmp/openclaw-gateway.launchd.log /tmp/openclaw-gateway.launchd.err.log
```
## If gateway is loaded but not running
```bash
cd /Volumes/Data/openclaw-setups/openclaw-setup-max
bash ./scripts/update_openclaw.sh
```
If still failing, see:
- `docs/operations/troubleshooting.md`

View File

@ -106,30 +106,30 @@ openclaw gateway run --force
- Launchd reports exit code `78: EX_CONFIG`.
### Fix that worked
- Ensure LaunchAgent command includes `gateway run`.
- Keep logs in `/tmp` (avoids potential external-volume path issues).
- Preferred: run the upgrade workflow, which self-heals LaunchAgent + guards.
- `update_openclaw.sh` prints timestamped logs so restart timing is visible during the run.
```bash
cd /Volumes/Data/openclaw-setups/openclaw-setup-max
bash ./scripts/update_openclaw.sh
```
- Manual fallback:
1. Keep LaunchAgent logs in `/tmp` (avoids external-volume symlink path issues).
2. Reinstall and restart the gateway service.
File:
- `~/Library/LaunchAgents/ai.openclaw.gateway.plist`
Expected ProgramArguments section:
```xml
<array>
<string>/opt/homebrew/bin/node</string>
<string>/opt/homebrew/lib/node_modules/openclaw/dist/index.js</string>
<string>gateway</string>
<string>run</string>
<string>--port</string>
<string>18789</string>
</array>
Set log paths:
```bash
/usr/libexec/PlistBuddy -c "Set :StandardOutPath /tmp/openclaw-gateway.launchd.log" ~/Library/LaunchAgents/ai.openclaw.gateway.plist
/usr/libexec/PlistBuddy -c "Set :StandardErrorPath /tmp/openclaw-gateway.launchd.err.log" ~/Library/LaunchAgents/ai.openclaw.gateway.plist
```
Expected log paths:
- `/tmp/openclaw-gateway.launchd.log`
- `/tmp/openclaw-gateway.launchd.err.log`
Reload service:
```bash
openclaw gateway install --force
launchctl bootout gui/$(id -u)/ai.openclaw.gateway 2>/dev/null || true
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.openclaw.gateway.plist
launchctl kickstart -k gui/$(id -u)/ai.openclaw.gateway
@ -137,8 +137,10 @@ launchctl kickstart -k gui/$(id -u)/ai.openclaw.gateway
Verify:
```bash
openclaw status --deep
launchctl print gui/$(id -u)/ai.openclaw.gateway | rg 'state =|last exit code|runs ='
openclaw gateway status
openclaw gateway health --json
ls -lT /tmp/openclaw-gateway.launchd.log /tmp/openclaw-gateway.launchd.err.log
```
## 6) docs/context/BOOT.md hook setup

View File

@ -0,0 +1,365 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'USAGE'
Usage:
bash ./scripts/update_openclaw.sh [options]
Options:
--channel <stable|beta|dev> Update channel (default: stable)
--tag <dist-tag|version> One-off npm tag/version (example: beta, 2026.2.24)
--skip-guard-stop Do not stop local model guard LaunchAgents before upgrade
--skip-guard-reinstall Do not reinstall local model guard LaunchAgents after upgrade
-h, --help Show this help
Examples:
bash ./scripts/update_openclaw.sh
bash ./scripts/update_openclaw.sh --channel beta
bash ./scripts/update_openclaw.sh --tag 2026.2.24
USAGE
}
now_ts() { date '+%Y-%m-%d %H:%M:%S%z'; }
log() { printf '[%s] [update-openclaw] %s\n' "$(now_ts)" "$*"; }
warn() { printf '[%s] [update-openclaw] WARN: %s\n' "$(now_ts)" "$*" >&2; }
err() { printf '[%s] [update-openclaw] ERROR: %s\n' "$(now_ts)" "$*" >&2; }
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
err "Required command missing: $1"
exit 1
fi
}
run_with_timeout() {
local timeout_s="$1"
shift
local start_ts elapsed pid
start_ts="$(date +%s)"
"$@" &
pid=$!
while kill -0 "$pid" 2>/dev/null; do
elapsed="$(( $(date +%s) - start_ts ))"
if (( elapsed >= timeout_s )); then
warn "Command timed out after ${timeout_s}s: $*"
kill -INT "$pid" 2>/dev/null || true
sleep 2
kill -TERM "$pid" 2>/dev/null || true
sleep 1
kill -KILL "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
return 124
fi
sleep 1
done
wait "$pid"
}
extract_version() {
local raw
raw="$("$1" --version 2>&1 || true)"
printf '%s\n' "$raw" | grep -Eo '[0-9]{4}\.[0-9]+\.[0-9]+' | head -n1
}
print_guard_status() {
local label="$1"
launchctl print "gui/$(id -u)/$label" 2>/dev/null | \
grep -E 'state =|last exit code|runs =|path =|program =' || true
}
is_launchagent_loaded() {
local label="$1"
launchctl print "gui/$(id -u)/$label" >/dev/null 2>&1
}
is_gateway_launchagent_running() {
local label="$1"
launchctl print "gui/$(id -u)/$label" 2>/dev/null | grep -q 'state = running'
}
fix_gateway_launchagent_log_paths_for_external_state() {
local label="$1"
local plist="$HOME/Library/LaunchAgents/$label.plist"
local state_dir_link_target=""
if [[ ! -f "$plist" ]]; then
return 0
fi
# launchd may fail with EX_CONFIG when stdout/stderr paths point into
# ~/.openclaw on an external-volume symlink. Force local /tmp logs.
if [[ -L "$HOME/.openclaw" ]]; then
state_dir_link_target="$(readlink "$HOME/.openclaw" || true)"
fi
if [[ "$state_dir_link_target" != /Volumes/* ]]; then
return 0
fi
if ! command -v /usr/libexec/PlistBuddy >/dev/null 2>&1; then
warn "PlistBuddy not found; skipping LaunchAgent log-path patch."
return 0
fi
local desired_out="/tmp/openclaw-gateway.launchd.log"
local desired_err="/tmp/openclaw-gateway.launchd.err.log"
local current_out=""
local current_err=""
current_out="$(/usr/libexec/PlistBuddy -c 'Print :StandardOutPath' "$plist" 2>/dev/null || true)"
current_err="$(/usr/libexec/PlistBuddy -c 'Print :StandardErrorPath' "$plist" 2>/dev/null || true)"
if [[ "$current_out" == "$desired_out" && "$current_err" == "$desired_err" ]]; then
return 0
fi
log "Patching gateway LaunchAgent logs to local /tmp paths (external state dir detected)"
/usr/libexec/PlistBuddy -c "Set :StandardOutPath $desired_out" "$plist"
/usr/libexec/PlistBuddy -c "Set :StandardErrorPath $desired_err" "$plist"
launchctl bootout "gui/$(id -u)/$label" 2>/dev/null || true
launchctl bootstrap "gui/$(id -u)" "$plist"
launchctl kickstart -k "gui/$(id -u)/$label"
}
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
CHANNEL="stable"
TAG=""
STOP_GUARDS="true"
REINSTALL_GUARDS="true"
UPDATE_MAX_SECONDS="${UPDATE_MAX_SECONDS:-600}"
while (( "$#" )); do
case "$1" in
--channel)
shift
if [[ $# -eq 0 ]]; then
err "--channel requires a value"
usage
exit 1
fi
CHANNEL="$1"
;;
--tag)
shift
if [[ $# -eq 0 ]]; then
err "--tag requires a value"
usage
exit 1
fi
TAG="$1"
;;
--skip-guard-stop)
STOP_GUARDS="false"
;;
--skip-guard-reinstall)
REINSTALL_GUARDS="false"
;;
-h|--help)
usage
exit 0
;;
*)
err "Unknown argument: $1"
usage
exit 1
;;
esac
shift
done
case "$CHANNEL" in
stable|beta|dev) ;;
*)
err "Invalid --channel '$CHANNEL'. Expected stable, beta, or dev."
exit 1
;;
esac
if ! [[ "$UPDATE_MAX_SECONDS" =~ ^[0-9]+$ ]] || [[ "$UPDATE_MAX_SECONDS" -lt 1 ]]; then
err "UPDATE_MAX_SECONDS must be a positive integer."
exit 1
fi
require_cmd openclaw
require_cmd launchctl
require_cmd cp
require_cmd mkdir
require_cmd date
if ! command -v npm >/dev/null 2>&1; then
warn "npm not found; fallback npm upgrade path will be unavailable."
fi
ts="$(date +%Y%m%d-%H%M%S)"
backup_dir="$HOME/.openclaw/backups/update-$ts"
mkdir -p "$backup_dir"
config_file="$HOME/.openclaw/openclaw.json"
sessions_file="$HOME/.openclaw/agents/main/sessions/sessions.json"
log "Backing up OpenClaw files to: $backup_dir"
if [[ -f "$config_file" ]]; then
cp "$config_file" "$backup_dir/openclaw.json"
else
warn "Config not found: $config_file"
fi
if [[ -f "$sessions_file" ]]; then
cp "$sessions_file" "$backup_dir/sessions.json"
else
warn "Sessions file not found: $sessions_file"
fi
if [[ "$STOP_GUARDS" == "true" ]]; then
log "Stopping local model guard LaunchAgents to avoid config races during update"
launchctl bootout "gui/$(id -u)/ai.openclaw.local.model-budget-guard" 2>/dev/null || true
launchctl bootout "gui/$(id -u)/ai.openclaw.local.model-schedule-guard" 2>/dev/null || true
fi
log "Stopping OpenClaw gateway before update"
if ! openclaw gateway stop; then
warn "Gateway stop returned non-zero (it may already be stopped). Continuing."
fi
current_version="$(extract_version openclaw || true)"
if [[ -n "$current_version" ]]; then
log "Current OpenClaw version: $current_version"
else
warn "Could not parse current OpenClaw version from 'openclaw --version'."
fi
if command -v npm >/dev/null 2>&1; then
latest_npm_version="$(npm view openclaw version 2>/dev/null || true)"
if [[ -n "$latest_npm_version" ]]; then
log "Latest npm OpenClaw version: $latest_npm_version"
fi
fi
log "Running doctor fix for schema/config migration"
if ! openclaw doctor --fix --non-interactive; then
warn "Doctor fix returned non-zero. Continuing with upgrade."
fi
update_cmd=(openclaw update --yes --no-restart --channel "$CHANNEL")
if [[ -n "$TAG" ]]; then
update_cmd+=(--tag "$TAG")
fi
log "Running OpenClaw updater: ${update_cmd[*]}"
if ! run_with_timeout "$UPDATE_MAX_SECONDS" "${update_cmd[@]}"; then
warn "Built-in updater failed."
if ! command -v npm >/dev/null 2>&1; then
err "npm not available for fallback update."
exit 1
fi
pkg="openclaw@latest"
if [[ -n "$TAG" ]]; then
pkg="openclaw@$TAG"
fi
log "Falling back to npm global install: npm install -g $pkg"
npm install -g "$pkg"
fi
new_version="$(extract_version openclaw || true)"
if [[ -n "$new_version" ]]; then
log "Updated OpenClaw version: $new_version"
else
warn "Could not parse updated version from 'openclaw --version'."
fi
log "Starting OpenClaw gateway after update"
if ! openclaw gateway start; then
err "Gateway failed to start after update."
exit 1
fi
# Some OpenClaw versions return success from "gateway start" even when the
# LaunchAgent is not installed/loaded; enforce a service-managed gateway.
gateway_label="ai.openclaw.gateway"
if ! launchctl print "gui/$(id -u)/$gateway_label" >/dev/null 2>&1; then
warn "Gateway LaunchAgent is not loaded after start; installing and retrying."
if ! openclaw gateway install --force; then
err "Failed to install gateway service."
exit 1
fi
if ! openclaw gateway start; then
err "Gateway failed to start after installing service."
exit 1
fi
if ! launchctl print "gui/$(id -u)/$gateway_label" >/dev/null 2>&1; then
err "Gateway LaunchAgent is still not loaded after install/start."
exit 1
fi
fi
fix_gateway_launchagent_log_paths_for_external_state "$gateway_label"
if ! is_gateway_launchagent_running "$gateway_label"; then
warn "Gateway LaunchAgent is loaded but not running; attempting kickstart."
launchctl kickstart -k "gui/$(id -u)/$gateway_label" || true
sleep 2
fi
if ! is_gateway_launchagent_running "$gateway_label"; then
err "Gateway LaunchAgent is still not running."
launchctl print "gui/$(id -u)/$gateway_label" | grep -E 'state =|last exit code|runs =|path =|program =' || true
exit 1
fi
log "Running post-upgrade health checks"
openclaw doctor --non-interactive || true
openclaw gateway status || true
openclaw models status || true
if [[ "$REINSTALL_GUARDS" == "true" ]]; then
installer="$SCRIPT_DIR/install_local_model_guardrails.sh"
if [[ -x "$installer" ]]; then
log "Reinstalling local model guard LaunchAgents"
bash "$installer"
# Run both periodic guards immediately after reinstall so profile/budget
# state is refreshed in this update run (not only on the next interval).
launchctl kickstart -k "gui/$(id -u)/ai.openclaw.local.model-schedule-guard" 2>/dev/null || true
launchctl kickstart -k "gui/$(id -u)/ai.openclaw.local.model-budget-guard" 2>/dev/null || true
sleep 2
if ! is_launchagent_loaded "ai.openclaw.local.model-schedule-guard"; then
err "Schedule guard LaunchAgent is not loaded after reinstall."
exit 1
fi
if ! is_launchagent_loaded "ai.openclaw.local.model-budget-guard"; then
err "Budget guard LaunchAgent is not loaded after reinstall."
exit 1
fi
else
warn "Guard installer missing or not executable: $installer"
fi
fi
log "Local guard status summary"
print_guard_status "ai.openclaw.local.model-budget-guard"
print_guard_status "ai.openclaw.local.model-schedule-guard"
if [[ -x "$SCRIPT_DIR/model_profile_switch.sh" ]]; then
log "Profile status"
bash "$SCRIPT_DIR/model_profile_switch.sh" status || true
fi
cat <<MSG
Upgrade complete.
- Backup directory: $backup_dir
- Channel: $CHANNEL
- Tag override: ${TAG:-none}
- Current version: ${new_version:-unknown}
If needed, inspect logs:
tail -n 80 /tmp/openclaw-model-budget-guard.log /tmp/openclaw-model-budget-guard.err.log
tail -n 80 /tmp/openclaw-model-schedule-guard.log /tmp/openclaw-model-schedule-guard.err.log
MSG

View File

@ -115,11 +115,59 @@ node_major_version() {
node -v 2>/dev/null | sed 's/^v//' | cut -d. -f1
}
fix_gateway_launchagent_logs_for_external_state() {
local plist="$HOME/Library/LaunchAgents/ai.openclaw.gateway.plist"
local desired_out="/tmp/openclaw-gateway.launchd.log"
local desired_err="/tmp/openclaw-gateway.launchd.err.log"
if [[ ! -L "$HOME/.openclaw" ]]; then
return 0
fi
local state_dir_link_target
state_dir_link_target="$(readlink "$HOME/.openclaw" || true)"
if [[ "$state_dir_link_target" != /Volumes/* ]]; then
return 0
fi
if [[ ! -f "$plist" ]]; then
return 0
fi
if ! command -v /usr/libexec/PlistBuddy >/dev/null 2>&1; then
log_warn "PlistBuddy not found; skipping gateway LaunchAgent log-path patch."
return 0
fi
local current_out current_err
current_out="$(/usr/libexec/PlistBuddy -c 'Print :StandardOutPath' "$plist" 2>/dev/null || true)"
current_err="$(/usr/libexec/PlistBuddy -c 'Print :StandardErrorPath' "$plist" 2>/dev/null || true)"
if [[ "$current_out" == "$desired_out" && "$current_err" == "$desired_err" ]]; then
return 0
fi
log_warn "Patching gateway LaunchAgent logs to /tmp (external OpenClaw state dir detected)."
/usr/libexec/PlistBuddy -c "Set :StandardOutPath $desired_out" "$plist"
/usr/libexec/PlistBuddy -c "Set :StandardErrorPath $desired_err" "$plist"
launchctl bootout "gui/$(id -u)/ai.openclaw.gateway" 2>/dev/null || true
launchctl bootstrap "gui/$(id -u)" "$plist" 2>/dev/null || true
launchctl kickstart -k "gui/$(id -u)/ai.openclaw.gateway" 2>/dev/null || true
}
link_data_dir() {
local source="$1"
local target="$2"
local label="$3"
# Safety: target must be a real directory on the external volume, not a symlink.
if [ -L "$target" ]; then
local target_link
target_link="$(readlink "$target" || true)"
log_err "ERROR: ${label} target is a symlink: ${target} -> ${target_link}"
log_err "Expected a real directory on external storage. Remove/fix target symlink and re-run."
exit 1
fi
mkdir -p "$target"
if [ -L "$source" ]; then
@ -150,6 +198,12 @@ link_data_dir() {
ln -s "$target" "$source"
log_info "${label}: symlink created ${source} -> ${target}"
# Verify direction is exactly source -> target.
if [ ! -L "$source" ] || [ "$(readlink "$source")" != "$target" ]; then
log_err "ERROR: ${label} symlink direction check failed for ${source} -> ${target}"
exit 1
fi
}
ensure_ollama_running() {
@ -201,6 +255,7 @@ fi
mkdir -p "$OLLAMA_DATA_TARGET" "$OPENCLAW_DATA_TARGET"
link_data_dir "$HOME/.ollama" "$OLLAMA_DATA_TARGET" "Ollama"
link_data_dir "$HOME/.openclaw" "$OPENCLAW_DATA_TARGET" "OpenClaw"
fix_gateway_launchagent_logs_for_external_state
# Step 2: Install / verify Ollama
if ! command -v ollama >/dev/null 2>&1; then