文件内容
scripts/update.sh
#!/bin/bash
# baidu drive Skill 自动更新脚本
# 通过百度配置接口检测并更新 Skill 文件
# CLI 更新由 bdpan 自身管理,本脚本不负责
set -e
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
CONFIG_API="https://pan.baidu.com/act/v2/api/conf?conf_key=bd_skills"
# 脚本所在目录(用于定位 Skill 文件)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
VERSION_FILE="${SKILL_DIR}/VERSION"
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 版本比较:返回 0 表示 $1 > $2,1 表示 $1 = $2,2 表示 $1 < $2
version_compare() {
if [ "$1" = "$2" ]; then
return 1
fi
local IFS=.
local i ver1=($1) ver2=($2)
for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)); do
ver1[i]=0
done
for ((i=0; i<${#ver1[@]}; i++)); do
if [ -z "${ver2[i]}" ]; then
ver2[i]=0
fi
if ((10#${ver1[i]} > 10#${ver2[i]})); then
return 0
fi
if ((10#${ver1[i]} < 10#${ver2[i]})); then
return 2
fi
done
return 1
}
# 去除版本号的 v 前缀(如 v1.2.0 → 1.2.0)
strip_v_prefix() {
echo "$1" | sed 's/^v//'
}
# 获取本地 Skill 版本
get_local_version() {
if [ -f "$VERSION_FILE" ]; then
local raw=$(cat "$VERSION_FILE" | tr -d '[:space:]')
strip_v_prefix "$raw"
else
echo "unknown"
fi
}
# 从 query string 中提取指定 key 的值
# 用法: query_get "version=1.1.2&url=https://..." "version"
query_get() {
local qs="$1"
local key="$2"
echo "$qs" | tr '&' '\n' | while IFS='=' read -r k v; do
if [ "$k" = "$key" ]; then
echo "$v"
return 0
fi
done
}
# 请求配置接口,返回 skills_info query string
fetch_skills_info() {
local response=""
if command -v curl &> /dev/null; then
response=$(curl -fsSL --connect-timeout 10 --max-time 30 "$CONFIG_API" 2>/dev/null) || {
log_error "无法连接配置服务器,请检查网络连接"
return 1
}
elif command -v wget &> /dev/null; then
response=$(wget -qO- --timeout=30 "$CONFIG_API" 2>/dev/null) || {
log_error "无法连接配置服务器,请检查网络连接"
return 1
}
else
log_error "未找到 curl 或 wget"
return 1
fi
# 检查 errno:从 JSON 中提取 "errno": 0
local errno=$(echo "$response" | grep -o '"errno"[[:space:]]*:[[:space:]]*[0-9]*' | head -1 | grep -o '[0-9]*$')
if [ "$errno" != "0" ]; then
log_error "配置接口返回错误 (errno: ${errno:-unknown})"
return 1
fi
# 从 response 中提取 skills_info 的值
# API 返回中 & 可能被编码为 \u0026,需要还原
local skills_info=$(echo "$response" | sed 's/\\u0026/\&/g' | grep -o 'version=[^"]*' | head -1 | sed 's/\\//g')
if [ -z "$skills_info" ]; then
log_error "未获取到版本配置信息"
return 1
fi
echo "$skills_info"
}
# 校验下载 URL 是否来自百度官方白名单域名
# 仅允许:*.baidu.com / issuecdn.baidupcs.com / pan.baidu.com
validate_url_host() {
local url="$1"
# 必须 https
case "$url" in
https://*) ;;
*)
log_error "拒绝更新:下载地址必须使用 HTTPS(实际:${url})"
return 1
;;
esac
# 提取 host(去掉 https://、路径、端口)
local host
host=$(echo "$url" | sed -E 's|^https://([^/:]+).*$|\1|')
if [ -z "$host" ]; then
log_error "拒绝更新:无法解析下载地址的主机名"
return 1
fi
case "$host" in
*.baidu.com|baidu.com|*.baidupcs.com|baidupcs.com|*.bdimg.com|*.bdstatic.com)
return 0
;;
*)
log_error "拒绝更新:下载地址不在百度官方域名白名单内(host=${host})"
log_error " 仅允许的域名:*.baidu.com、*.baidupcs.com、*.bdimg.com、*.bdstatic.com"
return 1
;;
esac
}
# 更新 Skill
do_update() {
local remote_url="$1"
local remote_version="$2"
if [ -z "$remote_url" ]; then
log_error "未找到 Skill 下载地址"
return 1
fi
# 域名白名单校验(在任何下载动作之前)
validate_url_host "$remote_url" || return 1
log_info "正在下载 Skill 更新包 (v${remote_version})..."
log_info "下载地址: ${remote_url}"
# 创建临时目录
local tmp_dir=$(mktemp -d)
trap "rm -rf '$tmp_dir'" EXIT
# 下载 zip
local zip_path="${tmp_dir}/baidu-drive.zip"
if command -v curl &> /dev/null; then
curl -fsSL -o "$zip_path" "$remote_url" || {
log_error "下载 Skill 更新包失败"
return 1
}
elif command -v wget &> /dev/null; then
wget -q -O "$zip_path" "$remote_url" || {
log_error "下载 Skill 更新包失败"
return 1
}
fi
# SHA256 完整性校验(强制)
local checksum=$(query_get "$SKILLS_INFO" "checksum")
if [ -z "$checksum" ]; then
log_error "配置接口未提供 checksum,无法验证更新包完整性,拒绝更新"
return 1
fi
local actual=""
if command -v sha256sum &> /dev/null; then
actual=$(sha256sum "$zip_path" | awk '{print $1}')
elif command -v shasum &> /dev/null; then
actual=$(shasum -a 256 "$zip_path" | awk '{print $1}')
else
log_error "未找到 sha256sum/shasum 工具,无法验证更新包完整性"
return 1
fi
if [ "$actual" != "$checksum" ]; then
log_error "SHA256 校验失败!文件可能被篡改"
log_error " 期望: ${checksum}"
log_error " 实际: ${actual}"
return 1
fi
log_info "SHA256 校验通过"
# 解压覆盖
log_info "正在解压更新..."
if command -v unzip &> /dev/null; then
unzip -qo "$zip_path" -d "$SKILL_DIR" || {
log_error "解压失败"
return 1
}
else
log_error "未找到 unzip 工具"
return 1
fi
# 更新 VERSION 文件
echo "$remote_version" > "$VERSION_FILE"
log_info "Skill 已更新到 v${remote_version}"
}
# 全局变量
SKILLS_INFO=""
# 主函数
main() {
local check_only="no"
local auto_yes="no"
# 解析参数
while [[ $# -gt 0 ]]; do
case $1 in
--check|-c)
check_only="yes"
shift
;;
--yes|-y)
auto_yes="yes"
shift
;;
--help|-h)
echo "用法: $0 [选项]"
echo ""
echo "选项:"
echo " --check, -c 仅检查更新,不执行"
echo " --yes, -y 跳过确认,自动更新"
echo " --help 显示帮助信息"
exit 0
;;
*)
log_error "未知参数: $1"
echo "使用 --help 查看帮助信息"
exit 1
;;
esac
done
# 获取本地版本
local local_version=$(get_local_version)
# 请求远程配置
log_info "正在检查更新..."
SKILLS_INFO=$(fetch_skills_info) || {
log_warn "无法获取更新信息,请稍后再试"
exit 1
}
# 解析远程版本和下载地址(strip v 前缀用于比较)
local remote_version=$(query_get "$SKILLS_INFO" "version")
local remote_version_clean=$(strip_v_prefix "$remote_version")
local remote_url=$(query_get "$SKILLS_INFO" "url")
if [ -z "$remote_version" ]; then
log_error "配置中未包含版本信息"
exit 1
fi
# 展示状态
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} baidu drive Skill 更新检查${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
echo -e " 本地版本: ${local_version}"
echo -e " 最新版本: ${remote_version}"
# 版本对比
local needs_update="no"
if [ "$local_version" = "unknown" ]; then
echo -e " 状态: ${YELLOW}版本未知,建议更新${NC}"
needs_update="yes"
else
set +e
version_compare "$remote_version_clean" "$local_version"
local cmp_result=$?
set -e
if [ $cmp_result -eq 0 ]; then
echo -e " 状态: ${YELLOW}有新版本可用${NC}"
needs_update="yes"
else
echo -e " 状态: ${GREEN}已是最新${NC}"
fi
fi
echo ""
# 无更新
if [ "$needs_update" = "no" ]; then
log_info "Skill 已是最新版本"
exit 0
fi
# 仅检查模式
if [ "$check_only" = "yes" ]; then
exit 0
fi
# 用户确认
# 安全限制:Agent 环境中禁止使用 --yes 跳过确认
if [ "$auto_yes" = "yes" ]; then
if [ -n "$CLAUDE_CODE" ] || [ -n "$ANTHROPIC_API_KEY" ] || [ -n "$MCP_SERVER" ]; then
log_warn "检测到 Agent 环境,忽略 --yes 参数,保留用户确认环节"
auto_yes="no"
fi
fi
if [ "$auto_yes" != "yes" ]; then
# 在最终确认前展示下载源与校验值,便于用户做安全审查
local _preview_url=$(query_get "$SKILLS_INFO" "url")
local _preview_checksum=$(query_get "$SKILLS_INFO" "checksum")
echo -e "${YELLOW}本次更新将影响 Skill 目录文件:${NC}${SKILL_DIR}"
echo " 下载地址: ${_preview_url:-<未提供>}"
echo " SHA256: ${_preview_checksum:-<未提供(将被拒绝更新)>}"
echo " 影响范围: 仅 Skill 文档/脚本,不会修改用户网盘内容或本地文件"
echo ""
echo -n -e "${YELLOW}是否更新 Skill 到 v${remote_version}? [y/N] ${NC}"
read -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log_info "已取消更新"
exit 0
fi
fi
echo ""
# 执行更新
do_update "$remote_url" "$remote_version" || {
log_error "Skill 更新失败"
exit 1
}
echo ""
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN} ✓ 更新完成${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
}
# 执行主函数
main "$@"