Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
363036e188
commit
9dc95e0f29
10
README.md
10
README.md
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:**
|
||||
|
||||
@ -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.
|
||||
|
||||
66
openclaw-setup-max/docs/operations/UPGRADING.md
Normal file
66
openclaw-setup-max/docs/operations/UPGRADING.md
Normal 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`
|
||||
@ -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
|
||||
|
||||
365
openclaw-setup-max/scripts/update_openclaw.sh
Executable file
365
openclaw-setup-max/scripts/update_openclaw.sh
Executable 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
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user