#!/usr/bin/env bash # apply-403-fix.sh — 幂等应用/回滚 dsh 403 修复补丁(方案 2) # # 背景:官方 dsh 把特权 /api 方法(settings.*、credentials.* 等)钉死在 # loopback,反向代理访问时即使配置了 --trusted-host 也返回 403。本脚本把 # 一行补丁打到运行时实际加载的全局 @deepseek-ai/dsh-client-connection 上, # 让特权方法也使用配置的 trustedHosts 列表校验(trustedHosts 仍是 # DNS-rebinding 围栏,不是认证,语义与上游一致)。 # # 用法: # apply-403-fix.sh # 应用补丁(幂等,已应用则跳过) # apply-403-fix.sh --check # 仅检查补丁状态,不修改 # apply-403-fix.sh --revert # 从备份回滚补丁 # apply-403-fix.sh --help # 帮助 # # 说明: # - 通过 `readlink -f $(which dsh)` 定位全局 dsh 包根,不依赖硬编码路径 # - 应用前自动备份为 lib/index.js.orig # - 补丁文件在工程内维护:patches/@deepseek-ai+dsh-client-connection@0.1.0-rc.7.patch set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PATCH_FILE="$SCRIPT_DIR/patches/@deepseek-ai+dsh-client-connection@0.1.0-rc.7.patch" # 期望补丁后特权检查行包含 trustedHosts(而非空列表) PATCHED_MARKER='PRIVILEGED_METHODS.has(method) && !isTrustedApiRequest(request, trustedHosts)' ORIGINAL_MARKER='PRIVILEGED_METHODS.has(method) && !isTrustedApiRequest(request, []))' die() { echo "[apply-403-fix] 错误: $*" >&2; exit 1; } info() { echo "[apply-403-fix] $*"; } # ── 定位运行时实际加载的全局 dsh 包 ────────────────────────────────────── DSH_BIN="$(command -v dsh || true)" if [ -z "$DSH_BIN" ]; then die "未找到 dsh 命令。请确认已全局安装 @deepseek-ai/dsh。" fi DSH_REAL="$(readlink -f "$DSH_BIN")" # .../lib/node_modules/@deepseek-ai/dsh/lib/bin.js DSH_ROOT="$(dirname "$(dirname "$DSH_REAL")")" # .../lib/node_modules/@deepseek-ai/dsh CC_DIR="$DSH_ROOT/node_modules/@deepseek-ai/dsh-client-connection" TARGET="$CC_DIR/lib/index.js" if [ ! -f "$TARGET" ]; then die "未找到 $TARGET(dsh 依赖布局与预期不符,请检查 dsh 安装)。" fi if [ ! -f "$PATCH_FILE" ]; then die "未找到补丁文件 $PATCH_FILE。" fi # ── 检查状态 ────────────────────────────────────────────────────────────── check_state() { if grep -qF "$PATCHED_MARKER" "$TARGET"; then echo "patched" elif grep -qF "$ORIGINAL_MARKER" "$TARGET"; then echo "unpatched" else echo "unknown" fi } # ── --check ─────────────────────────────────────────────────────────────── if [ "${1:-}" = "--check" ]; then case "$(check_state)" in patched) info "已打补丁: $TARGET"; exit 0 ;; unpatched) info "未打补丁: $TARGET"; exit 0 ;; *) die "无法识别 $TARGET 的状态(可能 dsh 版本已升级,补丁已过期)。" ;; esac fi # ── --revert ────────────────────────────────────────────────────────────── if [ "${1:-}" = "--revert" ]; then if [ "$(check_state)" = "unpatched" ]; then info "当前未打补丁,无需回滚。" exit 0 fi if [ ! -f "$TARGET.orig" ]; then die "没有备份文件 $TARGET.orig,无法回滚(可能不是本脚本打的补丁)。" fi cp "$TARGET.orig" "$TARGET" info "已从备份回滚: $TARGET" exit 0 fi # ── --help / 未知参数 ──────────────────────────────────────────────────── if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then grep '^#' "$0" | sed 's/^# \{0,1\}//' exit 0 fi if [ $# -gt 0 ]; then die "未知参数: $*(用 --help 查看用法)" fi # ── 应用补丁(幂等) ───────────────────────────────────────────────────── case "$(check_state)" in patched) info "已打补丁,跳过: $TARGET" exit 0 ;; unknown) die "无法识别 $TARGET 的状态,拒绝修改。请人工检查(可能 dsh 已升级)。" ;; esac # 备份原始文件(仅在应用时创建,--revert 依赖它) if [ ! -f "$TARGET.orig" ]; then cp "$TARGET" "$TARGET.orig" info "已备份原始文件: $TARGET.orig" else info "备份已存在,保留: $TARGET.orig" fi # 在包根目录执行 patch -p1(补丁内路径为 lib/index.js) if ! (cd "$CC_DIR" && patch -p1 --forward < "$PATCH_FILE"); then info "patch 应用失败,正在回滚本次修改…" if [ -f "$TARGET.orig" ]; then cp "$TARGET.orig" "$TARGET"; fi die "补丁应用失败,已恢复原状。请人工检查。" fi # 应用后校验 if [ "$(check_state)" = "patched" ]; then info "补丁已应用: $TARGET" else die "补丁应用后校验失败,请人工检查 $TARGET。" fi info "提示: 需要重启 dsh(my_deepseek.sh stop && my_deepseek.sh)后生效。"