Skip to content

Commit f92c54f

Browse files
authored
fix: support OPENCLAW_HOME env var (#275)
fix: support OPENCLAW_HOME for non-standard OpenClaw paths\n\nAdds get_openclaw_home() helper in scripts/utils.py and updates all\ninstall/runtime scripts to resolve OpenClaw home from OPENCLAW_HOME\nenvironment variable with fallback to ~/.openclaw.\n\nCloses #271
1 parent 65c09cb commit f92c54f

File tree

8 files changed

+47
-27
lines changed

8 files changed

+47
-27
lines changed

install.ps1

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
$ErrorActionPreference = "Stop"
77

88
$REPO_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path
9-
$OC_HOME = Join-Path $env:USERPROFILE ".openclaw"
9+
$OC_HOME = if ($env:OPENCLAW_HOME) { $env:OPENCLAW_HOME } else { Join-Path $env:USERPROFILE ".openclaw" }
1010
$OC_CFG = Join-Path $OC_HOME "openclaw.json"
1111

1212
function Write-Banner {
@@ -118,7 +118,10 @@ function Register-Agents {
118118
$pyScript = @"
119119
import json, pathlib, sys, os
120120
121-
cfg_path = pathlib.Path(os.environ['USERPROFILE']) / '.openclaw' / 'openclaw.json'
121+
oc_home = pathlib.Path(
122+
os.environ.get('OPENCLAW_HOME', str(pathlib.Path(os.environ['USERPROFILE']) / '.openclaw'))
123+
).expanduser()
124+
cfg_path = oc_home / 'openclaw.json'
122125
cfg = json.loads(cfg_path.read_text(encoding='utf-8'))
123126
124127
AGENTS = [
@@ -142,7 +145,7 @@ existing_ids = {a['id'] for a in agents_list}
142145
added = 0
143146
for ag in AGENTS:
144147
ag_id = ag['id']
145-
ws = str(pathlib.Path(os.environ['USERPROFILE']) / f'.openclaw/workspace-{ag_id}')
148+
ws = str(oc_home / f'workspace-{ag_id}')
146149
if ag_id not in existing_ids:
147150
entry = {'id': ag_id, 'workspace': ws, **{k:v for k,v in ag.items() if k!='id'}}
148151
agents_list.append(entry)

install.sh

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
set -e
66

77
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8-
OC_HOME="$HOME/.openclaw"
8+
OC_HOME="${OPENCLAW_HOME:-$HOME/.openclaw}"
99
OC_CFG="$OC_HOME/openclaw.json"
1010

1111
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
@@ -129,9 +129,10 @@ register_agents() {
129129
log "已备份配置: $OC_CFG.bak.*"
130130

131131
python3 << 'PYEOF'
132-
import json, pathlib, sys
132+
import json, os as _os, pathlib, sys
133133
134-
cfg_path = pathlib.Path.home() / '.openclaw' / 'openclaw.json'
134+
oc_home = pathlib.Path(_os.environ.get('OPENCLAW_HOME', str(pathlib.Path.home() / '.openclaw'))).expanduser()
135+
cfg_path = oc_home / 'openclaw.json'
135136
cfg = json.loads(cfg_path.read_text())
136137
137138
AGENTS = [
@@ -155,7 +156,7 @@ existing_ids = {a['id'] for a in agents_list}
155156
added = 0
156157
for ag in AGENTS:
157158
ag_id = ag['id']
158-
ws = str(pathlib.Path.home() / f'.openclaw/workspace-{ag_id}')
159+
ws = str(oc_home / f'workspace-{ag_id}')
159160
if ag_id not in existing_ids:
160161
entry = {'id': ag_id, 'workspace': ws, **{k:v for k,v in ag.items() if k!='id'}}
161162
agents_list.append(entry)

scripts/apply_model_changes.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
"""应用 data/pending_model_changes.json → openclaw.json,并重启 Gateway"""
33
import json, pathlib, subprocess, datetime, shutil, logging, glob
44
from file_lock import atomic_json_write, atomic_json_read
5+
from utils import get_openclaw_home
56

67
log = logging.getLogger('model_change')
78
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(name)s] %(message)s', datefmt='%H:%M:%S')
89

910
BASE = pathlib.Path(__file__).parent.parent
1011
DATA = BASE / 'data'
11-
OPENCLAW_CFG = pathlib.Path.home() / '.openclaw' / 'openclaw.json'
12+
OPENCLAW_HOME = get_openclaw_home()
13+
OPENCLAW_CFG = OPENCLAW_HOME / 'openclaw.json'
1214
PENDING = DATA / 'pending_model_changes.json'
1315
CHANGE_LOG = DATA / 'model_change_log.json'
1416
MAX_BACKUPS = 10

scripts/skill_manager.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
from pathlib import Path
2727

2828
sys.path.insert(0, str(Path(__file__).parent))
29-
from utils import now_iso, safe_name, read_json
29+
from utils import get_openclaw_home, now_iso, safe_name, read_json
3030

31-
OCLAW_HOME = Path.home() / '.openclaw'
31+
OCLAW_HOME = get_openclaw_home()
3232

3333

3434
def _download_file(url: str, timeout: int = 30, retries: int = 3) -> str:
@@ -230,8 +230,8 @@ def remove_remote(agent_id: str, name: str) -> bool:
230230

231231
def _get_hub_url(skill_name):
232232
"""获取 skill 的 Hub URL,支持环境变量覆盖"""
233-
base = (Path.home() / '.openclaw' / 'skills-hub-url').read_text().strip() \
234-
if (Path.home() / '.openclaw' / 'skills-hub-url').exists() else None
233+
hub_url_file = OCLAW_HOME / 'skills-hub-url'
234+
base = hub_url_file.read_text().strip() if hub_url_file.exists() else None
235235
base = base or os.environ.get(_HUB_BASE_ENV) or OFFICIAL_SKILLS_HUB_BASE
236236
return f'{base.rstrip("/")}/{skill_name}/SKILL.md'
237237

@@ -306,7 +306,7 @@ def import_official_hub(agent_ids: list) -> bool:
306306
print(f' 1. 检查网络: curl -I {OFFICIAL_SKILLS_HUB_BASE}/code_review/SKILL.md')
307307
print(f' 2. 设置代理: export https_proxy=http://your-proxy:port')
308308
print(f' 3. 使用镜像: export {_HUB_BASE_ENV}=https://ghproxy.com/{OFFICIAL_SKILLS_HUB_BASE}')
309-
print(f' 4. 自定义源: echo "https://your-mirror/skills" > ~/.openclaw/skills-hub-url')
309+
print(f' 4. 自定义源: echo "https://your-mirror/skills" > {OCLAW_HOME / "skills-hub-url"}')
310310
print(f' 5. 单独重试: python3 scripts/skill_manager.py add-remote --agent <agent> --name <skill> --source <url>')
311311
return success == total
312312

scripts/sync_agent_config.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
"""
66
import json, os, pathlib, datetime, logging
77
from file_lock import atomic_json_write
8+
from utils import get_openclaw_home
89

910
log = logging.getLogger('sync_agent_config')
1011
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(name)s] %(message)s', datefmt='%H:%M:%S')
1112

1213
# Auto-detect project root (parent of scripts/)
1314
BASE = pathlib.Path(__file__).parent.parent
1415
DATA = BASE / 'data'
15-
OPENCLAW_CFG = pathlib.Path.home() / '.openclaw' / 'openclaw.json'
16+
OPENCLAW_HOME = get_openclaw_home()
17+
OPENCLAW_CFG = OPENCLAW_HOME / 'openclaw.json'
1618

1719
ID_LABEL = {
1820
'taizi': {'label': '太子', 'role': '太子', 'duty': '飞书消息分拣与回奏', 'emoji': '🤴'},
@@ -137,7 +139,7 @@ def main():
137139
if ag_id not in ID_LABEL:
138140
continue
139141
meta = ID_LABEL[ag_id]
140-
workspace = ag.get('workspace', str(pathlib.Path.home() / f'.openclaw/workspace-{ag_id}'))
142+
workspace = ag.get('workspace', str(OPENCLAW_HOME / f'workspace-{ag_id}'))
141143
if 'allowAgents' in ag:
142144
allow_agents = ag.get('allowAgents', []) or []
143145
else:
@@ -155,13 +157,13 @@ def main():
155157

156158
# 补充不在 openclaw.json agents list 中的 agent(兼容旧版 main)
157159
EXTRA_AGENTS = {
158-
'taizi': {'model': default_model, 'workspace': str(pathlib.Path.home() / '.openclaw/workspace-taizi'),
160+
'taizi': {'model': default_model, 'workspace': str(OPENCLAW_HOME / 'workspace-taizi'),
159161
'allowAgents': ['zhongshu']},
160-
'main': {'model': default_model, 'workspace': str(pathlib.Path.home() / '.openclaw/workspace-main'),
162+
'main': {'model': default_model, 'workspace': str(OPENCLAW_HOME / 'workspace-main'),
161163
'allowAgents': ['zhongshu','menxia','shangshu','hubu','libu','bingbu','xingbu','gongbu','libu_hr']},
162-
'zaochao': {'model': default_model, 'workspace': str(pathlib.Path.home() / '.openclaw/workspace-zaochao'),
164+
'zaochao': {'model': default_model, 'workspace': str(OPENCLAW_HOME / 'workspace-zaochao'),
163165
'allowAgents': []},
164-
'libu_hr': {'model': default_model, 'workspace': str(pathlib.Path.home() / '.openclaw/workspace-libu_hr'),
166+
'libu_hr': {'model': default_model, 'workspace': str(OPENCLAW_HOME / 'workspace-libu_hr'),
165167
'allowAgents': ['shangshu']},
166168
}
167169
for ag_id, extra in EXTRA_AGENTS.items():
@@ -265,7 +267,7 @@ def sync_scripts_to_workspaces():
265267
return
266268
synced = 0
267269
for proj_name, runtime_id in _SOUL_DEPLOY_MAP.items():
268-
ws_scripts = pathlib.Path.home() / f'.openclaw/workspace-{runtime_id}' / 'scripts'
270+
ws_scripts = OPENCLAW_HOME / f'workspace-{runtime_id}' / 'scripts'
269271
ws_scripts.mkdir(parents=True, exist_ok=True)
270272
for src_file in scripts_src.iterdir():
271273
if src_file.suffix not in ('.py', '.sh') or src_file.stem.startswith('__'):
@@ -277,7 +279,7 @@ def sync_scripts_to_workspaces():
277279
except Exception:
278280
continue
279281
# also sync to workspace-main for legacy compatibility
280-
ws_main_scripts = pathlib.Path.home() / '.openclaw/workspace-main/scripts'
282+
ws_main_scripts = OPENCLAW_HOME / 'workspace-main' / 'scripts'
281283
ws_main_scripts.mkdir(parents=True, exist_ok=True)
282284
for src_file in scripts_src.iterdir():
283285
if src_file.suffix not in ('.py', '.sh') or src_file.stem.startswith('__'):
@@ -300,7 +302,7 @@ def deploy_soul_files():
300302
src = agents_dir / proj_name / 'SOUL.md'
301303
if not src.exists():
302304
continue
303-
ws_dst = pathlib.Path.home() / f'.openclaw/workspace-{runtime_id}' / 'soul.md'
305+
ws_dst = OPENCLAW_HOME / f'workspace-{runtime_id}' / 'soul.md'
304306
ws_dst.parent.mkdir(parents=True, exist_ok=True)
305307
# 只在内容不同时更新(避免不必要的写入)
306308
src_text = src.read_text(encoding='utf-8', errors='ignore')
@@ -313,7 +315,7 @@ def deploy_soul_files():
313315
deployed += 1
314316
# 太子兼容:同步一份到 legacy main agent 目录
315317
if runtime_id == 'taizi':
316-
ag_dst = pathlib.Path.home() / '.openclaw/agents/main/SOUL.md'
318+
ag_dst = OPENCLAW_HOME / 'agents' / 'main' / 'SOUL.md'
317319
ag_dst.parent.mkdir(parents=True, exist_ok=True)
318320
try:
319321
ag_text = ag_dst.read_text(encoding='utf-8', errors='ignore')
@@ -322,7 +324,7 @@ def deploy_soul_files():
322324
if src_text != ag_text:
323325
ag_dst.write_text(src_text, encoding='utf-8')
324326
# 确保 sessions 目录存在
325-
sess_dir = pathlib.Path.home() / f'.openclaw/agents/{runtime_id}/sessions'
327+
sess_dir = OPENCLAW_HOME / 'agents' / runtime_id / 'sessions'
326328
sess_dir.mkdir(parents=True, exist_ok=True)
327329
if deployed:
328330
log.info(f'{deployed} SOUL.md files deployed')

scripts/sync_from_openclaw_runtime.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import traceback
77
import logging
88
from file_lock import atomic_json_write, atomic_json_read
9+
from utils import get_openclaw_home
910

1011
log = logging.getLogger('sync_runtime')
1112
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(name)s] %(message)s', datefmt='%H:%M:%S')
@@ -14,7 +15,7 @@
1415
DATA = BASE / 'data'
1516
DATA.mkdir(exist_ok=True)
1617
SYNC_STATUS = DATA / 'sync_status.json'
17-
SESSIONS_ROOT = pathlib.Path.home() / '.openclaw' / 'agents'
18+
SESSIONS_ROOT = get_openclaw_home() / 'agents'
1819

1920

2021
def write_status(**kwargs):

scripts/sync_officials_stats.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22
"""同步各官员统计数据 → data/officials_stats.json"""
33
import json, pathlib, datetime, logging
44
from file_lock import atomic_json_write
5+
from utils import get_openclaw_home
56

67
log = logging.getLogger('officials')
78
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(name)s] %(message)s', datefmt='%H:%M:%S')
89

910
BASE = pathlib.Path(__file__).resolve().parent.parent
1011
DATA = BASE / 'data'
11-
AGENTS_ROOT = pathlib.Path.home() / '.openclaw' / 'agents'
12-
OPENCLAW_CFG = pathlib.Path.home() / '.openclaw' / 'openclaw.json'
12+
OPENCLAW_HOME = get_openclaw_home()
13+
AGENTS_ROOT = OPENCLAW_HOME / 'agents'
14+
OPENCLAW_CFG = OPENCLAW_HOME / 'openclaw.json'
1315

1416
# Anthropic 定价(每1M token,美元)
1517
MODEL_PRICING = {

scripts/utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
三省六部 · 公共工具函数
44
避免 read_json / now_iso 等基础函数在多个脚本中重复定义
55
"""
6+
import os
67
import json, pathlib, datetime
78

89

@@ -14,6 +15,14 @@ def read_json(path, default=None):
1415
return default if default is not None else {}
1516

1617

18+
def get_openclaw_home() -> pathlib.Path:
19+
"""Return OpenClaw home directory, respecting OPENCLAW_HOME env var."""
20+
env = os.environ.get('OPENCLAW_HOME')
21+
if env:
22+
return pathlib.Path(env).expanduser()
23+
return pathlib.Path.home() / '.openclaw'
24+
25+
1726
def now_iso():
1827
"""返回 UTC ISO 8601 时间字符串(末尾 Z)"""
1928
return datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00', 'Z')

0 commit comments

Comments
 (0)