Files
openclaw-config/scripts/db-conn.sh
T

191 lines
8.3 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# =============================================================================
# db-conn.sh —— agent 访问自己专属 MySQL 库的唯一入口(统一 helper)
#
# 【用途】
# agent 需要查/写自己的库(库名 = 自己的 agent id)时,一律通过本脚本。
# 不要自己拼 mysql 命令,不要用 root,不要用 docker exec 直连。
#
# 【为什么需要它】
# 口令存放在 ~/.openclaw/.env,键名是中文的 DB_PASSWORD_<中文名>(便于人阅读)。
# 实测确认:中文键名**无法**作为环境变量注入 agent 的执行环境,也无法交给
# `openclaw secrets store --kind env` 托管(其名称语法限制为纯大写 ASCII)。
# 依据见 ~/.openclaw/docs/agent-创建规范与自动化-设计.md 附录 A 第 8 条。
# 因此由本脚本按 agent id 反查中文名 → 读 .env → 代入容器内的 docker MySQL。
#
# 【用法】
# db-conn.sh <agent_id> 交互式 mysql(已切到自己的库)
# db-conn.sh <agent_id> --sql "select 1" 执行一条 SQL,结果打到 stdout
# db-conn.sh <agent_id> --print 只打印等价命令(口令用占位符,可安全复制)
# db-conn.sh --help
#
# 【安全约定】
# 1. 口令只通过环境变量 MYSQL_PWD 传给容器内的 mysql 客户端(客户端原生支持),
# 绝不出现在 argv/命令行,也绝不打印到 stdout/stderr,不落盘。
# 2. 传参写成 `docker exec -e MYSQL_PWD`**不带 =值**):docker 会从本脚本进程
# 的环境里转发该变量。若写成 `-e MYSQL_PWD="$值"`,口令会进入 docker 进程的
# argv`ps -eo args` 可见(已实测),故不采用。
# 3. 账号固定 agent_<id>、库固定 <id>;脚本不提供切换账号/库的开关。
# 4. --print 用占位符替换口令,输出可安全粘贴进聊天记录/工单。
# 5. 本脚本不读取、也不使用任何 root 凭据。
#
# 【退出码】
# 0 成功
# 2 用法/环境错误(参数缺失或非法、agent 不存在、中文名取不到、口令缺失)
# 其他 docker 或 mysql 的原始退出码
#
# 【连接字符集】
# 容器内 mysql 客户端默认按 latin1 建连,写中文会报
# `ERROR 1265 (01000) Data truncated for column '...'`。故本脚本所有 mysql
# 调用点统一带 `--default-character-set=utf8mb4`,调用方无需再手写 SET NAMES。
# =============================================================================
set -euo pipefail
# —— 绝对路径与固定 PATH:不依赖登录 shell / rc 文件,cron 与 agent exec 都能跑 ——
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
OPENCLAW_ROOT="${OPENCLAW_ROOT:-/home/yangxuan/.openclaw}"
ENV_FILE="$OPENCLAW_ROOT/.env"
CONFIG_FILE="$OPENCLAW_ROOT/openclaw.json"
MYSQL_CONTAINER="mysql"
usage() {
cat <<'USAGE'
用法:
db-conn.sh <agent_id> 交互式连接自己的库
db-conn.sh <agent_id> --sql "<SQL>" 执行一条 SQL 并输出结果
db-conn.sh <agent_id> --print 只打印等价命令(口令以占位符显示)
db-conn.sh --help
<agent_id> 即 openclaw.json 中 agents.entries 的键(如 main、juaner、sql)。
账号固定为 agent_<agent_id>,库固定为 <agent_id>;口令自动从 ~/.openclaw/.env
按中文键名 DB_PASSWORD_<中文名> 读取,不会出现在命令行里。
USAGE
}
# —— 打印用:把 SQL 包成可直接复制的 shell 字面量(优先单引号,含单引号时退回 %q)——
quote_sql() {
local s="$1"
if [[ "$s" != *"'"* ]]; then
printf "'%s'" "$s"
else
printf '%q' "$s"
fi
}
# —— ⓪ 参数解析 ——
AGENT_ID=""
SQL=""
PRINT_ONLY=0
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) usage; exit 0 ;;
--print) PRINT_ONLY=1; shift ;;
--sql)
[[ $# -ge 2 ]] || { echo "错误:--sql 后面缺少 SQL 语句" >&2; usage >&2; exit 2; }
SQL="$2"; shift 2 ;;
--sql=*) SQL="${1#--sql=}"; shift ;;
--*) echo "错误:未知参数 $1" >&2; usage >&2; exit 2 ;;
*)
if [[ -z "$AGENT_ID" ]]; then
AGENT_ID="$1"; shift
else
echo "错误:多余的参数 $1(只接受一个 <agent_id>" >&2; usage >&2; exit 2
fi ;;
esac
done
[[ -n "$AGENT_ID" ]] || { echo "错误:缺少 <agent_id>" >&2; usage >&2; exit 2; }
[[ "$AGENT_ID" =~ ^[a-z][a-z0-9-]{0,23}$ ]] || {
echo "错误:agent_id 非法(只允许小写字母开头、[a-z0-9-]、最长 24 字符):$AGENT_ID" >&2
exit 2
}
DB_USER="agent_$AGENT_ID"
DB_NAME="$AGENT_ID"
# —— ① 环境依赖 ——
command -v python3 >/dev/null 2>&1 || { echo "错误:找不到 python3(本脚本用它解析 $CONFIG_FILE" >&2; exit 2; }
command -v docker >/dev/null 2>&1 || { echo "错误:找不到 docker 命令" >&2; exit 2; }
[[ -f "$CONFIG_FILE" ]] || { echo "错误:找不到配置文件 $CONFIG_FILE" >&2; exit 2; }
[[ -f "$ENV_FILE" ]] || { echo "错误:找不到口令文件 $ENV_FILE" >&2; exit 2; }
# —— ② agent_id → 中文名(agents.entries.<id>.name;取不到即报错)——
AGENT_NAME=""
NAME_RC=0
AGENT_NAME="$(python3 - "$CONFIG_FILE" "$AGENT_ID" <<'PY'
import json, sys
cfg_path, agent_id = sys.argv[1], sys.argv[2]
try:
with open(cfg_path, encoding="utf-8") as fh:
cfg = json.load(fh)
except Exception as exc:
sys.stderr.write("读取 %s 失败:%s\n" % (cfg_path, exc))
sys.exit(3)
entries = ((cfg.get("agents") or {}).get("entries")) or {}
entry = entries.get(agent_id)
if not isinstance(entry, dict):
sys.exit(4)
# 主键是 name;个别条目若只有 identity.name,则兜底取它,避免 helper 对拓扑差异敏感
name = str(entry.get("name") or "").strip()
if not name and isinstance(entry.get("identity"), dict):
name = str(entry["identity"].get("name") or "").strip()
if not name:
sys.exit(5)
sys.stdout.write(name)
PY
)" || NAME_RC=$?
case "$NAME_RC" in
0) ;;
4) echo "错误:agent '$AGENT_ID' 不在 $CONFIG_FILE 的 agents.entries 中" >&2; exit 2 ;;
5) echo "错误:agent '$AGENT_ID' 在配置里没有 name(中文名),无法定位其口令键" >&2; exit 2 ;;
*) echo "错误:解析 $CONFIG_FILE 失败(python3 退出码 $NAME_RC" >&2; exit 2 ;;
esac
# —— ③ 中文名 → 口令(.env 内键名 DB_PASSWORD_<中文名>)——
PWD_KEY="DB_PASSWORD_$AGENT_NAME"
PWD_VALUE=""
PWD_VALUE="$(
awk -v k="$PWD_KEY" 'index($0, k "=") == 1 { print substr($0, length(k) + 2); exit }' "$ENV_FILE"
)"
# 去掉行尾 CR 与成对引号(.env 可能被编辑器写坏)
PWD_VALUE="${PWD_VALUE%$'\r'}"
case "$PWD_VALUE" in
\"*\") PWD_VALUE="${PWD_VALUE:1:${#PWD_VALUE}-2}" ;;
\'*\') PWD_VALUE="${PWD_VALUE:1:${#PWD_VALUE}-2}" ;;
esac
if [[ -z "$PWD_VALUE" ]]; then
echo "错误:$ENV_FILE 里没有 $PWD_KEYagent '$AGENT_ID'/中文名 '$AGENT_NAME'" >&2
echo " 该 agent 可能尚未纳入 —— 请联系杨轩用 new-agent.sh 补建/纳入后重试" >&2
exit 2
fi
# —— ④ --print:只打印等价命令,口令用占位符(绝不打印真实值)——
if [[ "$PRINT_ONLY" == "1" ]]; then
if [[ -n "$SQL" ]]; then
printf "MYSQL_PWD='<从 .env 读取:%s>' docker exec -e MYSQL_PWD %s mysql --default-character-set=utf8mb4 -u%s -N -B -e %s %s\n" \
"$PWD_KEY" "$MYSQL_CONTAINER" "$DB_USER" "$(quote_sql "$SQL")" "$DB_NAME"
else
# 交互式:本地有 TTY 才能 -it(与下面实际执行时的判定保持一致)
if [[ -t 0 && -t 1 ]]; then TTY_FLAGS="-it"; else TTY_FLAGS="-i"; fi
printf "MYSQL_PWD='<从 .env 读取:%s>' docker exec %s -e MYSQL_PWD %s mysql --default-character-set=utf8mb4 -u%s %s\n" \
"$PWD_KEY" "$TTY_FLAGS" "$MYSQL_CONTAINER" "$DB_USER" "$DB_NAME"
fi
exit 0
fi
# —— ⑤ 连接:口令经 MYSQL_PWD 环境变量转发(不进 argv、不进 stdout)——
if [[ -n "$SQL" ]]; then
MYSQL_PWD="$PWD_VALUE" docker exec -e MYSQL_PWD "$MYSQL_CONTAINER" \
mysql --default-character-set=utf8mb4 -u"$DB_USER" -N -B -e "$SQL" "$DB_NAME"
else
if [[ -t 0 && -t 1 ]]; then TTY_FLAGS="-it"; else TTY_FLAGS="-i"; fi
echo "→ 已连接:库 $DB_NAME,账号 $DB_USER(输入 exit 或按 Ctrl-D 退出)" >&2
MYSQL_PWD="$PWD_VALUE" docker exec $TTY_FLAGS -e MYSQL_PWD "$MYSQL_CONTAINER" \
mysql --default-character-set=utf8mb4 -u"$DB_USER" "$DB_NAME"
fi