OpenIM + LiveKit 内网测试环境一键部署包
- docker-compose: openim-server v3.8.3-patch.12 + mongo/redis/etcd/kafka/minio + livekit v1.13.5 - 按约束移除 openim-chat(GPLv3) 及依赖它的 web/admin 前端; 监控组件收进 profile m - scripts/deploy.sh 一键部署, scripts/selftest.sh 自测(注册/登录/文字/语音/文件/一对一通话), scripts/restart.sh 重启 - README: 部署记录(服务器要求/端口表/运维/接入参数)
This commit is contained in:
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
# OpenIM + LiveKit 内网测试环境一键部署
|
||||
# 用法: ./scripts/deploy.sh [服务器内网IP] (不传则自动探测)
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# 1. 首次运行从模板生成 .env
|
||||
if [ ! -f .env ]; then
|
||||
cp .env.example .env
|
||||
echo "已从 .env.example 生成 .env"
|
||||
fi
|
||||
|
||||
# 2. 确定服务器内网 IP
|
||||
IP="${1:-}"
|
||||
if [ -z "$IP" ]; then
|
||||
IP=$(hostname -I 2>/dev/null | awk '{print $1}')
|
||||
fi
|
||||
if [ -z "$IP" ]; then
|
||||
echo "无法自动获取内网 IP,请手动传入: ./scripts/deploy.sh <服务器IP>"
|
||||
exit 1
|
||||
fi
|
||||
echo "使用服务器 IP: $IP"
|
||||
|
||||
# 3. 把 IP 写进 .env(MinIO 外链地址必须是客户端能访问的地址)
|
||||
sed -i "s|^SERVER_IP=.*|SERVER_IP=${IP}|" .env
|
||||
sed -i "s|^MINIO_EXTERNAL_ADDRESS=.*|MINIO_EXTERNAL_ADDRESS=\"http://${IP}:10005\"|" .env
|
||||
sed -i "s|^GRAFANA_URL=.*|GRAFANA_URL=\"http://${IP}:13000/\"|" .env
|
||||
|
||||
# 4. 预创建挂载目录(避免 docker 把文件路径自动建成目录)
|
||||
mkdir -p components/redis/config components/mnt/data components/mongodb components/etcd components/kafka
|
||||
touch components/redis/config/redis.conf
|
||||
|
||||
# 5. 拉镜像并启动
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
|
||||
# 6. 等待 openim-server 健康(首次启动约 30-60 秒)
|
||||
echo "等待 openim-server 就绪(最长约 2 分钟)..."
|
||||
status="unknown"
|
||||
for _ in $(seq 1 24); do
|
||||
status=$(docker inspect -f '{{.State.Health.Status}}' openim-server 2>/dev/null || echo "unknown")
|
||||
[ "$status" = "healthy" ] && break
|
||||
sleep 5
|
||||
done
|
||||
|
||||
docker compose ps
|
||||
echo
|
||||
if [ "$status" = "healthy" ]; then
|
||||
echo "部署完成。下一步运行自测: ./scripts/selftest.sh ${IP}"
|
||||
else
|
||||
echo "openim-server 尚未 healthy(当前: ${status})。可执行 docker compose logs -f openim-server 查看日志。"
|
||||
fi
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# 重启测试环境。用法: ./scripts/restart.sh —— 重启全部服务
|
||||
# ./scripts/restart.sh livekit —— 只重启指定服务
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
docker compose restart "$@"
|
||||
echo "已重启。OpenIM 完全就绪约需 30-60 秒,查看状态: docker compose ps"
|
||||
Executable
+187
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env bash
|
||||
# OpenIM + LiveKit 测试环境自测脚本
|
||||
# 覆盖:管理员令牌、注册两个测试账号、签发用户令牌(登录)、
|
||||
# 互发文字/语音/文件消息、消息回查、LiveKit 一对一房间双端进房与媒体发布订阅。
|
||||
# 用法: ./scripts/selftest.sh [服务器内网IP]
|
||||
set -uo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# ---------- 参数与公共函数 ----------
|
||||
env_get() { grep -E "^$1=" .env | head -1 | cut -d= -f2- | tr -d '"'; }
|
||||
|
||||
IP="${1:-$(env_get SERVER_IP)}"
|
||||
IP="${IP:-127.0.0.1}"
|
||||
API="http://${IP}:10002"
|
||||
SECRET="$(env_get OPENIM_SECRET)"; SECRET="${SECRET:-openIM123}"
|
||||
MINIO_AK="$(env_get MINIO_ACCESS_KEY_ID)"; MINIO_AK="${MINIO_AK:-root}"
|
||||
MINIO_SK="$(env_get MINIO_SECRET_ACCESS_KEY)"; MINIO_SK="${MINIO_SK:-openIM123}"
|
||||
MINIO_PORT="$(env_get MINIO_PORT)"; MINIO_PORT="${MINIO_PORT:-10005}"
|
||||
LK_KEY="$(env_get LIVEKIT_API_KEY)"
|
||||
LK_SECRET="$(env_get LIVEKIT_API_SECRET)"
|
||||
LK_HTTP="http://${IP}:7880"
|
||||
LK_WS="ws://${IP}:7880"
|
||||
LK_CLI_IMAGE="livekit/livekit-cli:v2.18.2"
|
||||
MC_IMAGE="minio/mc:latest"
|
||||
|
||||
FAILED=0
|
||||
pass() { echo " [通过] $1"; }
|
||||
fail() { echo " [失败] $1"; FAILED=1; }
|
||||
note() { echo " [提示] $1"; }
|
||||
|
||||
# 优先用宿主机 curl;没有则在容器里跑 curl
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
http_post() { curl -s -m 15 -X POST "$1" -H 'Content-Type: application/json' ${TOKEN:+-H "token: $TOKEN"} -d "$2"; }
|
||||
else
|
||||
note "宿主机无 curl,使用容器化 curl"
|
||||
http_post() { docker run --rm --network host curlimages/curl:8.10.1 -s -m 15 -X POST "$1" -H 'Content-Type: application/json' ${TOKEN:+-H "token: $TOKEN"} -d "$2"; }
|
||||
fi
|
||||
|
||||
json_ok() { echo "$1" | tr -d ' \n' | grep -q '"errCode":0'; }
|
||||
json_field() { echo "$1" | sed -n "s/.*\"$2\":\"\([^\"]*\)\".*/\1/p" | head -1; }
|
||||
|
||||
echo "==============================================="
|
||||
echo " OpenIM 自测 (API: $API)"
|
||||
echo "==============================================="
|
||||
|
||||
# ---------- 1. 管理员令牌 ----------
|
||||
echo "1. 获取管理员令牌(/auth/get_admin_token)"
|
||||
TOKEN=""
|
||||
resp=$(http_post "$API/auth/get_admin_token" "{\"secret\":\"${SECRET}\",\"userID\":\"imAdmin\"}")
|
||||
if json_ok "$resp" && [ -n "$(json_field "$resp" token)" ]; then
|
||||
TOKEN=$(json_field "$resp" token)
|
||||
pass "管理员令牌获取成功"
|
||||
else
|
||||
fail "管理员令牌获取失败: $resp"
|
||||
echo "OpenIM API 不可用,终止自测。"; exit 1
|
||||
fi
|
||||
|
||||
# ---------- 2. 注册两个测试账号 ----------
|
||||
echo "2. 注册测试账号 test001 / test002(/user/user_register)"
|
||||
resp=$(http_post "$API/user/user_register" "{\"secret\":\"${SECRET}\",\"users\":[{\"userID\":\"test001\",\"nickname\":\"测试一号\"},{\"userID\":\"test002\",\"nickname\":\"测试二号\"}]}")
|
||||
if json_ok "$resp"; then
|
||||
pass "两个测试账号注册成功"
|
||||
else
|
||||
# 重复执行脚本时账号已存在,属于正常情况
|
||||
note "注册接口返回非 0(通常是账号已存在,重复自测属正常): $(echo "$resp" | head -c 200)"
|
||||
fi
|
||||
|
||||
# ---------- 3. 签发用户令牌(登录) ----------
|
||||
echo "3. 签发用户令牌(/auth/get_user_token,platformID=5)"
|
||||
resp=$(http_post "$API/auth/get_user_token" "{\"secret\":\"${SECRET}\",\"platformID\":5,\"userID\":\"test001\"}")
|
||||
T1=$(json_field "$resp" token)
|
||||
resp=$(http_post "$API/auth/get_user_token" "{\"secret\":\"${SECRET}\",\"platformID\":5,\"userID\":\"test002\"}")
|
||||
T2=$(json_field "$resp" token)
|
||||
if [ -n "$T1" ] && [ -n "$T2" ]; then
|
||||
pass "test001 / test002 均拿到用户令牌(登录链路可用)"
|
||||
else
|
||||
fail "用户令牌签发失败"
|
||||
fi
|
||||
|
||||
# ---------- 4. 互发文字消息 ----------
|
||||
MARK="selftest-$(date +%s)"
|
||||
echo "4. test001 -> test002 发送文字消息(/msg/send_msg)"
|
||||
resp=$(http_post "$API/msg/send_msg" "{\"sendID\":\"test001\",\"recvID\":\"test002\",\"senderNickname\":\"测试一号\",\"senderPlatformID\":5,\"content\":{\"content\":\"自测文字 ${MARK}\"},\"contentType\":101,\"sessionType\":1}")
|
||||
if json_ok "$resp"; then pass "文字消息发送成功"; else fail "文字消息发送失败: $(echo "$resp" | head -c 200)"; fi
|
||||
|
||||
echo " test002 -> test001 回复文字消息"
|
||||
resp=$(http_post "$API/msg/send_msg" "{\"sendID\":\"test002\",\"recvID\":\"test001\",\"senderNickname\":\"测试二号\",\"senderPlatformID\":5,\"content\":{\"content\":\"收到 ${MARK}\"},\"contentType\":101,\"sessionType\":1}")
|
||||
if json_ok "$resp"; then pass "回复消息发送成功"; else fail "回复消息发送失败: $(echo "$resp" | head -c 200)"; fi
|
||||
|
||||
# ---------- 5. 上传文件到 MinIO 并发送语音/文件消息 ----------
|
||||
echo "5. 上传附件到 MinIO 并发送语音、文件消息"
|
||||
mkdir -p .selftest
|
||||
echo "openim selftest file ${MARK}" > .selftest/selftest-file.txt
|
||||
head -c 4096 /dev/urandom 2>/dev/null > .selftest/selftest-voice.amr || printf 'fake-amr-%s' "$MARK" > .selftest/selftest-voice.amr
|
||||
FILE_SIZE=$(wc -c < .selftest/selftest-file.txt | tr -d ' ')
|
||||
VOICE_SIZE=$(wc -c < .selftest/selftest-voice.amr | tr -d ' ')
|
||||
|
||||
MCHOST="http://${MINIO_AK}:${MINIO_SK}@${IP}:${MINIO_PORT}"
|
||||
mc_run() { docker run --rm --network host -v "$PWD/.selftest:/data" -e "MC_HOST_local=${MCHOST}" --entrypoint mc "$MC_IMAGE" "$@"; }
|
||||
|
||||
if mc_run cp "/data/selftest-file.txt" "local/openim/selftest/${MARK}/" >/dev/null 2>&1 \
|
||||
&& mc_run cp "/data/selftest-voice.amr" "local/openim/selftest/${MARK}/" >/dev/null 2>&1; then
|
||||
FILE_URL=$(mc_run share download --expire 168h "local/openim/selftest/${MARK}/selftest-file.txt" 2>/dev/null | grep -o 'http[^ ]*' | head -1)
|
||||
VOICE_URL=$(mc_run share download --expire 168h "local/openim/selftest/${MARK}/selftest-voice.amr" 2>/dev/null | grep -o 'http[^ ]*' | head -1)
|
||||
if [ -n "$FILE_URL" ] && [ -n "$VOICE_URL" ]; then
|
||||
pass "附件已上传 MinIO 并生成访问链接"
|
||||
|
||||
resp=$(http_post "$API/msg/send_msg" "{\"sendID\":\"test001\",\"recvID\":\"test002\",\"senderNickname\":\"测试一号\",\"senderPlatformID\":5,\"content\":{\"uuid\":\"\",\"soundPath\":\"\",\"sourceUrl\":\"${VOICE_URL}\",\"dataSize\":${VOICE_SIZE},\"duration\":3},\"contentType\":103,\"sessionType\":1}")
|
||||
if json_ok "$resp"; then pass "语音消息发送成功"; else fail "语音消息发送失败: $(echo "$resp" | head -c 200)"; fi
|
||||
|
||||
resp=$(http_post "$API/msg/send_msg" "{\"sendID\":\"test001\",\"recvID\":\"test002\",\"senderNickname\":\"测试一号\",\"senderPlatformID\":5,\"content\":{\"filePath\":\"\",\"uuid\":\"\",\"sourceUrl\":\"${FILE_URL}\",\"fileName\":\"selftest-file.txt\",\"fileSize\":${FILE_SIZE}},\"contentType\":105,\"sessionType\":1}")
|
||||
if json_ok "$resp"; then pass "文件消息发送成功"; else fail "文件消息发送失败: $(echo "$resp" | head -c 200)"; fi
|
||||
else
|
||||
fail "MinIO 生成下载链接失败"
|
||||
fi
|
||||
else
|
||||
fail "附件上传 MinIO 失败(检查 minio 容器与 ${IP}:${MINIO_PORT} 连通性)"
|
||||
fi
|
||||
|
||||
# ---------- 6. 消息回查 ----------
|
||||
echo "6. 消息落库回查(/msg/search_msg)"
|
||||
sleep 2
|
||||
resp=$(http_post "$API/msg/search_msg" "{\"sendID\":\"test001\",\"recvID\":\"test002\",\"contentType\":0,\"sendTime\":\"\",\"sessionType\":1,\"pagination\":{\"pageNumber\":1,\"showNumber\":20}}")
|
||||
if json_ok "$resp" && echo "$resp" | grep -q "$MARK"; then
|
||||
pass "文字消息已落库可回查"
|
||||
else
|
||||
fail "消息回查未找到刚发送的内容: $(echo "$resp" | head -c 200)"
|
||||
fi
|
||||
if echo "$resp" | grep -q "selftest-voice.amr"; then pass "语音消息已落库可回查"; else fail "语音消息回查未找到"; fi
|
||||
if echo "$resp" | grep -q "selftest-file.txt"; then pass "文件消息已落库可回查"; else fail "文件消息回查未找到"; fi
|
||||
|
||||
# ---------- 7. LiveKit 一对一语音房间 ----------
|
||||
echo "==============================================="
|
||||
echo " LiveKit 自测 (信令: $LK_WS)"
|
||||
echo "==============================================="
|
||||
ROOM="selftest-$(date +%s)"
|
||||
echo "7. 两名用户进入同一房间,主叫发布音频,验证双向可见"
|
||||
|
||||
cleanup_lk() { docker rm -f lk-caller lk-callee >/dev/null 2>&1 || true; }
|
||||
trap cleanup_lk EXIT
|
||||
cleanup_lk
|
||||
|
||||
lk() { docker run --rm --network host "$LK_CLI_IMAGE" --url "$1" --api-key "$LK_KEY" --api-secret "$LK_SECRET" "${@:2}"; }
|
||||
|
||||
docker run -d --name lk-caller --network host "$LK_CLI_IMAGE" \
|
||||
--url "$LK_WS" --api-key "$LK_KEY" --api-secret "$LK_SECRET" \
|
||||
room join --identity caller --publish-demo "$ROOM" >/dev/null 2>&1
|
||||
docker run -d --name lk-callee --network host "$LK_CLI_IMAGE" \
|
||||
--url "$LK_WS" --api-key "$LK_KEY" --api-secret "$LK_SECRET" \
|
||||
room join --identity callee "$ROOM" >/dev/null 2>&1
|
||||
|
||||
sleep 10
|
||||
plist=$(lk "$LK_HTTP" room participants list "$ROOM" 2>&1)
|
||||
if echo "$plist" | grep -q "caller" && echo "$plist" | grep -q "callee"; then
|
||||
pass "双方已进入同一房间(房间: $ROOM)"
|
||||
else
|
||||
fail "房间成员不符,list 输出: $(echo "$plist" | head -c 300)"
|
||||
fi
|
||||
|
||||
# 主叫发布了媒体轨道(publish-demo 会发布音视频 demo 轨)
|
||||
if echo "$plist" | grep -qi "MICROPHONE\|AUDIO"; then
|
||||
pass "主叫音频轨道已发布"
|
||||
else
|
||||
note "未从成员列表确认音频轨道(可能输出格式差异),请人工核对: lk room participants list"
|
||||
fi
|
||||
|
||||
# 被叫端日志应出现订阅到轨道的记录
|
||||
if docker logs lk-callee 2>&1 | grep -qi "track"; then
|
||||
pass "被叫端已接收到主叫的媒体轨道(一对一语音链路打通)"
|
||||
else
|
||||
fail "被叫端日志未见轨道订阅记录: $(docker logs lk-callee 2>&1 | tail -5)"
|
||||
fi
|
||||
cleanup_lk
|
||||
trap - EXIT
|
||||
|
||||
# ---------- 汇总 ----------
|
||||
echo "==============================================="
|
||||
if [ "$FAILED" = "0" ]; then
|
||||
echo " 自测全部通过 ✔"
|
||||
echo " 客户端接入参数:"
|
||||
echo " OpenIM API : $API"
|
||||
echo " OpenIM WS : ws://${IP}:10001"
|
||||
echo " LiveKit WS : $LK_WS (key: $LK_KEY)"
|
||||
else
|
||||
echo " 自测存在失败项,请根据上方 [失败] 排查(docker compose logs -f openim-server / livekit)"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user