a8926764 commited on
Commit
42eda82
·
verified ·
1 Parent(s): c874a0f

Upload 2 files

Browse files
Files changed (2) hide show
  1. start-openclaw.sh +184 -0
  2. sync.py +193 -0
start-openclaw.sh ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # OpenClaw 主启动脚本
3
+ # 功能:恢复配置 → 生成配置文件 → 启动服务
4
+
5
+ set -e # 遇到错误立即退出
6
+
7
+ # 配置环境
8
+ OPENCLAW_DIR="/root/.openclaw"
9
+ CONFIG_FILE="$OPENCLAW_DIR/openclaw.json"
10
+ SYNC_SCRIPT="/usr/local/bin/sync.py"
11
+ LOG_FILE="/var/log/openclaw-startup.log"
12
+
13
+ # 日志函数
14
+ log() {
15
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
16
+ echo "$1"
17
+ }
18
+
19
+ # 错误处理函数
20
+ error_exit() {
21
+ log "错误: $1"
22
+ exit 1
23
+ }
24
+
25
+ # 确保目录存在
26
+ ensure_directories() {
27
+ log "创建必要的目录结构..."
28
+ mkdir -p "$OPENCLAW_DIR"
29
+ mkdir -p "$OPENCLAW_DIR/agents/main/agent"
30
+ mkdir -p "$OPENCLAW_DIR/agents/main/sessions"
31
+ mkdir -p "$OPENCLAW_DIR/credentials"
32
+ mkdir -p "$(dirname "$LOG_FILE")"
33
+ }
34
+
35
+ # 从环境变量生成 OpenClaw 配置文件[6,7](@ref)
36
+ generate_config() {
37
+ log "生成 OpenClaw 配置文件..."
38
+
39
+ # 获取环境变量,设置默认值
40
+ local PORT="${PORT:-18789}"
41
+ local GATEWAY_PASSWORD="${OPENCLAW_GATEWAY_PASSWORD:-openclaw}"
42
+ local NVIDIA_API_KEY="${NVAPI_KEY}"
43
+ local MODEL_ID="${MODEL_ID:-moonshotai/kimi-k2.5}"
44
+
45
+ if [ -z "$NVIDIA_API_KEY" ]; then
46
+ log "警告: NVAPI_KEY 环境变量未设置,NVIDIA 模型将不可用"
47
+ fi
48
+
49
+ # 创建配置文件
50
+ cat > "$CONFIG_FILE" << EOF
51
+ {
52
+ "gateway": {
53
+ "mode": "local",
54
+ "bind": "lan",
55
+ "port": $PORT,
56
+ "trustedProxies": ["0.0.0.0/0"],
57
+ "auth": {
58
+ "mode": "token",
59
+ "token": "$GATEWAY_PASSWORD"
60
+ },
61
+ "controlUi": {
62
+ "allowInsecureAuth": true
63
+ }
64
+ },
65
+ "models": {
66
+ "providers": {
67
+ "nvidia": {
68
+ "baseUrl": "https://integrate.api.nvidia.com/v1",
69
+ "apiKey": "$NVIDIA_API_KEY",
70
+ "api": "openai-completions",
71
+ "models": [
72
+ {
73
+ "id": "$MODEL_ID",
74
+ "name": "Kimi k2.5",
75
+ "reasoning": false,
76
+ "input": ["text"],
77
+ "contextWindow": 200000,
78
+ "maxTokens": 8192
79
+ }
80
+ ]
81
+ }
82
+ }
83
+ },
84
+ "agents": {
85
+ "defaults": {
86
+ "maxConcurrent": 4,
87
+ "subagents": {
88
+ "maxConcurrent": 8
89
+ }
90
+ }
91
+ },
92
+ "meta": {
93
+ "generatedOn": "$(date -Iseconds)",
94
+ "source": "auto-generated"
95
+ }
96
+ }
97
+ EOF
98
+
99
+ # 设置正确的文件权限
100
+ chmod 600 "$CONFIG_FILE"
101
+ chmod 700 "$OPENCLAW_DIR"
102
+
103
+ log "OpenClaw 配置文件已生成: $CONFIG_FILE"
104
+ }
105
+
106
+ # 启动数据同步服务
107
+ start_sync_service() {
108
+ log "启动配置同步服务..."
109
+
110
+ if [ -f "$SYNC_SCRIPT" ]; then
111
+ # 首次同步:从Dataset恢复配置
112
+ if python3 "$SYNC_SCRIPT" download; then
113
+ log "配置恢复成功"
114
+
115
+ # 如果Dataset中有配置,使用Dataset的配置
116
+ if [ -f "$CONFIG_FILE" ]; then
117
+ log "使用Dataset中的配置文件"
118
+ return
119
+ fi
120
+ else
121
+ log "配置恢复失败,将使用新配置"
122
+ fi
123
+ else
124
+ log "同步脚本不存在: $SYNC_SCRIPT"
125
+ fi
126
+
127
+ # 生成新的配置文件
128
+ generate_config
129
+ }
130
+
131
+ # 启动 OpenClaw 网关服务
132
+ start_openclaw() {
133
+ local PORT="${PORT:-18789}"
134
+
135
+ log "启动 OpenClaw 网关服务..."
136
+
137
+ # 检查配置文件是否存在
138
+ if [ ! -f "$CONFIG_FILE" ]; then
139
+ error_exit "配置文件不存在: $CONFIG_FILE"
140
+ fi
141
+
142
+ # 验证配置文件语法
143
+ if ! python3 -m json.tool "$CONFIG_FILE" > /dev/null 2>&1; then
144
+ error_exit "配置文件语法错误"
145
+ fi
146
+
147
+ log "=== OpenClaw 启动配置 ==="
148
+ log "端口: $PORT"
149
+ log "配置文件: $CONFIG_FILE"
150
+ log "日志文件: $LOG_FILE"
151
+ log "=========================="
152
+
153
+ # 启动网关服务[6](@ref)
154
+ exec openclaw gateway run \
155
+ --port "$PORT" \
156
+ --allow-unconfigured \
157
+ --token "${OPENCLAW_GATEWAY_PASSWORD:-openclaw}"
158
+ }
159
+
160
+ # 主执行流程
161
+ main() {
162
+ log "=== 开始 OpenClaw 启动流程 ==="
163
+
164
+ # 执行步骤
165
+ ensure_directories
166
+ start_sync_service
167
+
168
+ # 如果同步脚本存在,启动后台同步服务
169
+ if [ -f "$SYNC_SCRIPT" ]; then
170
+ log "启动后台同步服务..."
171
+ python3 "$SYNC_SCRIPT" sync &
172
+ SYNC_PID=$!
173
+ log "后台同步服务已启动 (PID: $SYNC_PID)"
174
+ fi
175
+
176
+ # 启动 OpenClaw
177
+ start_openclaw
178
+ }
179
+
180
+ # 捕获信号,确保优雅退出
181
+ trap 'log "收到退出信号,正在关闭..."' TERM INT
182
+
183
+ # 运行主函数
184
+ main
sync.py ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ OpenClaw 配置同步脚本
4
+ 功能:从 Hugging Face Dataset 拉取配置,并定时推送变更回 Dataset
5
+ 作者:根据用户需求生成
6
+ 日期:2026-02-08
7
+ """
8
+
9
+ import os
10
+ import json
11
+ import time
12
+ import logging
13
+ import hashlib
14
+ from pathlib import Path
15
+ from huggingface_hub import HfApi, Repository, snapshot_download
16
+
17
+ # 配置日志
18
+ logging.basicConfig(
19
+ level=logging.INFO,
20
+ format='%(asctime)s - %(levelname)s - %(message)s'
21
+ )
22
+ logger = logging.getLogger(__name__)
23
+
24
+ class OpenClawConfigSync:
25
+ def __init__(self):
26
+ self.hf_token = os.getenv('HF_TOKEN', '')
27
+ self.dataset_repo = os.getenv('HF_DATASET', '')
28
+ self.local_config_dir = Path('/root/.openclaw')
29
+ self.sync_interval = 300 # 5分钟同步一次
30
+
31
+ if not self.hf_token or not self.dataset_repo:
32
+ logger.error('HF_TOKEN 或 HF_DATASET 环境变量未设置')
33
+ raise ValueError('缺少必要的环境变量')
34
+
35
+ self.api = HfApi(token=self.hf_token)
36
+ self.repo_dir = Path('/tmp/openclaw_dataset')
37
+
38
+ def calculate_file_hash(self, file_path):
39
+ """计算文件MD5哈希值用于比较变更[1,2](@ref)"""
40
+ hash_md5 = hashlib.md5()
41
+ try:
42
+ with open(file_path, "rb") as f:
43
+ for chunk in iter(lambda: f.read(4096), b""):
44
+ hash_md5.update(chunk)
45
+ return hash_md5.hexdigest()
46
+ except Exception as e:
47
+ logger.error(f"计算文件哈希失败 {file_path}: {e}")
48
+ return None
49
+
50
+ def ensure_local_dir(self):
51
+ """确保本地配置目录存在"""
52
+ self.local_config_dir.mkdir(parents=True, exist_ok=True)
53
+
54
+ def download_from_dataset(self):
55
+ """从Dataset拉取最新配置[8](@ref)"""
56
+ try:
57
+ logger.info(f'从Dataset拉取配置: {self.dataset_repo}')
58
+
59
+ # 下载Dataset内容
60
+ snapshot_dir = snapshot_download(
61
+ repo_id=self.dataset_repo,
62
+ token=self.hf_token,
63
+ local_dir=self.repo_dir,
64
+ allow_patterns=['*.json', '*.yaml', '*.yml'],
65
+ force_download=True
66
+ )
67
+
68
+ config_src = Path(snapshot_dir)
69
+ if not config_src.exists():
70
+ logger.warning('Dataset中未找到配置文件,将使用默认配置')
71
+ return False
72
+
73
+ # 复制配置文件到本地目录
74
+ for config_file in config_src.glob('*'):
75
+ if config_file.is_file():
76
+ dest_file = self.local_config_dir / config_file.name
77
+ # 备份原文件
78
+ if dest_file.exists():
79
+ backup_file = dest_file.with_suffix(f'.bak{int(time.time())}')
80
+ dest_file.rename(backup_file)
81
+
82
+ import shutil
83
+ shutil.copy2(config_file, dest_file)
84
+ logger.info(f'已恢复配置: {config_file.name}')
85
+
86
+ return True
87
+
88
+ except Exception as e:
89
+ logger.error(f'从Dataset拉取配置失败: {e}')
90
+ return False
91
+
92
+ def upload_to_dataset(self):
93
+ """推送配置变更回Dataset[8](@ref)"""
94
+ try:
95
+ logger.info('推送配置变更到Dataset')
96
+
97
+ # 克隆Dataset仓库
98
+ repo = Repository(
99
+ local_dir=self.repo_dir,
100
+ repo_id=self.dataset_repo,
101
+ token=self.hf_token,
102
+ clone_from=self.dataset_repo
103
+ )
104
+
105
+ # 复制更新的配置文件
106
+ for config_file in self.local_config_dir.glob('*'):
107
+ if config_file.suffix in ['.json', '.yaml', '.yml']:
108
+ dest_file = self.repo_dir / config_file.name
109
+ import shutil
110
+ shutil.copy2(config_file, dest_file)
111
+
112
+ # 提交并推送变更
113
+ repo.git_add(auto_lfs_track=True)
114
+ repo.git_commit(f"自动同步配置 {time.strftime('%Y-%m-%d %H:%M:%S')}")
115
+ repo.git_push()
116
+
117
+ logger.info('配置已成功推送到Dataset')
118
+ return True
119
+
120
+ except Exception as e:
121
+ logger.error(f'推送配置到Dataset失败: {e}')
122
+ return False
123
+
124
+ def config_changed(self):
125
+ """检查配置是否有变更"""
126
+ try:
127
+ for config_file in self.local_config_dir.glob('*.json'):
128
+ current_hash = self.calculate_file_hash(config_file)
129
+ # 这里可以比较当前哈希与上次保存的哈希值
130
+ # 简化处理:总是返回True,确保定期备份
131
+ return True
132
+ except Exception as e:
133
+ logger.error(f'检查配置变更失败: {e}')
134
+
135
+ return True
136
+
137
+ def run_sync(self, mode='download'):
138
+ """运行同步流程"""
139
+ self.ensure_local_dir()
140
+
141
+ if mode == 'download':
142
+ return self.download_from_dataset()
143
+ elif mode == 'upload':
144
+ if self.config_changed():
145
+ return self.upload_to_dataset()
146
+ else:
147
+ logger.info('配置无变更,跳过上传')
148
+ return True
149
+ return False
150
+
151
+ def start_periodic_sync(self):
152
+ """启动定时同步服务"""
153
+ logger.info('启动定时同步服务')
154
+ while True:
155
+ try:
156
+ time.sleep(self.sync_interval)
157
+ self.run_sync('upload')
158
+ except Exception as e:
159
+ logger.error(f'定时同步失败: {e}')
160
+ time.sleep(60) # 出错后等待1分钟再重试
161
+
162
+ def main():
163
+ """主函数"""
164
+ import sys
165
+
166
+ if len(sys.argv) != 2 or sys.argv[1] not in ['download', 'upload', 'sync']:
167
+ print('用法: python sync.py [download|upload|sync]')
168
+ sys.exit(1)
169
+
170
+ mode = sys.argv[1]
171
+
172
+ try:
173
+ sync = OpenClawConfigSync()
174
+
175
+ if mode == 'download':
176
+ sync.run_sync('download')
177
+ elif mode == 'upload':
178
+ sync.run_sync('upload')
179
+ elif mode == 'sync':
180
+ # 后台运行定时同步
181
+ import threading
182
+ sync.run_sync('download')
183
+ sync_thread = threading.Thread(target=sync.start_periodic_sync)
184
+ sync_thread.daemon = True
185
+ sync_thread.start()
186
+ sync_thread.join()
187
+
188
+ except Exception as e:
189
+ logger.error(f'同步服务失败: {e}')
190
+ sys.exit(1)
191
+
192
+ if __name__ == '__main__':
193
+ main()