sync-codex-health 拉 health/alive/endpoints(key 文件 600); e2e --from-endpoints 真调中转;余额不足单独 break;refresh 不覆盖 call 结果。
243 lines
7.7 KiB
Bash
Executable file
243 lines
7.7 KiB
Bash
Executable file
#!/usr/bin/env bash
|
||
# clashctl — 本机 ClashX Meta / Mihomo 控制面薄封装
|
||
#
|
||
# 通用 API 命令 → vendored clash-api.py
|
||
# (upstream: Hsiifu3/clash-verge-skill, MIT)
|
||
# 神州/蛮荒缺口:ensure-direct · probe · regression · 系统代理铁律
|
||
#
|
||
# 约定(2026-07-13):
|
||
# - 系统代理必须 ON(否则上不了外网)
|
||
# - 隔离靠 mode=rule + 生产 DIRECT,不靠关系统代理
|
||
#
|
||
# 用法:
|
||
# bash scripts/manhuang/clashctl status
|
||
# bash scripts/manhuang/clashctl mode rule
|
||
# bash scripts/manhuang/clashctl ensure-direct
|
||
# bash scripts/manhuang/clashctl probe --deep
|
||
# bash scripts/manhuang/clashctl regression
|
||
set -euo pipefail
|
||
|
||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||
API_PY="${ROOT}/scripts/manhuang/clash-api.py"
|
||
PROBE_PY="${ROOT}/scripts/manhuang/probe-proxy-path.py"
|
||
REGRESS="${ROOT}/scripts/manhuang/check-net-path-regression.sh"
|
||
SNIPPET="${ROOT}/configs/manhuang/clash-rules-shenzhou-direct.snippet.yaml"
|
||
ENHANCED_CFG="${HOME}/.config/clash.meta/config-shenzhou-direct.yaml"
|
||
|
||
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}"
|
||
export CLASH_API="${CLASH_API:-http://127.0.0.1:9090}"
|
||
export CLASH_PREFER_HTTP="${CLASH_PREFER_HTTP:-1}"
|
||
export CLASH_SECRET="${CLASH_SECRET:-}"
|
||
|
||
API_BASE="${CLASH_API%/}"
|
||
|
||
# 与 snippet / 探针一致的核心生产后缀
|
||
PROD_SUFFIXES=(
|
||
weixiaoduo.com
|
||
wenpai.org
|
||
feibisi.com
|
||
feicode.com
|
||
cravatar.com
|
||
admincdn.com
|
||
)
|
||
|
||
usage() {
|
||
cat <<'EOF'
|
||
clashctl — ClashX Meta / Mihomo external-controller 封装
|
||
|
||
通用(转发 clash-api.py):
|
||
status 版本 / mode / 端口 / 流量 / 选中节点
|
||
mode [rule|global|direct]
|
||
groups | nodes <g> | select <g> <node>
|
||
delay <node> | delay-group <g>
|
||
rules [--limit N] | conns | dns <domain>
|
||
flush-dns | restart | upgrade-geo
|
||
|
||
本仓运维:
|
||
ensure-direct mode=rule + 检查生产 DIRECT;缺规则时尝试热加载增强配置
|
||
probe [--deep] 网络供应链探针
|
||
regression [--strict]
|
||
sys-proxy macOS scutil 系统代理(期望 ON)
|
||
chain-breaks 刷新 chain_v2 breaks 缓存(本地,不 SSH)
|
||
pay-probe [--force] 礼品卡/虚拟卡市价探针 → 刷新 breaks
|
||
e2e [--call] [--from-endpoints] 端到端;--call 可用 endpoints 文件
|
||
sync-codex [--refresh-quick] scout→Mac 号池 health
|
||
board [--dry-run] breaks → Task Board observe
|
||
|
||
环境:
|
||
CLASH_API=http://127.0.0.1:9090 CLASH_SECRET=…
|
||
CLASH_PREFER_HTTP=1 默认走 HTTP(ClashX Meta :9090)
|
||
EOF
|
||
}
|
||
|
||
api() {
|
||
if [[ -n "${CLASH_SECRET}" ]]; then
|
||
python3 "$API_PY" --api "$API_BASE" --secret "$CLASH_SECRET" "$@"
|
||
else
|
||
python3 "$API_PY" --api "$API_BASE" "$@"
|
||
fi
|
||
}
|
||
|
||
http_json() {
|
||
local method="$1" path="$2" data="${3:-}"
|
||
local url="${API_BASE}${path}"
|
||
local args=(-sS -m 8 -X "$method" "$url" -H "Content-Type: application/json")
|
||
[[ -n "${CLASH_SECRET}" ]] && args+=(-H "Authorization: Bearer ${CLASH_SECRET}")
|
||
if [[ -n "$data" ]]; then
|
||
curl "${args[@]}" -d "$data"
|
||
else
|
||
curl "${args[@]}"
|
||
fi
|
||
}
|
||
|
||
cmd_sys_proxy() {
|
||
if ! command -v scutil >/dev/null 2>&1; then
|
||
echo "scutil 不可用(非 macOS?)" >&2
|
||
return 1
|
||
fi
|
||
local out http_en socks_en http_host http_port on=0
|
||
out="$(scutil --proxy 2>/dev/null || true)"
|
||
http_en="$(echo "$out" | awk '/HTTPEnable/{print $3; exit}')"
|
||
https_en="$(echo "$out" | awk '/HTTPSEnable/{print $3; exit}')"
|
||
socks_en="$(echo "$out" | awk '/SOCKSEnable/{print $3; exit}')"
|
||
http_host="$(echo "$out" | awk '/^ HTTPProxy /{print $3; exit}')"
|
||
http_port="$(echo "$out" | awk '/^ HTTPPort /{print $3; exit}')"
|
||
socks_host="$(echo "$out" | awk '/^ SOCKSProxy /{print $3; exit}')"
|
||
socks_port="$(echo "$out" | awk '/^ SOCKSPort /{print $3; exit}')"
|
||
[[ "${http_en:-0}" == "1" || "${https_en:-0}" == "1" || "${socks_en:-0}" == "1" ]] && on=1
|
||
local dest="${http_host:-${socks_host:-?}}:${http_port:-${socks_port:-?}}"
|
||
if [[ "$on" -eq 1 ]]; then
|
||
echo "系统代理: ON → ${dest}(常态;关了上不了外网)"
|
||
return 0
|
||
fi
|
||
echo "系统代理: OFF ⚠ 本机可能上不了外网;请打开 ClashX Meta「系统代理」" >&2
|
||
echo "隔离靠 rule+生产 DIRECT,不要用关代理当隔离。" >&2
|
||
return 2
|
||
}
|
||
|
||
cmd_ensure_direct() {
|
||
echo "[ensure-direct] controller ${API_BASE}"
|
||
|
||
local mode
|
||
mode="$(http_json GET /configs | python3 -c 'import sys,json; print(json.load(sys.stdin).get("mode",""))' 2>/dev/null || true)"
|
||
if [[ -z "$mode" ]]; then
|
||
echo "FAIL: 无法连接 ${API_BASE}" >&2
|
||
return 1
|
||
fi
|
||
if [[ "$(echo "$mode" | tr '[:upper:]' '[:lower:]')" != "rule" ]]; then
|
||
echo "[ensure-direct] mode=${mode} → rule"
|
||
http_json PATCH /configs '{"mode":"rule"}' >/dev/null
|
||
else
|
||
echo "[ensure-direct] mode=rule ok"
|
||
fi
|
||
|
||
local missing
|
||
missing="$(
|
||
http_json GET /rules | PROD_LIST="${PROD_SUFFIXES[*]}" python3 -c '
|
||
import json, os, sys
|
||
need = [x for x in os.environ.get("PROD_LIST", "").split() if x]
|
||
data = json.load(sys.stdin)
|
||
have = set()
|
||
for r in data.get("rules") or []:
|
||
if str(r.get("proxy") or "") != "DIRECT":
|
||
continue
|
||
p = str(r.get("payload") or "").lower().strip()
|
||
if p:
|
||
have.add(p)
|
||
miss = []
|
||
for n in need:
|
||
nl = n.lower()
|
||
if nl in have or any(nl == h or nl.endswith("." + h) for h in have):
|
||
continue
|
||
miss.append(n)
|
||
print(" ".join(miss))
|
||
'
|
||
)"
|
||
|
||
if [[ -z "${missing// }" ]]; then
|
||
echo "[ensure-direct] 生产 DIRECT 已覆盖: ${PROD_SUFFIXES[*]}"
|
||
else
|
||
echo "[ensure-direct] 缺少 DIRECT: $missing"
|
||
if [[ -f "$ENHANCED_CFG" ]]; then
|
||
echo "[ensure-direct] 热加载 $ENHANCED_CFG"
|
||
local payload
|
||
payload="$(python3 -c 'import json,sys; print(json.dumps({"path": sys.argv[1]}))' "$ENHANCED_CFG")"
|
||
if http_json PUT "/configs?force=true" "$payload" >/dev/null 2>&1; then
|
||
echo "[ensure-direct] force reload 已提交"
|
||
http_json PATCH /configs '{"mode":"rule"}' >/dev/null 2>&1 || true
|
||
else
|
||
echo "[ensure-direct] force reload 失败;请手动合并:" >&2
|
||
echo " $SNIPPET" >&2
|
||
fi
|
||
else
|
||
echo "[ensure-direct] 无 $ENHANCED_CFG" >&2
|
||
echo " 请合并 $SNIPPET 到 rules 顶部后 reload" >&2
|
||
fi
|
||
fi
|
||
|
||
cmd_sys_proxy || true
|
||
echo "[ensure-direct] 完成 · 建议: clashctl regression"
|
||
}
|
||
|
||
cmd_probe() {
|
||
local args=()
|
||
[[ "${1:-}" == "--deep" || "${1:-}" == "deep" ]] && args+=(--deep)
|
||
shift || true
|
||
python3 "$PROBE_PY" "${args[@]}" "$@"
|
||
}
|
||
|
||
cmd_regression() {
|
||
bash "$REGRESS" "$@"
|
||
}
|
||
|
||
cmd_chain_breaks() {
|
||
export PYTHONPATH="${ROOT}/src${PYTHONPATH:+:$PYTHONPATH}"
|
||
python3 "${ROOT}/scripts/manhuang/refresh-chain-breaks.py" "$@"
|
||
}
|
||
|
||
cmd_board() {
|
||
# 先刷新 breaks,再推 Board
|
||
cmd_chain_breaks >/dev/null 2>&1 || true
|
||
bash "${ROOT}/scripts/manhuang/actions-to-board.sh" "$@"
|
||
}
|
||
|
||
cmd_pay_probe() {
|
||
bash "${ROOT}/scripts/manhuang/probe-pay-rails.sh" "$@"
|
||
}
|
||
|
||
cmd_e2e() {
|
||
export PYTHONPATH="${ROOT}/src${PYTHONPATH:+:$PYTHONPATH}"
|
||
python3 "${ROOT}/scripts/manhuang/probe-e2e-capability.py" "$@"
|
||
cmd_chain_breaks >/dev/null 2>&1 || true
|
||
}
|
||
|
||
cmd_sync_codex() {
|
||
bash "${ROOT}/scripts/manhuang/sync-codex-health.sh" "$@"
|
||
}
|
||
|
||
main() {
|
||
local cmd="${1:-}"
|
||
shift || true
|
||
case "$cmd" in
|
||
""|-h|--help|help) usage ;;
|
||
status|mode|groups|nodes|select|delay|delay-group|conns|conns-close|rules|dns|flush-dns|restart|upgrade-geo)
|
||
api "$cmd" "$@"
|
||
;;
|
||
ensure-direct|ensure) cmd_ensure_direct "$@" ;;
|
||
probe) cmd_probe "$@" ;;
|
||
regression|regress) cmd_regression "$@" ;;
|
||
sys-proxy|sysproxy|proxy-status) cmd_sys_proxy "$@" ;;
|
||
chain-breaks|breaks|chain) cmd_chain_breaks "$@" ;;
|
||
pay-probe|pay) cmd_pay_probe "$@" ;;
|
||
e2e|capability) cmd_e2e "$@" ;;
|
||
sync-codex|codex-sync|sync-health) cmd_sync_codex "$@" ;;
|
||
board|to-board) cmd_board "$@" ;;
|
||
*)
|
||
echo "unknown: $cmd" >&2
|
||
usage >&2
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
main "$@"
|