@yyy9527/openclaw-manager 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +37 -0
- package/README.md +195 -0
- package/config/agents.yaml +22 -0
- package/config/channels.yaml +14 -0
- package/config/clawhub-skills.txt +6 -0
- package/config/model-env-templates/README.md +25 -0
- package/config/model-env-templates/dev.env.example +10 -0
- package/config/model-env-templates/master.env.example +10 -0
- package/config/model-env-templates/test.env.example +10 -0
- package/config/openclaw.env +20 -0
- package/config/openclaw.env.example +49 -0
- package/config/versions.lock +9 -0
- package/docker-compose.yml +24 -0
- package/package.json +47 -0
- package/scripts/apply_config.sh +33 -0
- package/scripts/bootstrap_models.sh +133 -0
- package/scripts/check_env.sh +55 -0
- package/scripts/health_check.sh +108 -0
- package/scripts/install_openclaw.sh +119 -0
- package/scripts/openclaw_ctl.sh +323 -0
- package/scripts/set_qwen_vendor_model.sh +142 -0
- package/scripts/setup_env.sh +156 -0
- package/scripts/skills_sync.sh +154 -0
- package/scripts/sync_model_configs.sh +217 -0
- package/src/integrations/feishu/notifier.js +46 -0
- package/src/orchestrator/index.js +30 -0
- package/src/tui/index.js +74 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
6
|
+
ENV_FILE="${OPENCLAW_DOTENV_FILE:-${PROJECT_ROOT}/.env}"
|
|
7
|
+
CONFIG_FILE="${OPENCLAW_CONFIG_FILE:-${PROJECT_ROOT}/config/openclaw.env}"
|
|
8
|
+
|
|
9
|
+
log() {
|
|
10
|
+
printf '[check-env] %s\n' "$*"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
warn() {
|
|
14
|
+
printf '[check-env][WARN] %s\n' "$*" >&2
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if [ ! -f "${ENV_FILE}" ]; then
|
|
18
|
+
warn "未找到环境文件:${ENV_FILE}"
|
|
19
|
+
warn "请先从 .env.example 复制并填写,或通过 --env-file 指定其他文件。"
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
set -a
|
|
24
|
+
# shellcheck source=/dev/null
|
|
25
|
+
[ -f "${CONFIG_FILE}" ] && source "${CONFIG_FILE}"
|
|
26
|
+
# shellcheck source=/dev/null
|
|
27
|
+
source "${ENV_FILE}"
|
|
28
|
+
set +a
|
|
29
|
+
|
|
30
|
+
missing=()
|
|
31
|
+
require_var() {
|
|
32
|
+
local key="$1"
|
|
33
|
+
if [ -z "${!key:-}" ]; then
|
|
34
|
+
missing+=("${key}")
|
|
35
|
+
fi
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# 基础协作能力必需项
|
|
39
|
+
require_var "GIT_TOKEN"
|
|
40
|
+
|
|
41
|
+
# 飞书开启时必需项
|
|
42
|
+
if [ "${FEISHU_ENABLED:-true}" = "true" ]; then
|
|
43
|
+
require_var "FEISHU_WEBHOOK_URL"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
if [ "${#missing[@]}" -gt 0 ]; then
|
|
47
|
+
warn "检测到缺失的环境变量(来源:${ENV_FILE}):"
|
|
48
|
+
for key in "${missing[@]}"; do
|
|
49
|
+
warn " - ${key}"
|
|
50
|
+
done
|
|
51
|
+
warn "请补齐后重试。"
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
log ".env 关键变量检查通过"
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
6
|
+
CONFIG_FILE="${OPENCLAW_CONFIG_FILE:-${PROJECT_ROOT}/config/openclaw.env}"
|
|
7
|
+
ENV_FILE="${OPENCLAW_DOTENV_FILE:-${PROJECT_ROOT}/.env}"
|
|
8
|
+
|
|
9
|
+
log() {
|
|
10
|
+
printf '[health-check] %s\n' "$*"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
fail() {
|
|
14
|
+
printf '[health-check][ERROR] %s\n' "$*" >&2
|
|
15
|
+
exit 1
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
require_cmd() {
|
|
19
|
+
local cmd="$1"
|
|
20
|
+
command -v "${cmd}" >/dev/null 2>&1 || fail "缺少命令:${cmd}"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
api_get() {
|
|
24
|
+
local url="$1"
|
|
25
|
+
curl --silent --show-error \
|
|
26
|
+
-H "Accept: application/json" \
|
|
27
|
+
"${url}"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
load_env() {
|
|
31
|
+
[ -f "${CONFIG_FILE}" ] || fail "缺少配置文件:${CONFIG_FILE}"
|
|
32
|
+
[ -f "${ENV_FILE}" ] || fail "缺少环境文件:${ENV_FILE}"
|
|
33
|
+
set -a
|
|
34
|
+
# shellcheck source=/dev/null
|
|
35
|
+
source "${CONFIG_FILE}"
|
|
36
|
+
# shellcheck source=/dev/null
|
|
37
|
+
source "${ENV_FILE}"
|
|
38
|
+
set +a
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
check_docker() {
|
|
42
|
+
log "检查 Docker daemon 状态"
|
|
43
|
+
docker info >/dev/null 2>&1 || fail "Docker daemon 不可用,请先启动 Docker Desktop"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
check_gitee() {
|
|
47
|
+
[ "${GIT_PLATFORM}" = "gitee" ] || fail "当前只支持 gitee 健康检查,检测到 GIT_PLATFORM=${GIT_PLATFORM}"
|
|
48
|
+
: "${GIT_API_BASE:?GIT_API_BASE 未配置}"
|
|
49
|
+
: "${GIT_REPO:?GIT_REPO 未配置}"
|
|
50
|
+
: "${GIT_TOKEN:?GIT_TOKEN 未配置}"
|
|
51
|
+
|
|
52
|
+
log "检查 Gitee 用户令牌有效性"
|
|
53
|
+
local user_json
|
|
54
|
+
user_json="$(api_get "${GIT_API_BASE}/user?access_token=${GIT_TOKEN}")"
|
|
55
|
+
local user_message
|
|
56
|
+
user_message="$(printf '%s' "${user_json}" | jq -r '.message // empty')"
|
|
57
|
+
[ -z "${user_message}" ] || fail "Gitee 用户鉴权失败:${user_message}"
|
|
58
|
+
local login
|
|
59
|
+
login="$(printf '%s' "${user_json}" | jq -r '.login // empty')"
|
|
60
|
+
[ -n "${login}" ] || fail "Gitee 令牌校验失败(未读取到 login)"
|
|
61
|
+
log "Gitee 用户鉴权通过:${login}"
|
|
62
|
+
|
|
63
|
+
log "检查 Gitee 仓库可访问性"
|
|
64
|
+
local repo_json
|
|
65
|
+
repo_json="$(api_get "${GIT_API_BASE}/repos/${GIT_REPO}?access_token=${GIT_TOKEN}")"
|
|
66
|
+
local repo_message
|
|
67
|
+
repo_message="$(printf '%s' "${repo_json}" | jq -r '.message // empty')"
|
|
68
|
+
if [ -n "${repo_message}" ]; then
|
|
69
|
+
fail "仓库访问失败:${repo_message}。请检查 GIT_REPO(应为 owner/repo)和 token 权限。"
|
|
70
|
+
fi
|
|
71
|
+
local full_name
|
|
72
|
+
full_name="$(printf '%s' "${repo_json}" | jq -r '.full_name // empty')"
|
|
73
|
+
[ -n "${full_name}" ] || fail "仓库访问失败,请检查 GIT_REPO 或 token 权限"
|
|
74
|
+
log "仓库可访问:${full_name}"
|
|
75
|
+
|
|
76
|
+
log "检查 Issue API 基础可用性"
|
|
77
|
+
local issues_json
|
|
78
|
+
issues_json="$(api_get "${GIT_API_BASE}/repos/${GIT_REPO}/issues?access_token=${GIT_TOKEN}&state=open&page=1&per_page=1")"
|
|
79
|
+
local issues_message
|
|
80
|
+
issues_message="$(printf '%s' "${issues_json}" | jq -r 'if type=="object" then .message // empty else empty end')"
|
|
81
|
+
if [ -n "${issues_message}" ]; then
|
|
82
|
+
fail "Issue API 调用失败:${issues_message}"
|
|
83
|
+
fi
|
|
84
|
+
printf '%s' "${issues_json}" | jq -e 'if type=="array" then true else false end' >/dev/null || fail "Issue API 返回异常"
|
|
85
|
+
log "Issue API 可用"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
check_feishu_config() {
|
|
89
|
+
if [ "${FEISHU_ENABLED:-true}" = "true" ]; then
|
|
90
|
+
: "${FEISHU_WEBHOOK_URL:?FEISHU_WEBHOOK_URL 未配置}"
|
|
91
|
+
log "飞书通知已启用,Webhook 已配置"
|
|
92
|
+
else
|
|
93
|
+
log "飞书通知已禁用,跳过"
|
|
94
|
+
fi
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
main() {
|
|
98
|
+
require_cmd curl
|
|
99
|
+
require_cmd jq
|
|
100
|
+
require_cmd docker
|
|
101
|
+
load_env
|
|
102
|
+
check_docker
|
|
103
|
+
check_gitee
|
|
104
|
+
check_feishu_config
|
|
105
|
+
log "健康检查通过"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
main "$@"
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
6
|
+
CONFIG_FILE="${OPENCLAW_CONFIG_FILE:-${PROJECT_ROOT}/config/openclaw.env}"
|
|
7
|
+
ENV_FILE="${OPENCLAW_DOTENV_FILE:-${PROJECT_ROOT}/.env}"
|
|
8
|
+
|
|
9
|
+
log() {
|
|
10
|
+
printf '[install_openclaw] %s\n' "$*"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
fail() {
|
|
14
|
+
printf '[install_openclaw][ERROR] %s\n' "$*" >&2
|
|
15
|
+
exit 1
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
ensure_file() {
|
|
19
|
+
local file="$1"
|
|
20
|
+
[ -f "${file}" ] || fail "缺少文件:${file}"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
ensure_cmd() {
|
|
24
|
+
local cmd="$1"
|
|
25
|
+
command -v "${cmd}" >/dev/null 2>&1 || fail "缺少命令:${cmd}"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
project_name_for_instance() {
|
|
29
|
+
local instance_id="$1"
|
|
30
|
+
printf '%s-%s' "${OPENCLAW_NAME_PREFIX}" "${instance_id}" | tr '[:upper:]' '[:lower:]'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
load_env() {
|
|
34
|
+
set -a
|
|
35
|
+
# shellcheck source=/dev/null
|
|
36
|
+
source "${CONFIG_FILE}"
|
|
37
|
+
if [ -f "${ENV_FILE}" ]; then
|
|
38
|
+
# shellcheck source=/dev/null
|
|
39
|
+
source "${ENV_FILE}"
|
|
40
|
+
fi
|
|
41
|
+
set +a
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
check_required_vars() {
|
|
45
|
+
: "${OPENCLAW_IMAGE:?OPENCLAW_IMAGE 未配置}"
|
|
46
|
+
: "${OPENCLAW_VERSION:?OPENCLAW_VERSION 未配置}"
|
|
47
|
+
: "${OPENCLAW_INSTANCE_IDS:?OPENCLAW_INSTANCE_IDS 未配置}"
|
|
48
|
+
: "${OPENCLAW_BASE_HTTP_PORT:?OPENCLAW_BASE_HTTP_PORT 未配置}"
|
|
49
|
+
: "${OPENCLAW_CONTAINER_PORT:?OPENCLAW_CONTAINER_PORT 未配置}"
|
|
50
|
+
if [ -z "${OPENCLAW_HOME_ROOT:-}" ]; then
|
|
51
|
+
# 向后兼容旧变量名
|
|
52
|
+
OPENCLAW_HOME_ROOT="${OPENCLAW_DATA_ROOT:-}"
|
|
53
|
+
fi
|
|
54
|
+
: "${OPENCLAW_HOME_ROOT:?OPENCLAW_HOME_ROOT 未配置}"
|
|
55
|
+
OPENCLAW_SHARED_SKILLS_DIR="${OPENCLAW_SHARED_SKILLS_DIR:-${OPENCLAW_HOME_ROOT}/shared/skills}"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
split_instances() {
|
|
59
|
+
local ids="${OPENCLAW_INSTANCE_IDS}"
|
|
60
|
+
IFS=',' read -r -a INSTANCE_LIST <<<"${ids}"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
port_in_use() {
|
|
64
|
+
local port="$1"
|
|
65
|
+
lsof -iTCP:"${port}" -sTCP:LISTEN -n -P >/dev/null 2>&1
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
compose_up_for_instance() {
|
|
69
|
+
local instance_id="$1"
|
|
70
|
+
local ordinal="$2"
|
|
71
|
+
local http_port="$((OPENCLAW_BASE_HTTP_PORT + ordinal))"
|
|
72
|
+
local instance_home_dir="${OPENCLAW_HOME_ROOT}/${instance_id}"
|
|
73
|
+
local data_dir="${instance_home_dir}"
|
|
74
|
+
local logs_dir="${PROJECT_ROOT}/logs/${OPENCLAW_NAME_PREFIX}-${instance_id}"
|
|
75
|
+
|
|
76
|
+
if port_in_use "${http_port}"; then
|
|
77
|
+
fail "HTTP 端口已被占用:${http_port}(实例 ${instance_id})"
|
|
78
|
+
fi
|
|
79
|
+
mkdir -p "${instance_home_dir}" "${OPENCLAW_SHARED_SKILLS_DIR}" "${logs_dir}"
|
|
80
|
+
log "准备启动实例 ${instance_id}(host_http=${http_port}, container_port=${OPENCLAW_CONTAINER_PORT})"
|
|
81
|
+
|
|
82
|
+
OPENCLAW_INSTANCE_ID="${instance_id}" \
|
|
83
|
+
OPENCLAW_INSTANCE_ORDINAL="${ordinal}" \
|
|
84
|
+
OPENCLAW_HTTP_PORT="${http_port}" \
|
|
85
|
+
OPENCLAW_CONTAINER_PORT="${OPENCLAW_CONTAINER_PORT}" \
|
|
86
|
+
OPENCLAW_INSTANCE_HOME_DIR="${instance_home_dir}" \
|
|
87
|
+
OPENCLAW_INSTANCE_DATA_DIR="${data_dir}" \
|
|
88
|
+
OPENCLAW_INSTANCE_LOG_DIR="${logs_dir}" \
|
|
89
|
+
docker compose -p "$(project_name_for_instance "${instance_id}")" -f "${PROJECT_ROOT}/docker-compose.yml" up -d --remove-orphans
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
main() {
|
|
93
|
+
ensure_file "${CONFIG_FILE}"
|
|
94
|
+
ensure_file "${PROJECT_ROOT}/docker-compose.yml"
|
|
95
|
+
ensure_cmd docker
|
|
96
|
+
|
|
97
|
+
if ! docker info >/dev/null 2>&1; then
|
|
98
|
+
fail "Docker daemon 未就绪,请先启动 Docker Desktop。"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
load_env
|
|
102
|
+
check_required_vars
|
|
103
|
+
split_instances
|
|
104
|
+
"${PROJECT_ROOT}/scripts/skills_sync.sh"
|
|
105
|
+
|
|
106
|
+
local i=0
|
|
107
|
+
for id in "${INSTANCE_LIST[@]}"; do
|
|
108
|
+
compose_up_for_instance "${id}" "${i}"
|
|
109
|
+
i=$((i + 1))
|
|
110
|
+
done
|
|
111
|
+
|
|
112
|
+
if ! "${PROJECT_ROOT}/scripts/bootstrap_models.sh"; then
|
|
113
|
+
log "模型引导未完全成功,可稍后手动执行:npm run bootstrap-models"
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
log "全部实例启动完成"
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
main "$@"
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
6
|
+
DEFAULT_CONFIG_FILE="${PROJECT_ROOT}/config/openclaw.env"
|
|
7
|
+
DEFAULT_DOTENV_FILE="${PROJECT_ROOT}/.env"
|
|
8
|
+
|
|
9
|
+
OPENCLAW_CONFIG_FILE="${OPENCLAW_CONFIG_FILE:-${DEFAULT_CONFIG_FILE}}"
|
|
10
|
+
OPENCLAW_DOTENV_FILE="${OPENCLAW_DOTENV_FILE:-${DEFAULT_DOTENV_FILE}}"
|
|
11
|
+
|
|
12
|
+
usage() {
|
|
13
|
+
cat <<'EOF'
|
|
14
|
+
OpenClaw Manager CLI(多实例运维入口)
|
|
15
|
+
|
|
16
|
+
用法:
|
|
17
|
+
./scripts/openclaw_ctl.sh [--config <path>] [--env-file <path>] <command> [args]
|
|
18
|
+
|
|
19
|
+
全局参数:
|
|
20
|
+
-h, --help 显示帮助
|
|
21
|
+
--config <path> 指定 openclaw 运行配置文件(默认 ./config/openclaw.env)
|
|
22
|
+
--env-file <path> 指定敏感变量文件(默认 ./.env)
|
|
23
|
+
|
|
24
|
+
命令:
|
|
25
|
+
setup
|
|
26
|
+
install
|
|
27
|
+
start | stop | restart | status
|
|
28
|
+
logs [service]
|
|
29
|
+
list-claw
|
|
30
|
+
tui
|
|
31
|
+
chat-claw <name>
|
|
32
|
+
start-claw <name> | stop-claw <name> | status-claw <name> | logs-claw <name>
|
|
33
|
+
check-env
|
|
34
|
+
sync-skills
|
|
35
|
+
sync-model-configs
|
|
36
|
+
apply-config
|
|
37
|
+
bootstrap-models
|
|
38
|
+
set-qwen
|
|
39
|
+
health-check
|
|
40
|
+
self-test
|
|
41
|
+
|
|
42
|
+
示例:
|
|
43
|
+
./scripts/openclaw_ctl.sh --config ./config/openclaw.dev.env status
|
|
44
|
+
./scripts/openclaw_ctl.sh --env-file ./.env.prod start-claw master
|
|
45
|
+
EOF
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
project_name_for_instance() {
|
|
49
|
+
local instance_id="$1"
|
|
50
|
+
printf '%s-%s' "${OPENCLAW_NAME_PREFIX}" "${instance_id}" | tr '[:upper:]' '[:lower:]'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
run_install() {
|
|
54
|
+
"${PROJECT_ROOT}/scripts/install_openclaw.sh"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
load_config() {
|
|
58
|
+
local config_file="${OPENCLAW_CONFIG_FILE}"
|
|
59
|
+
if [ ! -f "${config_file}" ]; then
|
|
60
|
+
echo "缺少 ${config_file}" >&2
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
set -a
|
|
64
|
+
# shellcheck source=/dev/null
|
|
65
|
+
source "${config_file}"
|
|
66
|
+
set +a
|
|
67
|
+
|
|
68
|
+
if [ -z "${OPENCLAW_HOME_ROOT:-}" ]; then
|
|
69
|
+
OPENCLAW_HOME_ROOT="${OPENCLAW_DATA_ROOT:-}"
|
|
70
|
+
fi
|
|
71
|
+
: "${OPENCLAW_HOME_ROOT:?OPENCLAW_HOME_ROOT 未配置}"
|
|
72
|
+
OPENCLAW_SHARED_SKILLS_DIR="${OPENCLAW_SHARED_SKILLS_DIR:-${OPENCLAW_HOME_ROOT}/shared/skills}"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
ensure_instance_exists() {
|
|
76
|
+
local target="$1"
|
|
77
|
+
IFS=',' read -r -a instance_list <<<"${OPENCLAW_INSTANCE_IDS}"
|
|
78
|
+
for id in "${instance_list[@]}"; do
|
|
79
|
+
if [ "${id}" = "${target}" ]; then
|
|
80
|
+
return 0
|
|
81
|
+
fi
|
|
82
|
+
done
|
|
83
|
+
echo "未找到 claw 实例:${target}" >&2
|
|
84
|
+
exit 1
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
list_claws() {
|
|
88
|
+
load_config
|
|
89
|
+
IFS=',' read -r -a instance_list <<<"${OPENCLAW_INSTANCE_IDS}"
|
|
90
|
+
for id in "${instance_list[@]}"; do
|
|
91
|
+
printf '%s\n' "${id}"
|
|
92
|
+
done
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
run_compose_for_instance() {
|
|
96
|
+
local target="$1"
|
|
97
|
+
shift
|
|
98
|
+
load_config
|
|
99
|
+
ensure_instance_exists "${target}"
|
|
100
|
+
|
|
101
|
+
local ordinal=0
|
|
102
|
+
IFS=',' read -r -a instance_list <<<"${OPENCLAW_INSTANCE_IDS}"
|
|
103
|
+
for id in "${instance_list[@]}"; do
|
|
104
|
+
if [ "${id}" = "${target}" ]; then
|
|
105
|
+
OPENCLAW_INSTANCE_ID="${id}" \
|
|
106
|
+
OPENCLAW_INSTANCE_ORDINAL="${ordinal}" \
|
|
107
|
+
OPENCLAW_HTTP_PORT="$((OPENCLAW_BASE_HTTP_PORT + ordinal))" \
|
|
108
|
+
OPENCLAW_CONTAINER_PORT="${OPENCLAW_CONTAINER_PORT:-18789}" \
|
|
109
|
+
OPENCLAW_INSTANCE_HOME_DIR="${OPENCLAW_HOME_ROOT}/${id}" \
|
|
110
|
+
OPENCLAW_SHARED_SKILLS_DIR="${OPENCLAW_SHARED_SKILLS_DIR:-${OPENCLAW_HOME_ROOT}/shared/skills}" \
|
|
111
|
+
OPENCLAW_INSTANCE_DATA_DIR="${OPENCLAW_HOME_ROOT}/${id}" \
|
|
112
|
+
OPENCLAW_INSTANCE_LOG_DIR="${PROJECT_ROOT}/logs/${OPENCLAW_NAME_PREFIX}-${id}" \
|
|
113
|
+
docker compose -p "$(project_name_for_instance "${id}")" -f "${PROJECT_ROOT}/docker-compose.yml" "$@"
|
|
114
|
+
return 0
|
|
115
|
+
fi
|
|
116
|
+
ordinal=$((ordinal + 1))
|
|
117
|
+
done
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
chat_with_claw() {
|
|
121
|
+
local claw_name="$1"
|
|
122
|
+
local running_service
|
|
123
|
+
running_service="$(run_compose_for_instance "${claw_name}" ps --status running --services 2>/dev/null | awk 'NF{print $1; exit}')"
|
|
124
|
+
if [ "${running_service}" != "openclaw" ]; then
|
|
125
|
+
echo "实例 ${claw_name} 当前未运行,请先执行: npm run start-claw -- ${claw_name}" >&2
|
|
126
|
+
exit 1
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
local max_wait=30
|
|
130
|
+
local waited=0
|
|
131
|
+
until run_compose_for_instance "${claw_name}" exec -T openclaw sh -lc 'openclaw health >/dev/null 2>&1'; do
|
|
132
|
+
waited=$((waited + 2))
|
|
133
|
+
if [ "${waited}" -ge "${max_wait}" ]; then
|
|
134
|
+
echo "实例 ${claw_name} 网关尚未就绪(等待 ${max_wait}s 超时)。可先执行: npm run apply-config" >&2
|
|
135
|
+
exit 1
|
|
136
|
+
fi
|
|
137
|
+
sleep 2
|
|
138
|
+
done
|
|
139
|
+
|
|
140
|
+
run_compose_for_instance "${claw_name}" exec openclaw sh -lc '
|
|
141
|
+
SESSION_KEY="chat-'${claw_name}'"
|
|
142
|
+
if command -v openclaw >/dev/null 2>&1; then
|
|
143
|
+
exec openclaw tui --session "${SESSION_KEY}"
|
|
144
|
+
elif command -v claw >/dev/null 2>&1; then
|
|
145
|
+
exec claw tui --session "${SESSION_KEY}"
|
|
146
|
+
else
|
|
147
|
+
echo "容器内未找到 openclaw/claw 命令,无法进入对话 TUI。"
|
|
148
|
+
exit 127
|
|
149
|
+
fi
|
|
150
|
+
'
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
compose_cmd_all_instances() {
|
|
154
|
+
load_config
|
|
155
|
+
|
|
156
|
+
IFS=',' read -r -a instance_list <<<"${OPENCLAW_INSTANCE_IDS}"
|
|
157
|
+
local i=0
|
|
158
|
+
for id in "${instance_list[@]}"; do
|
|
159
|
+
OPENCLAW_INSTANCE_ID="${id}" \
|
|
160
|
+
OPENCLAW_INSTANCE_ORDINAL="${i}" \
|
|
161
|
+
OPENCLAW_HTTP_PORT="$((OPENCLAW_BASE_HTTP_PORT + i))" \
|
|
162
|
+
OPENCLAW_CONTAINER_PORT="${OPENCLAW_CONTAINER_PORT:-18789}" \
|
|
163
|
+
OPENCLAW_INSTANCE_HOME_DIR="${OPENCLAW_HOME_ROOT}/${id}" \
|
|
164
|
+
OPENCLAW_SHARED_SKILLS_DIR="${OPENCLAW_SHARED_SKILLS_DIR:-${OPENCLAW_HOME_ROOT}/shared/skills}" \
|
|
165
|
+
OPENCLAW_INSTANCE_DATA_DIR="${OPENCLAW_HOME_ROOT}/${id}" \
|
|
166
|
+
OPENCLAW_INSTANCE_LOG_DIR="${PROJECT_ROOT}/logs/${OPENCLAW_NAME_PREFIX}-${id}" \
|
|
167
|
+
docker compose -p "$(project_name_for_instance "${id}")" -f "${PROJECT_ROOT}/docker-compose.yml" "$@"
|
|
168
|
+
i=$((i + 1))
|
|
169
|
+
done
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
run_self_test() {
|
|
173
|
+
echo "[self-test] 检查脚本可执行权限"
|
|
174
|
+
test -x "${PROJECT_ROOT}/scripts/setup_env.sh"
|
|
175
|
+
test -x "${PROJECT_ROOT}/scripts/install_openclaw.sh"
|
|
176
|
+
test -x "${PROJECT_ROOT}/scripts/openclaw_ctl.sh"
|
|
177
|
+
test -x "${PROJECT_ROOT}/scripts/check_env.sh"
|
|
178
|
+
test -x "${PROJECT_ROOT}/scripts/health_check.sh"
|
|
179
|
+
test -x "${PROJECT_ROOT}/scripts/skills_sync.sh"
|
|
180
|
+
test -x "${PROJECT_ROOT}/scripts/sync_model_configs.sh"
|
|
181
|
+
test -x "${PROJECT_ROOT}/scripts/bootstrap_models.sh"
|
|
182
|
+
test -x "${PROJECT_ROOT}/scripts/apply_config.sh"
|
|
183
|
+
test -x "${PROJECT_ROOT}/scripts/set_qwen_vendor_model.sh"
|
|
184
|
+
echo "[self-test] 检查关键文件存在"
|
|
185
|
+
test -f "${PROJECT_ROOT}/docker-compose.yml"
|
|
186
|
+
test -f "${PROJECT_ROOT}/config/openclaw.env.example"
|
|
187
|
+
test -f "${PROJECT_ROOT}/config/clawhub-skills.txt"
|
|
188
|
+
test -f "${PROJECT_ROOT}/.env.example"
|
|
189
|
+
echo "[self-test] 检查 Git 仓库状态(里程碑提交前置)"
|
|
190
|
+
if ! git -C "${PROJECT_ROOT}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
191
|
+
echo "[self-test][WARN] 当前目录不是 Git 仓库。里程碑提交会被阻塞,请先执行 git init。"
|
|
192
|
+
fi
|
|
193
|
+
echo "[self-test] 基础检查通过"
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
POSITIONAL_ARGS=()
|
|
197
|
+
while [ "$#" -gt 0 ]; do
|
|
198
|
+
case "$1" in
|
|
199
|
+
-h|--help|help)
|
|
200
|
+
usage
|
|
201
|
+
exit 0
|
|
202
|
+
;;
|
|
203
|
+
--config)
|
|
204
|
+
[ -n "${2:-}" ] || { echo "缺少 --config 的参数路径" >&2; exit 1; }
|
|
205
|
+
OPENCLAW_CONFIG_FILE="$2"
|
|
206
|
+
shift 2
|
|
207
|
+
;;
|
|
208
|
+
--env-file)
|
|
209
|
+
[ -n "${2:-}" ] || { echo "缺少 --env-file 的参数路径" >&2; exit 1; }
|
|
210
|
+
OPENCLAW_DOTENV_FILE="$2"
|
|
211
|
+
shift 2
|
|
212
|
+
;;
|
|
213
|
+
--)
|
|
214
|
+
shift
|
|
215
|
+
while [ "$#" -gt 0 ]; do
|
|
216
|
+
POSITIONAL_ARGS+=("$1")
|
|
217
|
+
shift
|
|
218
|
+
done
|
|
219
|
+
;;
|
|
220
|
+
-*)
|
|
221
|
+
echo "未知参数: $1" >&2
|
|
222
|
+
usage
|
|
223
|
+
exit 1
|
|
224
|
+
;;
|
|
225
|
+
*)
|
|
226
|
+
POSITIONAL_ARGS+=("$1")
|
|
227
|
+
shift
|
|
228
|
+
;;
|
|
229
|
+
esac
|
|
230
|
+
done
|
|
231
|
+
|
|
232
|
+
export OPENCLAW_CONFIG_FILE
|
|
233
|
+
export OPENCLAW_DOTENV_FILE
|
|
234
|
+
|
|
235
|
+
set -- "${POSITIONAL_ARGS[@]}"
|
|
236
|
+
command="${1:-}"
|
|
237
|
+
case "${command}" in
|
|
238
|
+
setup)
|
|
239
|
+
"${PROJECT_ROOT}/scripts/setup_env.sh"
|
|
240
|
+
;;
|
|
241
|
+
install)
|
|
242
|
+
run_install
|
|
243
|
+
;;
|
|
244
|
+
start)
|
|
245
|
+
compose_cmd_all_instances up -d
|
|
246
|
+
;;
|
|
247
|
+
stop)
|
|
248
|
+
compose_cmd_all_instances down
|
|
249
|
+
;;
|
|
250
|
+
restart)
|
|
251
|
+
compose_cmd_all_instances restart
|
|
252
|
+
;;
|
|
253
|
+
status)
|
|
254
|
+
compose_cmd_all_instances ps
|
|
255
|
+
;;
|
|
256
|
+
logs)
|
|
257
|
+
service="${2:-}"
|
|
258
|
+
if [ -n "${service}" ]; then
|
|
259
|
+
compose_cmd_all_instances logs -f "${service}"
|
|
260
|
+
else
|
|
261
|
+
compose_cmd_all_instances logs --tail=200
|
|
262
|
+
fi
|
|
263
|
+
;;
|
|
264
|
+
list-claw)
|
|
265
|
+
list_claws
|
|
266
|
+
;;
|
|
267
|
+
tui)
|
|
268
|
+
node "${PROJECT_ROOT}/src/tui/index.js"
|
|
269
|
+
;;
|
|
270
|
+
chat-claw)
|
|
271
|
+
claw_name="${2:-}"
|
|
272
|
+
[ -n "${claw_name}" ] || { echo "用法: ./scripts/openclaw_ctl.sh chat-claw <name>" >&2; exit 1; }
|
|
273
|
+
chat_with_claw "${claw_name}"
|
|
274
|
+
;;
|
|
275
|
+
status-claw)
|
|
276
|
+
claw_name="${2:-}"
|
|
277
|
+
[ -n "${claw_name}" ] || { echo "用法: status-claw <name>" >&2; exit 1; }
|
|
278
|
+
run_compose_for_instance "${claw_name}" ps
|
|
279
|
+
;;
|
|
280
|
+
start-claw)
|
|
281
|
+
claw_name="${2:-}"
|
|
282
|
+
[ -n "${claw_name}" ] || { echo "用法: start-claw <name>" >&2; exit 1; }
|
|
283
|
+
run_compose_for_instance "${claw_name}" up -d
|
|
284
|
+
;;
|
|
285
|
+
stop-claw)
|
|
286
|
+
claw_name="${2:-}"
|
|
287
|
+
[ -n "${claw_name}" ] || { echo "用法: stop-claw <name>" >&2; exit 1; }
|
|
288
|
+
run_compose_for_instance "${claw_name}" down
|
|
289
|
+
;;
|
|
290
|
+
logs-claw)
|
|
291
|
+
claw_name="${2:-}"
|
|
292
|
+
[ -n "${claw_name}" ] || { echo "用法: logs-claw <name>" >&2; exit 1; }
|
|
293
|
+
run_compose_for_instance "${claw_name}" logs --tail=200
|
|
294
|
+
;;
|
|
295
|
+
self-test)
|
|
296
|
+
run_self_test
|
|
297
|
+
;;
|
|
298
|
+
check-env)
|
|
299
|
+
"${PROJECT_ROOT}/scripts/check_env.sh"
|
|
300
|
+
;;
|
|
301
|
+
sync-skills)
|
|
302
|
+
"${PROJECT_ROOT}/scripts/skills_sync.sh"
|
|
303
|
+
;;
|
|
304
|
+
sync-model-configs)
|
|
305
|
+
"${PROJECT_ROOT}/scripts/sync_model_configs.sh"
|
|
306
|
+
;;
|
|
307
|
+
apply-config)
|
|
308
|
+
"${PROJECT_ROOT}/scripts/apply_config.sh"
|
|
309
|
+
;;
|
|
310
|
+
bootstrap-models)
|
|
311
|
+
"${PROJECT_ROOT}/scripts/bootstrap_models.sh"
|
|
312
|
+
;;
|
|
313
|
+
set-qwen)
|
|
314
|
+
"${PROJECT_ROOT}/scripts/set_qwen_vendor_model.sh"
|
|
315
|
+
;;
|
|
316
|
+
health-check)
|
|
317
|
+
"${PROJECT_ROOT}/scripts/health_check.sh"
|
|
318
|
+
;;
|
|
319
|
+
*)
|
|
320
|
+
usage
|
|
321
|
+
exit 1
|
|
322
|
+
;;
|
|
323
|
+
esac
|