🌴 Sync 2026-10-30 02:13:56

This commit is contained in:
github-actions[bot] committed 2026-10-30 02:13:56 +08:00
1 parent 8d20d896c0
commit 57d68d2cec
34 files changed
+1383 -304

No files matched your search

+2 -2
View File
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=isp-split
PKG_VERSION:=0.3.0
PKG_RELEASE:=4
PKG_VERSION:=0.3.2
PKG_RELEASE:=5
PKG_LICENSE:=MIT
PKG_LICENSE_FILES:=LICENSE
PKG_MAINTAINER:=jihebube <jihebube@users.noreply.github.com>
+5
View File
@@ -7,6 +7,11 @@ STOP=10
start() {
(
# A-14:体检/状态缓存属主为 root,目录 700、文件 600。开机路径
# 只做幂等收紧,不主动创建敏感文件。
umask 077
mkdir -p /etc/isp-split/state 2>/dev/null || true
chmod 700 /etc/isp-split/state 2>/dev/null || true
i=0
while [ "$i" -lt 60 ]; do
if nft list table inet mwan3 >/dev/null 2>&1; then
+8 -5
View File
@@ -187,9 +187,9 @@ LINE_CHECK_ENABLE=1
# 归属地隐私开关:1=允许向第三方查询出口 IP 归属地;0=只关闭归属地查询
#
# 开启后,体检会把**每条 WAN 的出口公网 IP**发送给第三方(默认百度,
# 失败时 ip-api.com)查询归属地。只发送出口 IP,不发送内网地址、设备名、
# MAC、流量内容。ip-api 使用 HTTP,传输不加密;介意时请设为 0,或把
# LINE_CHECK_GEO_PROVIDER 固定为 baidu。
# 默认只走百度 HTTPS)查询归属地。只发送出口 IP,不发送内网地址、设备名、
# MAC、流量内容。ip-api 是显式选项且使用 HTTP 明文,不是默认兜底;介意时
# 请设为 0,或把 LINE_CHECK_GEO_PROVIDER 固定为 baidu。
# 关闭后不发起任何归属地请求,卡片显示「归属地:未查询(隐私开关关闭)」。
# 省/市不可知,但仍可用本地 @isp_* 集合判断运营商大类,不需要外发。
# 注意:此开关只关归属地;出口 IP 回显、STUN 与延迟探测仍会外发。
@@ -197,7 +197,7 @@ LINE_CHECK_ENABLE=1
LINE_CHECK_GEO=1
# 归属地服务商:auto | baidu | ip-api | off
# auto 先百度(HTTPS、国内可达),失败转 ip-api(HTTP)
# auto 只走百度(HTTPS、国内可达);ip-api 不再自动兜底
# baidu 只用百度;失败显示「归属地未知」,不会自动转 HTTP 源
# ip-api 只用 ip-api(HTTP,明文)
# off 等同 LINE_CHECK_GEO=0
@@ -244,7 +244,10 @@ stun.cloudflare.com:3478
"
# 出口 IP 回显服务(逗号分隔,按顺序尝试)。只用于拿公网出口 IP。
LINE_CHECK_ECHO_URLS="https://api.ipify.org,https://ipv4.icanhazip.com"
# F-23:默认回显源改为国内可达的 HTTPS 服务,按顺序尝试。允许返回裸 IP
# 或含 IP 的文本(如 ipip.net 的“当前 IP:x.x.x.x”),取第一个合法 IPv4。
# 不要放境外 api.ipify.org / icanhazip.com:国内运营商网络经常 RST/超时。
LINE_CHECK_ECHO_URLS="https://ip.3322.net,https://myip.ipip.net,https://ddns.oray.com/checkip"
# 体检请求的 User-Agent。部分运营商站点对默认 curl UA 返回 412/418,
# 必须带浏览器 UA;不要改成空值。
@@ -9,6 +9,10 @@
/etc/init.d/isp-split enable >/dev/null 2>&1
# A-14:state 首次创建就 700;旧版本留下的 755 也在这里纠正。
mkdir -p /etc/isp-split/state 2>/dev/null || true
chmod 700 /etc/isp-split/state 2>/dev/null || true
# 启用每日更新(只改数据,不重写 mwan3 配置)
if [ -x /etc/init.d/cron ]; then
mkdir -p /etc/crontabs
+108 -15
View File
@@ -24,6 +24,9 @@
# sh teardown.sh --list # 打印清单(selftest 与 postrm 漂移断言用),不动作
# 选项:
# --keep-conf-bak # 保留 /etc/isp-split.conf.bak.*(默认删除并逐条打印)
# --i-know-what-im-doing # 显式确认非默认 BASE(默认只允许 /etc/isp-split
# # 或 ISP_SPLIT_DESTDIR 下的沙箱路径)。系统目录
# # (/、/etc、/usr、/var、/bin、/sbin、/lib…)永远拒绝
#
# 为什么默认删 .conf.bak.*:它们是安装脚本每次覆盖安装都会新生成的副本,
# 不是用户手写的备份。留着等于"卸载"留下一批与工具同名同前缀的孤儿文件,
@@ -54,6 +57,12 @@ case "$DESTDIR" in
esac
BASE=${ISP_SPLIT_BASE:-$DESTDIR/etc/isp-split}
# 归一化尾部斜杠(含多个):`/etc//` 与 `/etc` 必须落到同一条拒绝分支;
# 根路径保持 `/`,否则空串会绕过下面的空值检查。
if [ "$BASE" != "/" ]; then
BASE=$(printf '%s' "$BASE" | sed 's#/*$##')
[ -n "$BASE" ] || BASE=/
fi
CONF=${ISP_SPLIT_CONF:-$DESTDIR/etc/isp-split.conf}
CRONTAB=${ISP_SPLIT_CRONTAB:-$DESTDIR/etc/crontabs/root}
LIB=${ISP_SPLIT_LIB:-$DESTDIR/usr/lib/isp-split}
@@ -69,9 +78,56 @@ CRON_SERVICE=${CRON_SERVICE:-$DESTDIR/etc/init.d/cron}
RPCD_SERVICE=${RPCD_SERVICE:-$DESTDIR/etc/init.d/rpcd}
KEEP_CONF_BAK=0
IWKD=0
SCOPE=""
say() { printf '[teardown] %s\n' "$*"; }
# A-10:BASE 是 `rm -rf "$BASE"` 的唯一根,必须在任何删除之前过白名单。
# 真实事故:ISP_SPLIT_BASE=/etc 时,旧 cmd_files 直接 rm -rf /etc,
# 而固定清单还会按 DESTDIR 删真实安装文件(附录 C)。
# 规则:
# 1) 永远拒绝空、/、相对路径、含 ..、以及系统目录本身;
# 2) 真实根(DESTDIR 为空)只接受默认 /etc/isp-split,其它路径必须显式
# --i-know-what-im-doing;
# 3) 沙箱(DESTDIR 非空)只接受 DESTDIR 子树(防止演练误删宿主 /etc)。
validate_base() {
case "$BASE" in
"" | "/" | "." | ".." | *..*)
echo "ISP_SPLIT_BASE 非法: '$BASE'(拒绝空、/、相对路径与 ..)" >&2
return 1
;;
/*) ;;
*)
echo "ISP_SPLIT_BASE 必须是绝对路径: '$BASE'" >&2
return 1
;;
esac
case "$BASE" in
/etc | /usr | /var | /bin | /sbin | /lib | /lib64 | /boot | /dev | /proc | /sys | /www | /root | /home | /tmp | /mnt | /opt)
echo "ISP_SPLIT_BASE 指向系统目录,拒绝执行: '$BASE'" >&2
return 1
;;
esac
# 沙箱:BASE 必须在 DESTDIR 里面;否则演练会删到宿主路径。
if [ -n "$DESTDIR" ]; then
case "$BASE" in
"$DESTDIR"/*) return 0 ;;
*)
echo "ISP_SPLIT_BASE 必须在 ISP_SPLIT_DESTDIR ($DESTDIR) 子树内: '$BASE'" >&2
return 1
;;
esac
fi
# 真实根:默认路径直接放行;其它路径必须显式确认。
[ "$BASE" = "/etc/isp-split" ] && return 0
[ "$IWKD" = "1" ] && return 0
echo "ISP_SPLIT_BASE 不是默认的 /etc/isp-split: '$BASE'" >&2
echo " 如确需操作该目录,请加 --i-know-what-im-doing;系统目录永远不会被接受。" >&2
return 1
}
have() { command -v "$1" >/dev/null 2>&1; }
# 只有真实根(DESTDIR 为空)才动服务:沙箱演练里重启服务既没意义,也会误伤宿主。
@@ -86,13 +142,13 @@ svc_restart() {
# 文件/目录清单(相对 DESTDIR)。--list 与 files 段共用,杜绝两处漂移。
files_list() {
cat <<'EOF'
cat <<EOF
path usr/sbin/isp-split
path etc/init.d/isp-split
dir usr/lib/isp-split
path etc/isp-split.conf
glob etc/isp-split.conf.bak.*
dir etc/isp-split
base $BASE
path etc/uci-defaults/99-isp-split
dir www/luci-static/resources/view/isp-split
dir www/luci-static/resources/isp-split
@@ -139,6 +195,7 @@ print_list() {
case "$_k" in
path) echo " $_v" ;;
dir) echo " $_v/(目录)" ;;
base) echo " $_v/(数据目录,随 ISP_SPLIT_BASE 推导)" ;;
glob) echo " $_v(历史备份,默认删除;--keep-conf-bak 保留)" ;;
esac
done
@@ -229,6 +286,31 @@ _state_read() {
return 0
}
# A-05:DNSDIR 白名单(与 isp-split 主脚本同一口径)。只允许 $BASE 子树
# 或 /tmp/dnsmasq*.d 形态;/etc、/、.. 一律不参与删除。
_dnsdir_ok() {
_ddo=${1:-}
case "$_ddo" in "" | "/" | "." | ".." | *..*) return 1 ;; esac
case "$_ddo" in /*) ;; *) return 1 ;; esac
while [ "$_ddo" != "/" ] && [ "${_ddo%/}" != "$_ddo" ]; do
_ddo=${_ddo%/}
done
case "$_ddo" in
"$BASE"/*) return 0 ;;
/tmp/dnsmasq*.d | /tmp/*/dnsmasq*.d) return 0 ;;
esac
return 1
}
# 删除片段的根目录限制:绝不 rm -f /isp-split.conf 或 /etc/isp-split.conf。
_frag_delete_ok() {
_fdo=${1:-}
case "$_fdo" in "" | "/" | "/etc" | "." | ".." | *..*) return 1 ;; esac
case "$_fdo" in /*) ;; *) return 1 ;; esac
[ -d "$_fdo" ] || return 1
return 0
}
# 片段可能残留的目录:运行配置里的 conf-dir=(权威源)+ UCI 当前值 +
# 状态文件记录值 + 默认目录 + 旧版目录,去重后逐行输出。
_frag_dirs() {
@@ -238,7 +320,8 @@ _frag_dirs() {
sed -n 's/^[[:space:]]*conf-dir=//p' "$_fd_f" 2>/dev/null
done
printf '%s\n' "${_frag_uci:-}" "${_frag_state:-}" \
"$DNSMASQ_DEFAULT_DIR" "$DNSDIR"
"$DNSMASQ_DEFAULT_DIR"
_dnsdir_ok "$DNSDIR" && printf '%s\n' "$DNSDIR"
} | tr ' ' '\n' |
sed -e 's/,.*$//' -e 's/^"//' -e 's/"$//' -e 's:/*$::' |
awk 'NF && !seen[$0]++'
@@ -272,6 +355,7 @@ cmd_runtime() {
# 2) 清片段。探测来源优先,但不依赖探测是否成功——目录列表是并集。
_n=0
for _d in $(_frag_dirs); do
_frag_delete_ok "$_d" || continue
[ -f "$_d/isp-split.conf" ] || continue
rm -f "$_d/isp-split.conf" 2>/dev/null && _n=$((_n + 1))
done
@@ -313,6 +397,19 @@ cmd_files() {
_err=0
while read -r _k _p; do
case "$_k" in
base)
# A-10:数据目录不再写死 etc/isp-split,而是跟随 BASE。
# 默认路径与自定义路径走同一条删除分支,避免“清单删默认、
# BASE 再删一次”的双路径。
[ -n "$BASE" ] || continue
[ -e "$BASE" ] || [ -L "$BASE" ] || continue
if rm -rf "$BASE"; then
_del=$((_del + 1))
else
say "错误:删不掉 $BASE"
_err=1
fi
;;
path)
[ -e "$DESTDIR/$_p" ] || [ -L "$DESTDIR/$_p" ] || continue
if rm -f "$DESTDIR/$_p"; then
@@ -358,17 +455,9 @@ cmd_files() {
$(files_list)
EOF
# BASE 可被 ISP_SPLIT_BASE 改名(自检沙箱),清单里的是默认路径,这里补上
# 真实值。ISP_SPLIT_LIB 故意不参与删除:它只用于离线自检,可能指到源码树,
# 删它会把仓库自己的 usr/lib/isp-split 抹掉。
if [ "$BASE" != "$DESTDIR/etc/isp-split" ] && [ -d "$BASE" ]; then
if rm -rf "$BASE"; then
_del=$((_del + 1))
else
say "错误:删不掉 $BASE"
_err=1
fi
fi
# ISP_SPLIT_LIB 故意不参与删除:它只用于离线自检,可能指到源码树,
# 删它会把仓库自己的 usr/lib/isp-split 抹掉。数据目录已在上面的
# base 分支随 BASE 推导删除(A-10)。
if [ "$_del" -gt 0 ]; then
say "已删除 $_del 项文件/目录"
@@ -387,6 +476,7 @@ EOF
while [ $# -gt 0 ]; do
case "$1" in
--keep-conf-bak) KEEP_CONF_BAK=1 ;;
--i-know-what-im-doing) IWKD=1 ;;
--list) SCOPE=list ;;
all | mwan3 | runtime | files) SCOPE=$1 ;;
-h | --help)
@@ -402,10 +492,13 @@ while [ $# -gt 0 ]; do
done
if [ -z "$SCOPE" ]; then
echo "用法: sh teardown.sh all|mwan3|runtime|files|--list [--keep-conf-bak]" >&2
echo "用法: sh teardown.sh all|mwan3|runtime|files|--list [--keep-conf-bak] [--i-know-what-im-doing]" >&2
exit 2
fi
# A-10:任何实际动作(含 --list 之外的删除)之前先验证 BASE。
validate_base || exit 1
case "$SCOPE" in
list) print_list ;;
mwan3) cmd_mwan3 ;;
File diff suppressed because it is too large. Load diff