不知道为什么,总喜欢看自己的使用统计数据。
最近上手部署了docker的CliProxyApi,管理我御三家的AI账号,用了段时间,昨晚更新的时候发现使用的统计数据给重置了...有种玩游戏闯关重头再来的感觉
查了文档,扒了扒源码看了看,目前似乎统计数据没有持久化,是丢内存里的。所以有了这个脚本,暂时对付对付。
逻辑很简单,就是先导出、更新重启,再导入。测了下没啥问题,分享给大家。

主要修改三个地方:
BASE_DIR="/root/cliproxyapi"MANAGEMENT_PASSWORD="your-pass-word"CONTAINER_NAME="cli-proxy-api"然后可以设置下定时执行,就定时会更新镜像,如果已经是最新镜像,就会跳过
# 编辑添加定时任务
crontab -e
# 定时更新 每30min检查一次
*/30 * * * * /root/cliproxyapi/upgrade.sh upgrade
#!/usr/bin/env bash
set -euo pipefail
# ============================================================
# CLIProxyAPI 升级/备份工具脚本
#
# 本脚本提供 3 个子命令,通过第一个参数选择要执行的功能:
#
# 1) export 仅导出当前使用统计为 JSON 文件(做备份用)
# 2) import 从最近一次的 JSON 备份中导入使用统计
# 3) upgrade 一键升级流程:先导出 -> 升级容器 -> 再导入
#
# 使用示例:
# ./upgrade.sh export # 只备份当前使用统计
# ./upgrade.sh import # 只从最近的备份恢复使用统计
# ./upgrade.sh upgrade # 完整执行升级流程(推荐)
# ============================================================
# ===== 基本配置 =====
# CLIProxyAPI 在宿主机上的工作目录(通常是放 docker-compose.yml 的目录)
BASE_DIR="/root/cliproxyapi"
# 统计信息备份目录
STATS_DIR="${BASE_DIR}/stats"
# 管理面板访问密码(对应 CLIProxyAPI 管理端的 X-Management-Key)
MANAGEMENT_PASSWORD="your-pass-word"
# CLIProxyAPI 服务访问地址(我这里修改端口)
HOST="127.0.0.1"
PORT="8317"
# docker-compose 中定义的服务名称(注意:是 service 名,不是镜像名)
CONTAINER_NAME="cli-proxy-api"
# 管理 API 基础地址
API_BASE="http://${HOST}:${PORT}/v0/management"
AUTH_HEADER="X-Management-Key: ${MANAGEMENT_PASSWORD}"
# 确保备份目录存在
mkdir -p "${STATS_DIR}"
# 生成时间戳,格式:20250128-153045
timestamp() {
date +"%Y%m%d-%H%M%S"
}
# ------------------------------------------------------------
# 子命令:export
# 功能:从 CLIProxyAPI 导出当前使用统计,并保存为 JSON 备份文件
# 用法:./upgrade.sh export
# ------------------------------------------------------------
export_usage() {
local ts file
ts="$(timestamp)"
file="${STATS_DIR}/usage-${ts}.json"
echo "导出使用统计到 ${file}..."
# 使用 curl 同时获取响应体和 HTTP 状态码
RESPONSE=$(curl -sS -w "\n%{http_code}" -H "${AUTH_HEADER}" "${API_BASE}/usage/export")
HTTP_CODE=$(echo "${RESPONSE}" | tail -n1)
BODY=$(echo "${RESPONSE}" | sed '$d')
if [[ "${HTTP_CODE}" != "200" ]]; then
echo "导出失败 (HTTP ${HTTP_CODE}): ${BODY}"
return 1
fi
echo "${BODY}" > "${file}"
echo "导出完成: ${file}"
}
# ------------------------------------------------------------
# 子命令:import
# 功能:从 stats 目录中“最新的” usage-*.json 备份文件中导入使用统计
# 用法:./upgrade.sh import
# 场景:升级后、迁移后,需要把使用统计恢复回来
# ------------------------------------------------------------
import_latest_usage() {
local latest
latest="$(ls -1t "${STATS_DIR}"/usage-*.json 2>/dev/null | head -n 1 || true)"
if [[ -z "${latest}" ]]; then
echo "没有找到可用的使用统计备份,跳过导入。"
return 0
fi
echo "导入最近一次的使用统计:${latest} ..."
RESPONSE=$(curl -sS -w "\n%{http_code}" -X POST \
-H "${AUTH_HEADER}" \
-H "Content-Type: application/json" \
-d @"${latest}" \
"${API_BASE}/usage/import")
HTTP_CODE=$(echo "${RESPONSE}" | tail -n1)
BODY=$(echo "${RESPONSE}" | sed '$d')
if [[ "${HTTP_CODE}" == "200" ]]; then
echo "导入完成。"
else
echo "导入失败 (HTTP ${HTTP_CODE}): ${BODY}"
return 1
fi
}
# ------------------------------------------------------------
# 辅助函数:等待 CLIProxyAPI 服务启动并能正常响应 HTTP 200
# 在 upgrade 流程中用于“拉起容器后等待后端真正就绪”
# ------------------------------------------------------------
wait_for_server() {
echo "等待 CLIProxyAPI 启动..."
for _ in $(seq 1 60); do
if curl -sS -o /dev/null -w "%{http_code}" "http://${HOST}:${PORT}/" 2>/dev/null | grep -q "200"; then
echo "服务已就绪。"
return 0
fi
sleep 2
done
echo "等待服务超时,请检查容器是否正常启动。" >&2
return 1
}
# ------------------------------------------------------------
# 子命令:upgrade (一键升级)
#
# 完整流程:
# 1. 导出当前使用统计(避免升级丢数据)
# 2. 使用 docker compose 拉取最新镜像并重启 cli-proxy-api 容器
# 3. 等待 CLIProxyAPI HTTP 服务就绪
# 4. 从刚刚导出的备份中重新导入使用统计
#
# 用法:./upgrade.sh upgrade
# ------------------------------------------------------------
upgrade() {
echo "=== 步骤 1:导出当前使用统计 ==="
export_usage
echo "=== 步骤 2:检查并拉取最新镜像 ==="
cd "${BASE_DIR}"
# 获取当前容器使用的镜像名称(从 docker-compose.yml 解析或从运行中的容器获取)
# 优先从运行中的容器获取,如果没有运行则从 docker-compose config 解析
if docker ps --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then
# 容器正在运行,从容器获取镜像名
current_image=$(docker inspect --format='{{.Config.Image}}' "${CONTAINER_NAME}" 2>/dev/null || true)
else
# 容器未运行,从 docker-compose config 解析镜像名
current_image=$(docker compose config --format json 2>/dev/null | grep -o '"image":"[^"]*"' | head -1 | cut -d'"' -f4 || true)
fi
if [[ -z "${current_image}" ]]; then
echo "警告:无法确定当前镜像名称,将强制拉取并重启。" >&2
current_image="unknown"
else
echo "当前使用的镜像:${current_image}"
# 获取 pull 前的镜像 ID(如果镜像存在)
image_id_before=$(docker image inspect "${current_image}" --format='{{.Id}}' 2>/dev/null || echo "")
fi
# 执行 pull
echo "正在拉取最新镜像..."
if ! docker compose pull "${CONTAINER_NAME}"; then
echo "拉取镜像失败,请检查 docker compose 配置和网络。" >&2
return 1
fi
# 获取 pull 后的镜像 ID
if [[ "${current_image}" != "unknown" ]]; then
image_id_after=$(docker image inspect "${current_image}" --format='{{.Id}}' 2>/dev/null || echo "")
# 如果 pull 前后镜像 ID 相同,说明没有新镜像
if [[ -n "${image_id_before}" && -n "${image_id_after}" && "${image_id_before}" == "${image_id_after}" ]]; then
echo "检测到镜像已是最新版本(镜像 ID 未变化),本次无需升级容器,后续步骤(重启 / 导入)将跳过。"
echo "=== 升级流程结束(无新镜像,无需变更) ==="
return 0
fi
fi
echo "检测到有新镜像或镜像已更新,继续执行容器重启和数据导入。"
echo "=== 步骤 3:重启容器 ==="
docker compose stop "${CONTAINER_NAME}" || true
docker compose up -d "${CONTAINER_NAME}"
echo "=== 步骤 4:等待服务就绪 ==="
wait_for_server
echo "=== 步骤 5:导入使用统计 ==="
import_latest_usage
echo "=== 升级流程完成 ==="
}
# ========= 命令行入口 =========
# 如果你想“无参数就默认执行 upgrade”,可以把下面的 "${1:-}" 改成:
# cmd="${1:-upgrade}"
# 然后把 case 的变量改成 "${cmd}" 即可。
#
# 当前版本设计为“必须显式传入子命令”,这样更安全,不容易误操作。
case "${1:-}" in
export)
export_usage
;;
import)
import_latest_usage
;;
upgrade)
upgrade
;;
*)
echo "用法: $0 {export|import|upgrade}" >&2
echo
echo "说明:"
echo " export 只导出当前使用统计到 stats 目录"
echo " import 只从最近的 usage-*.json 备份文件中导入使用统计"
echo " upgrade 先导出,再升级容器,最后导入(推荐用于升级系统)"
exit 1
;;
esac
加载评论中...